Browse Source

feat: Ability to set upper limit when using CPU auto-detection (#8280)

Oleksandr Prypkhan 3 months ago
parent
commit
42f3692224

+ 1 - 1
composer.json

@@ -27,7 +27,7 @@
         "clue/ndjson-react": "^1.0",
         "composer/semver": "^3.4",
         "composer/xdebug-handler": "^3.0.3",
-        "fidry/cpu-core-counter": "^1.0",
+        "fidry/cpu-core-counter": "^1.2",
         "react/child-process": "^0.6.5",
         "react/event-loop": "^1.0",
         "react/promise": "^2.0 || ^3.0",

+ 7 - 2
src/Runner/Parallel/ParallelConfigFactory.php

@@ -35,10 +35,12 @@ final class ParallelConfigFactory
     /**
      * @param null|positive-int $filesPerProcess
      * @param null|positive-int $processTimeout
+     * @param null|positive-int $maxProcesses
      */
     public static function detect(
         ?int $filesPerProcess = null,
-        ?int $processTimeout = null
+        ?int $processTimeout = null,
+        ?int $maxProcesses = null
     ): ParallelConfig {
         if (null === self::$cpuDetector) {
             self::$cpuDetector = new CpuCoreCounter([
@@ -47,8 +49,11 @@ final class ParallelConfigFactory
             ]);
         }
 
+        // Reserve 1 core for the main orchestrating process
+        $available = self::$cpuDetector->getAvailableForParallelisation(1, $maxProcesses);
+
         return new ParallelConfig(
-            self::$cpuDetector->getCount(),
+            $available->availableCpus,
             $filesPerProcess ?? ParallelConfig::DEFAULT_FILES_PER_PROCESS,
             $processTimeout ?? ParallelConfig::DEFAULT_PROCESS_TIMEOUT
         );

+ 47 - 13
tests/Runner/Parallel/ParallelConfigFactoryTest.php

@@ -27,6 +27,13 @@ use PhpCsFixer\Tests\TestCase;
  */
 final class ParallelConfigFactoryTest extends TestCase
 {
+    protected function tearDown(): void
+    {
+        $this->mockCpuCount(null);
+
+        parent::tearDown();
+    }
+
     public function testSequentialConfigHasExactlyOneProcess(): void
     {
         $config = ParallelConfigFactory::sequential();
@@ -39,29 +46,32 @@ final class ParallelConfigFactoryTest extends TestCase
      */
     public function testDetectConfigurationWithoutParams(): void
     {
-        \Closure::bind(static function (): void {
-            ParallelConfigFactory::$cpuDetector = new CpuCoreCounter([
-                new DummyCpuCoreFinder(7),
-            ]);
-        }, null, ParallelConfigFactory::class)();
+        $this->mockCpuCount(7);
 
         $config = ParallelConfigFactory::detect();
 
-        self::assertSame(7, $config->getMaxProcesses());
+        self::assertSame(6, $config->getMaxProcesses());
         self::assertSame(ParallelConfig::DEFAULT_FILES_PER_PROCESS, $config->getFilesPerProcess());
         self::assertSame(ParallelConfig::DEFAULT_PROCESS_TIMEOUT, $config->getProcessTimeout());
-
-        \Closure::bind(static function (): void {
-            ParallelConfigFactory::$cpuDetector = null;
-        }, null, ParallelConfigFactory::class)();
     }
 
     public function testDetectConfigurationWithParams(): void
     {
-        $config = ParallelConfigFactory::detect(22, 2_200);
+        $this->mockCpuCount(7);
+
+        $config1 = ParallelConfigFactory::detect(22, 2_200, 5);
+
+        self::assertSame(5, $config1->getMaxProcesses());
+        self::assertSame(22, $config1->getFilesPerProcess());
+        self::assertSame(2_200, $config1->getProcessTimeout());
+
+        $config2 = ParallelConfigFactory::detect(22, 2_200, 6);
+
+        self::assertSame(6, $config2->getMaxProcesses());
 
-        self::assertSame(22, $config->getFilesPerProcess());
-        self::assertSame(2_200, $config->getProcessTimeout());
+        $config3 = ParallelConfigFactory::detect(22, 2_200, 10);
+
+        self::assertSame(6, $config3->getMaxProcesses());
     }
 
     public function testDetectConfigurationWithDefaultValue(): void
@@ -77,6 +87,8 @@ final class ParallelConfigFactoryTest extends TestCase
      */
     public function testDetectConfigurationWithNamedArgs(): void
     {
+        $this->mockCpuCount(7);
+
         // First argument omitted, second one provided via named argument
         $config1 = \call_user_func_array([ParallelConfigFactory::class, 'detect'], ['processTimeout' => 300]);
 
@@ -85,10 +97,12 @@ final class ParallelConfigFactoryTest extends TestCase
 
         // Flipped order of arguments using named arguments syntax
         $config2 = \call_user_func_array([ParallelConfigFactory::class, 'detect'], [
+            'maxProcesses' => 1,
             'processTimeout' => 300,
             'filesPerProcess' => 5,
         ]);
 
+        self::assertSame(1, $config2->getMaxProcesses());
         self::assertSame(5, $config2->getFilesPerProcess());
         self::assertSame(300, $config2->getProcessTimeout());
 
@@ -97,5 +111,25 @@ final class ParallelConfigFactoryTest extends TestCase
 
         self::assertSame(7, $config3->getFilesPerProcess());
         self::assertSame(ParallelConfig::DEFAULT_PROCESS_TIMEOUT, $config3->getProcessTimeout());
+
+        // Only third argument provided, but via named argument
+        $config3 = \call_user_func_array([ParallelConfigFactory::class, 'detect'], ['maxProcesses' => 1]);
+
+        self::assertSame(1, $config3->getMaxProcesses());
+        self::assertSame(ParallelConfig::DEFAULT_FILES_PER_PROCESS, $config3->getFilesPerProcess());
+        self::assertSame(ParallelConfig::DEFAULT_PROCESS_TIMEOUT, $config3->getProcessTimeout());
+    }
+
+    /**
+     * @param null|positive-int $count
+     */
+    private function mockCpuCount(?int $count): void
+    {
+        \Closure::bind(static function () use ($count): void {
+            ParallelConfigFactory::$cpuDetector = null !== $count ?
+                new CpuCoreCounter([
+                    new DummyCpuCoreFinder($count),
+                ]) : null;
+        }, null, ParallelConfigFactory::class)();
     }
 }