Skip to content

Commit 98afb69

Browse files
AJenboondrejmirtes
authored andcommitted
Implement own multi extension restart manager
1 parent 7785596 commit 98afb69

52 files changed

Lines changed: 2878 additions & 692 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/e2e-tests.yml‎

Lines changed: 197 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,19 +2037,20 @@ jobs:
20372037
../bashunit assert not_contains 'Ds\Seq' "$OUTPUT"
20382038
- script: |
20392039
cd e2e/bug-15407
2040-
XDEBUG_HANDLER_DEBUG=1 ../../bin/phpstan analyse --no-progress 2>&1 | tee -a output.txt
2041-
../bashunit assert file_contains output.txt "Process restarting"
2040+
../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt
2041+
../bashunit assert file_contains output.txt "Restarting process: pcov (pcov.enabled=1)"
20422042
coverage: "pcov"
20432043
- script: |
20442044
cd e2e/bug-15407
2045-
XDEBUG_HANDLER_DEBUG=1 ../../bin/phpstan analyse --no-progress 2>&1 | tee -a output.txt
2046-
../bashunit assert file_contains output.txt "Process restarting"
2045+
../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt
2046+
../bashunit assert file_contains output.txt "Restarting process: pcov (pcov.enabled=1)"
20472047
coverage: "pcov"
20482048
ini-values: "pcov.enabled=1"
20492049
- script: |
20502050
cd e2e/bug-15407
2051-
XDEBUG_HANDLER_DEBUG=1 ../../bin/phpstan analyse --no-progress 2>&1 | tee -a output.txt
2052-
../bashunit assert file_not_contains output.txt "Process restarting"
2051+
# the restart may still happen to activate OPcache, just not for pcov
2052+
../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt
2053+
../bashunit assert file_not_contains output.txt "pcov (pcov.enabled"
20532054
coverage: "pcov"
20542055
ini-values: "pcov.enabled=0"
20552056
@@ -2117,8 +2118,8 @@ jobs:
21172118
run: make build-turbo
21182119

21192120
- name: "Load the turbo extension through php.ini"
2120-
# TurboProcessRestarter re-executes the process with the loaded php.ini
2121-
# to activate OPcache; an extension passed as -d would not survive that,
2121+
# the restart re-executes the process with the loaded ini files to
2122+
# activate OPcache; an extension passed as -d would not survive that,
21222123
# one from the ini scan dir is loaded again
21232124
run: |
21242125
echo "extension=$PWD/turbo-ext/phpstan_turbo.so" | sudo tee "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-phpstan-turbo.ini"
@@ -2246,8 +2247,8 @@ jobs:
22462247
make -j"$(nproc)"
22472248
22482249
- name: "Load both extensions through php.ini"
2249-
# TurboProcessRestarter re-executes the process with the loaded php.ini
2250-
# to activate OPcache; extensions passed as -d would not survive that,
2250+
# the restart re-executes the process with the loaded ini files to
2251+
# activate OPcache; extensions passed as -d would not survive that,
22512252
# ones from the ini scan dir are loaded again
22522253
run: |
22532254
printf 'extension=%s\nextension=%s\n' "$PWD/turbo-ext/phpstan_turbo.so" "$RUNNER_TEMP/forkunsafe/modules/forkunsafe.so" | sudo tee "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-phpstan-turbo.ini"
@@ -2281,3 +2282,189 @@ jobs:
22812282
echo "$OUTPUT"
22822283
../bashunit assert contains 'Mechanism: fork (pcntl_fork)' "$OUTPUT"
22832284
../bashunit assert contains '[OK] No errors' "$OUTPUT"
2285+
2286+
process-restart-e2e-tests:
2287+
name: "Process restart E2E tests"
2288+
runs-on: ${{ matrix.operating-system }}
2289+
timeout-minutes: 30
2290+
2291+
defaults:
2292+
run:
2293+
shell: bash
2294+
2295+
strategy:
2296+
fail-fast: false
2297+
matrix:
2298+
include:
2299+
# pcov armed: one restart, every process - the forked worker
2300+
# included, which no per-worker `-d` injection could reach - runs
2301+
# disarmed. pcov stays loaded: the rows disarm rather than unload,
2302+
# which keeps the extension's own symbols reflectable. setup-php
2303+
# loads OPcache dormant on CLI, so the same restart activates it in
2304+
# every Linux row
2305+
- operating-system: "ubuntu-latest"
2306+
coverage: "pcov"
2307+
extensions: ""
2308+
script: |
2309+
php ../../bin/phpstan analyse --no-progress
2310+
php check.php restarted-for=pcov,opcache pcov-armed=no pcov-loaded=yes opcache-cli-on=yes processes=2+ starts=2
2311+
2312+
# Windows has no pcntl_exec, so the restart runs as a child process
2313+
# whose exit code is passed on. OPcache is left alone there: the
2314+
# workers are spawned, and ProcessHelper activates it for each
2315+
- operating-system: "windows-latest"
2316+
coverage: "pcov"
2317+
extensions: ""
2318+
script: |
2319+
php ../../bin/phpstan analyse --no-progress
2320+
php check.php restarted-for=pcov pcov-armed=no processes=1+ starts=2
2321+
set +e
2322+
php ../../bin/phpstan analyse --no-progress --configuration failing.neon
2323+
EXIT_CODE=$?
2324+
set -e
2325+
if [ "$EXIT_CODE" != "1" ]; then
2326+
echo "FAIL: the restarted process' exit code was not passed on, got $EXIT_CODE"
2327+
exit 1
2328+
fi
2329+
2330+
# xdebug: one restart disarms it via xdebug.mode=off, and it stays
2331+
# loaded so xdebug_*() remains reflectable. No note about it being
2332+
# active either
2333+
- operating-system: "ubuntu-latest"
2334+
coverage: "xdebug"
2335+
extensions: ""
2336+
script: |
2337+
php ../../bin/phpstan analyse --no-progress 2>&1 | tee out.txt
2338+
if grep -q 'The Xdebug PHP extension is active, but' out.txt; then
2339+
echo "FAIL: xdebug survived the restart armed"
2340+
exit 1
2341+
fi
2342+
php check.php restarted-for=xdebug,opcache xdebug-armed=no xdebug-loaded=yes processes=2+ starts=2
2343+
2344+
# XDEBUG_MODE overrides xdebug.mode, so flagging the ini alone would
2345+
# be silently overridable - the row exports the env var too
2346+
- operating-system: "ubuntu-latest"
2347+
coverage: "xdebug"
2348+
extensions: ""
2349+
script: |
2350+
XDEBUG_MODE=develop,debug php ../../bin/phpstan analyse --no-progress
2351+
php check.php restarted-for=xdebug,opcache xdebug-armed=no xdebug-loaded=yes processes=2+ starts=2
2352+
2353+
# --xdebug keeps xdebug armed and does not restart for it - only for
2354+
# OPcache
2355+
- operating-system: "ubuntu-latest"
2356+
coverage: "xdebug"
2357+
extensions: ""
2358+
script: |
2359+
php ../../bin/phpstan analyse --no-progress --xdebug 2>&1 | tee out.txt
2360+
if ! grep -q 'enabled, and the Xdebug PHP extension is active' out.txt; then
2361+
echo "FAIL: --xdebug did not keep xdebug"
2362+
exit 1
2363+
fi
2364+
php check.php restarted-for=opcache xdebug-armed=yes processes=1+ starts=2
2365+
2366+
# xdebug and pcov together: one restart handles both. Both come in
2367+
# through `extensions` because setup-php's `coverage: xdebug` unloads
2368+
# pcov. xdebug is armed by its default mode, pcov on the command
2369+
# line - which also proves the composed ini carries runtime `-d`
2370+
# overrides over without reconstructing them from $argv
2371+
- operating-system: "ubuntu-latest"
2372+
coverage: ""
2373+
extensions: "xdebug, pcov"
2374+
script: |
2375+
php -d pcov.enabled=1 ../../bin/phpstan analyse --no-progress
2376+
php check.php restarted-for=xdebug,pcov,opcache pcov-armed=no xdebug-armed=no processes=2+ starts=2
2377+
2378+
# --xdebug with pcov armed: xdebug stays armed, pcov is still
2379+
# disarmed by the same restart - something xdebug-handler could never
2380+
# express
2381+
- operating-system: "ubuntu-latest"
2382+
coverage: ""
2383+
extensions: "xdebug, pcov"
2384+
script: |
2385+
php -d pcov.enabled=1 ../../bin/phpstan analyse --no-progress --xdebug
2386+
php check.php restarted-for=pcov,opcache pcov-armed=no xdebug-armed=yes processes=1+ starts=2
2387+
2388+
# uopz rewrites opcodes unless uopz.disable=1
2389+
- operating-system: "ubuntu-latest"
2390+
coverage: "none"
2391+
extensions: "uopz"
2392+
script: |
2393+
php ../../bin/phpstan analyse --no-progress
2394+
php check.php restarted-for=uopz,opcache uopz-armed=no processes=2+ starts=2
2395+
2396+
# OPcache is dormant on CLI by default - the restart activates it,
2397+
# and the forked workers inherit the parent's warm opcode cache
2398+
- operating-system: "ubuntu-latest"
2399+
coverage: "none"
2400+
extensions: "opcache"
2401+
script: |
2402+
php -d opcache.enable_cli=0 ../../bin/phpstan analyse --no-progress -vvv > out.txt 2>&1
2403+
cat out.txt
2404+
php check.php restarted-for=opcache opcache-cli-on=yes processes=2+ starts=2
2405+
if ! grep -qE 'Mechanism: +fork' out.txt; then
2406+
echo "FAIL: the workers were not forked under the activated OPcache"
2407+
exit 1
2408+
fi
2409+
2410+
# --debug restarts like any other run - it only makes the analysis
2411+
# serial, --xdebug is what keeps xdebug
2412+
- operating-system: "ubuntu-latest"
2413+
coverage: "pcov"
2414+
extensions: ""
2415+
script: |
2416+
php ../../bin/phpstan analyse --no-progress --debug
2417+
php check.php restarted-for=pcov,opcache pcov-armed=no processes=1 starts=2
2418+
2419+
# diagnose never restarts either, it reports what a run would do
2420+
- operating-system: "ubuntu-latest"
2421+
coverage: "pcov"
2422+
extensions: ""
2423+
script: |
2424+
php ../../bin/phpstan diagnose 2>&1 | tee out.txt
2425+
if ! grep -qE 'Restart if started now: +yes - pcov' out.txt; then
2426+
echo "FAIL: diagnose did not report the restart it would perform"
2427+
exit 1
2428+
fi
2429+
2430+
steps:
2431+
- name: Harden the runner (Audit all outbound calls)
2432+
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
2433+
with:
2434+
egress-policy: audit
2435+
2436+
- name: "Checkout"
2437+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
2438+
with:
2439+
persist-credentials: false
2440+
2441+
- name: "Install PHP"
2442+
uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0
2443+
with:
2444+
coverage: ${{ matrix.coverage }}
2445+
php-version: "8.2"
2446+
extensions: ${{ matrix.extensions }}
2447+
2448+
- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
2449+
2450+
- name: "Test"
2451+
working-directory: "e2e/restart-manager"
2452+
env:
2453+
SCRIPT: ${{ matrix.script }}
2454+
# Symfony wraps its notes to the terminal width and prefixes wrapped
2455+
# lines with "!", which can split a phrase in a way no post-processing
2456+
# rejoins - and would make the negative assertions pass vacuously
2457+
COLUMNS: "400"
2458+
run: |
2459+
# count every start of the main process, the one before the restart
2460+
# included - which only an ini-level hook sees
2461+
LINE=$(php -r 'echo "auto_prepend_file=\"", getcwd(), DIRECTORY_SEPARATOR, "start-log.php\"";')
2462+
INI=$(php -r 'echo php_ini_loaded_file();')
2463+
if [ "$RUNNER_OS" = "Windows" ]; then
2464+
echo "$LINE" >> "$INI"
2465+
else
2466+
echo "$LINE" | sudo tee -a "$INI" > /dev/null
2467+
fi
2468+
php ../../bin/phpstan clear-result-cache
2469+
rm -f probe.log starts.log
2470+
eval "$SCRIPT"

‎.github/workflows/phar.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106

107107
# Box leaves the members with mtime 0 here, which OPcache refuses to
108108
# cache at all; the commit date also keeps consecutive builds apart for
109-
# opcache.validate_timestamps (see TurboProcessRestarter::resolveOpcacheArgs()).
109+
# opcache.validate_timestamps (see OpcacheSettings::resolveIniEntries()).
110110
# resign.php fails if any member is left at mtime 0.
111111
- name: "Stamp PHAR member timestamps"
112112
run: php compiler/build/resign.php tmp/phpstan.phar "$(git log -1 --format=%cI)"
@@ -2276,8 +2276,8 @@ jobs:
22762276

22772277
- name: "Install PHP"
22782278
# Deliberately without `ini-file: development`: that ini lists the
2279-
# pcntl functions in disable_functions, and TurboProcessRestarter then
2280-
# returns before TurboExtensionSelector ever runs. With pcntl available
2279+
# pcntl functions in disable_functions, and RestartManager then never
2280+
# asks TurboExtensionSelector for the binary. With pcntl available
22812281
# the phar boots through the turbo restart code, which bin/phpstan
22822282
# loads before the Composer autoloader - on PHP < 8.0 without the
22832283
# symfony polyfills (https://github.com/phpstan/phpstan/issues/15137).

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ e2e/bashunit
2323
/e2e/result-cache-stale-objects/tmp
2424
/e2e/result-cache-truncated/tmp
2525
/e2e/result-cache-worker-count/tmp
26+
e2e/restart-manager/probe.log
27+
e2e/restart-manager/starts.log
28+
e2e/restart-manager/out.txt

‎Makefile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ download-coverage:
225225
# `make build-turbo PHP_CONFIG=/path/to/php-config` (command-line variables
226226
# reach the sub-make through MAKEFLAGS). Nothing is installed: load the
227227
# result with `php -d extension=...`, or from the ini scan dir when the run
228-
# re-executes itself (TurboProcessRestarter drops a -d extension).
228+
# re-executes itself (RestartManager drops a -d extension).
229229
TURBO_JOBS ?= $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)
230230

231231
.PHONY: build-turbo

‎bin/phpstan‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use PHPStan\Command\FixerWorkerCommand;
1010
use PHPStan\Command\ResultCacheInfoCommand;
1111
use PHPStan\Command\WorkerCommand;
1212
use PHPStan\Internal\ComposerHelper;
13+
use PHPStan\Process\Restart\RestartManager;
1314
use PHPStan\Turbo\TurboExtensionEnabler;
14-
use PHPStan\Turbo\TurboProcessRestarter;
1515
use Symfony\Component\Console\Helper\ProgressBar;
1616

1717
(function () {
@@ -27,8 +27,13 @@ use Symfony\Component\Console\Helper\ProgressBar;
2727

2828
require_once __DIR__ . '/../src/Turbo/TurboExtensionEnabler.php';
2929
require_once __DIR__ . '/../src/Turbo/TurboExtensionSelector.php';
30-
require_once __DIR__ . '/../src/Turbo/TurboProcessRestarter.php';
31-
TurboProcessRestarter::restartIfSuitable($_SERVER['argv']);
30+
require_once __DIR__ . '/../src/Process/Restart/RestartReason.php';
31+
require_once __DIR__ . '/../src/Process/Restart/OpcacheSettings.php';
32+
require_once __DIR__ . '/../src/Process/Restart/ExtensionRegistry.php';
33+
require_once __DIR__ . '/../src/Process/Restart/IniComposer.php';
34+
require_once __DIR__ . '/../src/Process/Restart/RestartExecutor.php';
35+
require_once __DIR__ . '/../src/Process/Restart/RestartManager.php';
36+
RestartManager::run($_SERVER['argv']);
3237
TurboExtensionEnabler::trustOwnTypesIfSuitable($_SERVER['argv']);
3338

3439
$analysisStartTime = microtime(true);

‎build/composer-dependency-analyser.php‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@
3333
$unknownClasses[] = 'PHPStanTurbo\Runtime';
3434
}
3535

36+
$unknownFunctions = [];
37+
foreach ([
38+
'uopz_allow_exit', // ext-uopz, only called when the extension is loaded
39+
'sapi_windows_set_ctrl_handler', // Windows-only, guarded by function_exists()
40+
] as $function) {
41+
// conditional for the same reason as PHPStanTurbo\Runtime above
42+
if (function_exists($function)) {
43+
continue;
44+
}
45+
46+
$unknownFunctions[] = $function;
47+
}
48+
3649
return $config
3750
->addPathToScan(__DIR__ . '/../bin', true)
3851
->ignoreErrorsOnPackages(
@@ -44,4 +57,5 @@
4457
)
4558
->ignoreErrorsOnPackage('phpunit/phpunit', [ErrorType::DEV_DEPENDENCY_IN_PROD]) // prepared test tooling
4659
->ignoreErrorsOnPath(__DIR__ . '/../tests', [ErrorType::UNKNOWN_CLASS, ErrorType::UNKNOWN_FUNCTION, ErrorType::SHADOW_DEPENDENCY]) // to be able to test invalid symbols
47-
->ignoreUnknownClasses($unknownClasses);
60+
->ignoreUnknownClasses($unknownClasses)
61+
->ignoreUnknownFunctions($unknownFunctions);

‎build/phpstan.neon‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ parameters:
159159
-
160160
# called from bin/phpstan before the autoloader, outside the analysed paths
161161
identifier: shipmonk.deadMethod
162-
message: '#^Unused PHPStan\\Turbo\\TurboProcessRestarter::restartIfSuitable#'
162+
message: '#^Unused PHPStan\\Process\\Restart\\RestartManager::run#'
163163
reportUnmatched: false
164164
-
165165
# called from bin/phpstan before the autoloader, outside the analysed paths

‎composer.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"composer-runtime-api": "^2.0",
2121
"clue/ndjson-react": "^1.0",
2222
"composer/semver": "^3.4",
23-
"composer/xdebug-handler": "^3.0.3",
2423
"fidry/cpu-core-counter": "^1.2",
2524
"guzzlehttp/guzzle": "^7.9",
2625
"hoa/compiler": "3.17.08.08",

0 commit comments

Comments
 (0)