Repository navigation
Expand file tree
/
Copy pathForkParallelChecker.php
More file actions
126 lines (109 loc) · 4.54 KB
/
Copy pathForkParallelChecker.php
File metadata and controls
126 lines (109 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php declare(strict_types = 1);
namespace PHPStan\Parallel;
use Phar;
use PHPStan\Command\Output;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Diagnose\DiagnoseExtension;
use PHPStan\Process\InheritedPhpConfig;
use PHPStan\Process\ProcessHelper;
use PHPStan\Turbo\TurboExtensionEnabler;
use PHPStan\Turbo\TurboProcessRestarter;
use function function_exists;
use function getmypid;
use function implode;
use function opcache_get_status;
use function sprintf;
use function str_starts_with;
use const PHP_OS_FAMILY;
/**
* Decides whether parallel analysis should fork workers via pcntl_fork()
* (see ForkedProcess) instead of spawning fresh PHP processes (see SpawnedProcess).
*
* Fork is used whenever possible — a forked worker inherits the booted
* process and skips the application re-boot a spawned one pays. It requires:
*
* - pcntl/posix functions (not available on Windows — always spawn there)
* - when running from a phar, the turbo extension, active with the expected
* version. The extension arms the phar-fork-guard: libphar serves phar://
* reads through one shared per-archive fd whose seek cursor forked
* processes race on, corrupting reads — the guard gives each forked child
* a private cursor. TurboProcessRestarter re-executes the main process
* with the distributed binary loaded when needed (a forked worker inherits
* the parent's extensions; the spawn-time `-d extension=` injection cannot
* reach it). Outside a phar there is nothing to guard, and no turbo is
* lost by forking either: the distributed binaries only exist next to a
* phar, so spawned workers of a source checkout run without turbo too —
* an ini-loaded extension is inherited by fork like any other.
* - JIT off — its shared code buffer is not safe to populate concurrently
* from forked children. Plain OPcache is fine: concurrent population of
* its SHM under the allocator lock is php-fpm's normal operating model,
* and TurboProcessRestarter deliberately activates it for the restarted
* process (with JIT pinned off).
*
* A forked worker ends with the extension's _exit() instead of PHP's full
* teardown (see ForkedChildTerminator): the child inherits every loaded
* extension, and a fork-unsafe one wedges its module shutdown.
*/
#[AutowiredService]
final class ForkParallelChecker implements DiagnoseExtension
{
public function isSupported(): bool
{
return $this->getDisabledReason() === null;
}
public function print(Output $output): void
{
$output->writeLineFormatted('<info>Parallel worker creation:</info>');
$reason = $this->getDisabledReason();
if ($reason === null) {
$output->writeLineFormatted('Mechanism: fork (pcntl_fork)');
$output->writeLineFormatted('');
return;
}
$output->writeLineFormatted('Mechanism: spawn (react/child-process)');
$output->writeLineFormatted(sprintf('Reason fork not used: %s', $reason));
// what a spawned worker's command line spells out for it (see
// ProcessHelper); the extension path is on the turbo lines
$output->writeLineFormatted(sprintf('Worker php options: %s', implode(' ', InheritedPhpConfig::getArgs())));
$parentPid = getmypid();
$output->writeLineFormatted('Worker -d entries:');
foreach (ProcessHelper::resolveWorkerIniEntries(TurboProcessRestarter::getOpcacheArgs(), PHP_OS_FAMILY, $parentPid === false ? 0 : $parentPid, 1) as $iniEntry) {
$output->writeLineFormatted(sprintf(
' %s%s',
$iniEntry,
str_starts_with($iniEntry, 'opcache.cache_id=') ? ' (numbered per worker)' : '',
));
}
$output->writeLineFormatted('');
}
private function getDisabledReason(): ?string
{
if (
!function_exists('pcntl_fork')
|| !function_exists('pcntl_waitpid')
|| !function_exists('pcntl_wifexited')
|| !function_exists('pcntl_wexitstatus')
|| !function_exists('posix_kill')
) {
return 'pcntl/posix functions are not available';
}
if (Phar::running(false) !== '' && !TurboExtensionEnabler::isActive()) {
return 'running from a phar without the active turbo extension (its fork guard protects phar:// reads in forked children)';
}
if ($this->isJitEnabled()) {
return 'JIT is enabled (forked workers require JIT to be off — their shared memory corrupts under concurrent population)';
}
return null;
}
private function isJitEnabled(): bool
{
if (!function_exists('opcache_get_status')) {
return false;
}
$status = opcache_get_status(false);
if ($status === false) {
return false;
}
return ($status['jit']['enabled'] ?? false) === true;
}
}