Skip to content

Commit a4d403a

Browse files
committed
Experiment: fork parallel workers via pcntl_fork()
2 parents f8dd187 + 9e6a5cd commit a4d403a

18 files changed

Lines changed: 1524 additions & 758 deletions

build/baseline-7.4.neon

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ parameters:
1111
path: ../src/Parallel/ParallelAnalyser.php
1212

1313
-
14-
message: "#^Class PHPStan\\\\Parallel\\\\Process has an uninitialized property \\$process\\. Give it default value or assign it in the constructor\\.$#"
14+
message: "#^Class PHPStan\\\\Parallel\\\\SpawnedProcess has an uninitialized property \\$process\\. Give it default value or assign it in the constructor\\.$#"
1515
count: 1
16-
path: ../src/Parallel/Process.php
16+
path: ../src/Parallel/SpawnedProcess.php
1717
-
1818
message: "#^Class PHPStan\\\\PhpDoc\\\\ResolvedPhpDocBlock has an uninitialized property \\$phpDocNodes\\. Give it default value or assign it in the constructor\\.$#"
1919
count: 1

composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
"phpstan/phpstan": "2.1.x",
6565
"symfony/polyfill-php73": "*"
6666
},
67+
"suggest": {
68+
"ext-pcntl": "Enables forking parallel analysis workers from the already-booted process (experimental, skips the per-worker re-boot)",
69+
"ext-posix": "Used together with ext-pcntl for forked parallel analysis workers"
70+
},
6771
"require-dev": {
6872
"cweagans/composer-patches": "^1.7.3",
6973
"php-parallel-lint/php-parallel-lint": "^1.2.0",

src/Command/AnalyseCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,7 @@ private function runFixer(InceptionResult $inceptionResult, Container $container
840840
$fixerApplication = $container->getByType(FixerApplication::class);
841841

842842
return $fixerApplication->run(
843-
$inceptionResult->getProjectConfigFile(),
843+
$inceptionResult,
844844
$input,
845845
$output,
846846
count($files),

src/Command/AnalyserRunner.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public function runAnalyser(
8383
if ($mainScript !== null && $schedule->getNumberOfProcesses() > 0) {
8484
$loop = new StreamSelectLoop();
8585
$result = null;
86-
$promise = $this->parallelAnalyser->analyse($loop, $schedule, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input, null);
86+
$promise = $this->parallelAnalyser->analyse($loop, $schedule, $allAnalysedFiles, $mainScript, $postFileCallback, $projectConfigFile, $tmpFile, $insteadOfFile, $input, null);
8787
$promise->then(static function (AnalyserResult $tmp) use (&$result): void {
8888
$result = $tmp;
8989
});

src/Command/FixerApplication.php

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@
1919
use PHPStan\File\FileMonitorResult;
2020
use PHPStan\File\FileReader;
2121
use PHPStan\File\FileWriter;
22+
use PHPStan\File\PathNotFoundException;
2223
use PHPStan\Internal\ComposerHelper;
2324
use PHPStan\Internal\DirectoryCreator;
2425
use PHPStan\Internal\DirectoryCreatorException;
2526
use PHPStan\Internal\HttpClientFactory;
27+
use PHPStan\Parallel\ForkParallelChecker;
2628
use PHPStan\PhpDoc\StubFilesProvider;
29+
use PHPStan\Process\ForkedProcessPromise;
2730
use PHPStan\Process\ProcessCanceledException;
2831
use PHPStan\Process\ProcessCrashedException;
2932
use PHPStan\Process\ProcessHelper;
3033
use PHPStan\Process\ProcessPromise;
34+
use PHPStan\Process\SpawnedProcessPromise;
3135
use PHPStan\ShouldNotHappenException;
3236
use React\ChildProcess\Process;
3337
use React\EventLoop\LoopInterface;
@@ -94,18 +98,22 @@ public function __construct(
9498
#[AutowiredParameter]
9599
private string $usedLevel,
96100
private HttpClientFactory $httpClientFactory,
101+
private ForkParallelChecker $forkParallelChecker,
102+
private FixerWorkerRunner $fixerWorkerRunner,
97103
)
98104
{
99105
}
100106

101107
public function run(
102-
?string $projectConfigFile,
108+
InceptionResult $inceptionResult,
103109
InputInterface $input,
104110
OutputInterface $output,
105111
int $filesCount,
106112
string $mainScript,
107113
): int
108114
{
115+
$projectConfigFile = $inceptionResult->getProjectConfigFile();
116+
109117
$loop = new StreamSelectLoop();
110118
$server = new TcpServer('127.0.0.1:0', $loop);
111119
/** @var string $serverAddress */
@@ -114,7 +122,7 @@ public function run(
114122
/** @var int<0, 65535> $serverPort */
115123
$serverPort = parse_url($serverAddress, PHP_URL_PORT);
116124

117-
$server->on('connection', function (ConnectionInterface $connection) use ($loop, $projectConfigFile, $input, $output, $mainScript, $filesCount): void {
125+
$server->on('connection', function (ConnectionInterface $connection) use ($loop, $inceptionResult, $projectConfigFile, $input, $output, $mainScript, $filesCount): void {
118126
// phpcs:disable SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly
119127
$jsonInvalidUtf8Ignore = defined('JSON_INVALID_UTF8_IGNORE') ? JSON_INVALID_UTF8_IGNORE : 0;
120128
// phpcs:enable
@@ -157,14 +165,15 @@ public function run(
157165

158166
$this->analyse(
159167
$loop,
168+
$inceptionResult,
160169
$mainScript,
161170
$projectConfigFile,
162171
$input,
163172
$output,
164173
$encoder,
165174
);
166175

167-
$this->monitorFileChanges($loop, function (FileMonitorResult $changes) use ($loop, $mainScript, $projectConfigFile, $input, $encoder, $output): void {
176+
$this->monitorFileChanges($loop, function (FileMonitorResult $changes) use ($loop, $inceptionResult, $mainScript, $projectConfigFile, $input, $encoder, $output): void {
168177
if ($this->processInProgress !== null) {
169178
$this->processInProgress->cancel();
170179
$this->processInProgress = null;
@@ -178,6 +187,7 @@ public function run(
178187

179188
$this->analyse(
180189
$loop,
190+
$inceptionResult,
181191
$mainScript,
182192
$projectConfigFile,
183193
$input,
@@ -417,6 +427,7 @@ private function monitorFileChanges(LoopInterface $loop, callable $hasChangesCal
417427

418428
private function analyse(
419429
LoopInterface $loop,
430+
InceptionResult $inceptionResult,
420431
string $mainScript,
421432
?string $projectConfigFile,
422433
InputInterface $input,
@@ -446,16 +457,16 @@ private function analyse(
446457
});
447458
});
448459

449-
$process = new ProcessPromise($loop, ProcessHelper::getWorkerCommand(
460+
$process = $this->createProcessPromise(
461+
$this->forkParallelChecker->isSupported(),
462+
$loop,
463+
$server,
450464
$mainScript,
451-
'fixer:worker',
452465
$projectConfigFile,
453-
[
454-
'--server-port',
455-
(string) $serverPort,
456-
],
457466
$input,
458-
));
467+
$serverPort,
468+
$inceptionResult,
469+
);
459470
$this->processInProgress = $process->run();
460471

461472
$this->processInProgress->then(function () use ($server): void {
@@ -566,4 +577,51 @@ private function getStubFiles(): array
566577
return $stubFiles;
567578
}
568579

580+
/**
581+
* @param int<0, 65535> $serverPort
582+
*/
583+
private function createProcessPromise(
584+
bool $useFork,
585+
LoopInterface $loop,
586+
TcpServer $server,
587+
string $mainScript,
588+
?string $projectConfigFile,
589+
InputInterface $input,
590+
int $serverPort,
591+
InceptionResult $inceptionResult,
592+
): ProcessPromise
593+
{
594+
if ($useFork) {
595+
try {
596+
[$inceptionFiles, $isOnlyFiles] = $inceptionResult->getFiles();
597+
} catch (PathNotFoundException | InceptionNotSuccessfulException) {
598+
throw new ShouldNotHappenException();
599+
}
600+
601+
return new ForkedProcessPromise(
602+
$loop,
603+
$this->fixerWorkerRunner,
604+
$server,
605+
$inceptionResult->getErrorOutput(),
606+
$inceptionFiles,
607+
$isOnlyFiles,
608+
$inceptionResult->getProjectConfigArray(),
609+
$projectConfigFile,
610+
$serverPort,
611+
$input,
612+
);
613+
}
614+
615+
return new SpawnedProcessPromise($loop, ProcessHelper::getWorkerCommand(
616+
$mainScript,
617+
'fixer:worker',
618+
$projectConfigFile,
619+
[
620+
'--server-port',
621+
(string) $serverPort,
622+
],
623+
$input,
624+
));
625+
}
626+
569627
}

0 commit comments

Comments
 (0)