Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 197 additions & 10 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2037,19 +2037,20 @@
../bashunit assert not_contains 'Ds\Seq' "$OUTPUT"
- script: |
cd e2e/bug-15407
XDEBUG_HANDLER_DEBUG=1 ../../bin/phpstan analyse --no-progress 2>&1 | tee -a output.txt
../bashunit assert file_contains output.txt "Process restarting"
../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt
../bashunit assert file_contains output.txt "Restarting process: pcov (pcov.enabled=1)"
coverage: "pcov"
- script: |
cd e2e/bug-15407
XDEBUG_HANDLER_DEBUG=1 ../../bin/phpstan analyse --no-progress 2>&1 | tee -a output.txt
../bashunit assert file_contains output.txt "Process restarting"
../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt
../bashunit assert file_contains output.txt "Restarting process: pcov (pcov.enabled=1)"
coverage: "pcov"
ini-values: "pcov.enabled=1"
- script: |
cd e2e/bug-15407
XDEBUG_HANDLER_DEBUG=1 ../../bin/phpstan analyse --no-progress 2>&1 | tee -a output.txt
../bashunit assert file_not_contains output.txt "Process restarting"
# the restart may still happen to activate OPcache, just not for pcov
../../bin/phpstan analyse --no-progress -vvv 2>&1 | tee -a output.txt
../bashunit assert file_not_contains output.txt "pcov (pcov.enabled"
coverage: "pcov"
ini-values: "pcov.enabled=0"

Expand Down Expand Up @@ -2117,8 +2118,8 @@
run: make build-turbo

- name: "Load the turbo extension through php.ini"
# TurboProcessRestarter re-executes the process with the loaded php.ini
# to activate OPcache; an extension passed as -d would not survive that,
# the restart re-executes the process with the loaded ini files to
# activate OPcache; an extension passed as -d would not survive that,
# one from the ini scan dir is loaded again
run: |
echo "extension=$PWD/turbo-ext/phpstan_turbo.so" | sudo tee "$(php -r 'echo PHP_CONFIG_FILE_SCAN_DIR;')/99-phpstan-turbo.ini"
Expand Down Expand Up @@ -2246,8 +2247,8 @@
make -j"$(nproc)"

- name: "Load both extensions through php.ini"
# TurboProcessRestarter re-executes the process with the loaded php.ini
# to activate OPcache; extensions passed as -d would not survive that,
# the restart re-executes the process with the loaded ini files to
# activate OPcache; extensions passed as -d would not survive that,
# ones from the ini scan dir are loaded again
run: |
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"
Expand Down Expand Up @@ -2281,3 +2282,189 @@
echo "$OUTPUT"
../bashunit assert contains 'Mechanism: fork (pcntl_fork)' "$OUTPUT"
../bashunit assert contains '[OK] No errors' "$OUTPUT"

process-restart-e2e-tests:
name: "Process restart E2E tests"
runs-on: ${{ matrix.operating-system }}
timeout-minutes: 30

defaults:
run:
shell: bash

strategy:
fail-fast: false
matrix:
include:
# pcov armed: one restart, every process - the forked worker
# included, which no per-worker `-d` injection could reach - runs
# disarmed. pcov stays loaded: the rows disarm rather than unload,
# which keeps the extension's own symbols reflectable. setup-php
# loads OPcache dormant on CLI, so the same restart activates it in
# every Linux row
- operating-system: "ubuntu-latest"
coverage: "pcov"
extensions: ""
script: |
php ../../bin/phpstan analyse --no-progress
php check.php restarted-for=pcov,opcache pcov-armed=no pcov-loaded=yes opcache-cli-on=yes processes=2+ starts=2

# Windows has no pcntl_exec, so the restart runs as a child process
# whose exit code is passed on. OPcache is left alone there: the
# workers are spawned, and ProcessHelper activates it for each
- operating-system: "windows-latest"
coverage: "pcov"
extensions: ""
script: |
php ../../bin/phpstan analyse --no-progress
php check.php restarted-for=pcov pcov-armed=no processes=1+ starts=2
set +e
php ../../bin/phpstan analyse --no-progress --configuration failing.neon
EXIT_CODE=$?
set -e
if [ "$EXIT_CODE" != "1" ]; then
echo "FAIL: the restarted process' exit code was not passed on, got $EXIT_CODE"
exit 1
fi

# xdebug: one restart disarms it via xdebug.mode=off, and it stays
# loaded so xdebug_*() remains reflectable. No note about it being
# active either
- operating-system: "ubuntu-latest"
coverage: "xdebug"
extensions: ""
script: |
php ../../bin/phpstan analyse --no-progress 2>&1 | tee out.txt
if grep -q 'The Xdebug PHP extension is active, but' out.txt; then
echo "FAIL: xdebug survived the restart armed"
exit 1
fi
php check.php restarted-for=xdebug,opcache xdebug-armed=no xdebug-loaded=yes processes=2+ starts=2

# XDEBUG_MODE overrides xdebug.mode, so flagging the ini alone would
# be silently overridable - the row exports the env var too
- operating-system: "ubuntu-latest"
coverage: "xdebug"
extensions: ""
script: |
XDEBUG_MODE=develop,debug php ../../bin/phpstan analyse --no-progress
php check.php restarted-for=xdebug,opcache xdebug-armed=no xdebug-loaded=yes processes=2+ starts=2

# --xdebug keeps xdebug armed and does not restart for it - only for
# OPcache
- operating-system: "ubuntu-latest"
coverage: "xdebug"
extensions: ""
script: |
php ../../bin/phpstan analyse --no-progress --xdebug 2>&1 | tee out.txt
if ! grep -q 'enabled, and the Xdebug PHP extension is active' out.txt; then
echo "FAIL: --xdebug did not keep xdebug"
exit 1
fi
php check.php restarted-for=opcache xdebug-armed=yes processes=1+ starts=2

# xdebug and pcov together: one restart handles both. Both come in
# through `extensions` because setup-php's `coverage: xdebug` unloads
# pcov. xdebug is armed by its default mode, pcov on the command
# line - which also proves the composed ini carries runtime `-d`
# overrides over without reconstructing them from $argv
- operating-system: "ubuntu-latest"
coverage: ""
extensions: "xdebug, pcov"
script: |
php -d pcov.enabled=1 ../../bin/phpstan analyse --no-progress
php check.php restarted-for=xdebug,pcov,opcache pcov-armed=no xdebug-armed=no processes=2+ starts=2

# --xdebug with pcov armed: xdebug stays armed, pcov is still
# disarmed by the same restart - something xdebug-handler could never
# express
- operating-system: "ubuntu-latest"
coverage: ""
extensions: "xdebug, pcov"
script: |
php -d pcov.enabled=1 ../../bin/phpstan analyse --no-progress --xdebug
php check.php restarted-for=pcov,opcache pcov-armed=no xdebug-armed=yes processes=1+ starts=2

# uopz rewrites opcodes unless uopz.disable=1
- operating-system: "ubuntu-latest"
coverage: "none"
extensions: "uopz"
script: |
php ../../bin/phpstan analyse --no-progress
php check.php restarted-for=uopz,opcache uopz-armed=no processes=2+ starts=2

# OPcache is dormant on CLI by default - the restart activates it,
# and the forked workers inherit the parent's warm opcode cache
- operating-system: "ubuntu-latest"
coverage: "none"
extensions: "opcache"
script: |
php -d opcache.enable_cli=0 ../../bin/phpstan analyse --no-progress -vvv > out.txt 2>&1
cat out.txt
php check.php restarted-for=opcache opcache-cli-on=yes processes=2+ starts=2
if ! grep -qE 'Mechanism: +fork' out.txt; then
echo "FAIL: the workers were not forked under the activated OPcache"
exit 1
fi

# --debug restarts like any other run - it only makes the analysis
# serial, --xdebug is what keeps xdebug
- operating-system: "ubuntu-latest"
coverage: "pcov"
extensions: ""
script: |
php ../../bin/phpstan analyse --no-progress --debug
php check.php restarted-for=pcov,opcache pcov-armed=no processes=1 starts=2

# diagnose never restarts either, it reports what a run would do
- operating-system: "ubuntu-latest"
coverage: "pcov"
extensions: ""
script: |
php ../../bin/phpstan diagnose 2>&1 | tee out.txt
if ! grep -qE 'Restart if started now: +yes - pcov' out.txt; then
echo "FAIL: diagnose did not report the restart it would perform"
exit 1
fi

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: "Checkout"
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
persist-credentials: false

- name: "Install PHP"
uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
coverage: ${{ matrix.coverage }}
php-version: "8.2"
extensions: ${{ matrix.extensions }}

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

- name: "Test"
working-directory: "e2e/restart-manager"
env:
SCRIPT: ${{ matrix.script }}
# Symfony wraps its notes to the terminal width and prefixes wrapped
# lines with "!", which can split a phrase in a way no post-processing
# rejoins - and would make the negative assertions pass vacuously
COLUMNS: "400"
run: |
# count every start of the main process, the one before the restart
# included - which only an ini-level hook sees
LINE=$(php -r 'echo "auto_prepend_file=\"", getcwd(), DIRECTORY_SEPARATOR, "start-log.php\"";')
INI=$(php -r 'echo php_ini_loaded_file();')
if [ "$RUNNER_OS" = "Windows" ]; then
echo "$LINE" >> "$INI"
else
echo "$LINE" | sudo tee -a "$INI" > /dev/null
fi
php ../../bin/phpstan clear-result-cache
rm -f probe.log starts.log
eval "$SCRIPT"
6 changes: 3 additions & 3 deletions .github/workflows/phar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ jobs:

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

- name: "Install PHP"
# Deliberately without `ini-file: development`: that ini lists the
# pcntl functions in disable_functions, and TurboProcessRestarter then
# returns before TurboExtensionSelector ever runs. With pcntl available
# pcntl functions in disable_functions, and RestartManager then never
# asks TurboExtensionSelector for the binary. With pcntl available
# the phar boots through the turbo restart code, which bin/phpstan
# loads before the Composer autoloader - on PHP < 8.0 without the
# symfony polyfills (https://github.com/phpstan/phpstan/issues/15137).
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ e2e/bashunit
/e2e/result-cache-stale-objects/tmp
/e2e/result-cache-truncated/tmp
/e2e/result-cache-worker-count/tmp
e2e/restart-manager/probe.log
e2e/restart-manager/starts.log
e2e/restart-manager/out.txt
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ download-coverage:
# `make build-turbo PHP_CONFIG=/path/to/php-config` (command-line variables
# reach the sub-make through MAKEFLAGS). Nothing is installed: load the
# result with `php -d extension=...`, or from the ini scan dir when the run
# re-executes itself (TurboProcessRestarter drops a -d extension).
# re-executes itself (RestartManager drops a -d extension).
TURBO_JOBS ?= $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)

.PHONY: build-turbo
Expand Down
11 changes: 8 additions & 3 deletions bin/phpstan
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use PHPStan\Command\FixerWorkerCommand;
use PHPStan\Command\ResultCacheInfoCommand;
use PHPStan\Command\WorkerCommand;
use PHPStan\Internal\ComposerHelper;
use PHPStan\Process\Restart\RestartManager;
use PHPStan\Turbo\TurboExtensionEnabler;
use PHPStan\Turbo\TurboProcessRestarter;
use Symfony\Component\Console\Helper\ProgressBar;

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

require_once __DIR__ . '/../src/Turbo/TurboExtensionEnabler.php';
require_once __DIR__ . '/../src/Turbo/TurboExtensionSelector.php';
require_once __DIR__ . '/../src/Turbo/TurboProcessRestarter.php';
TurboProcessRestarter::restartIfSuitable($_SERVER['argv']);
require_once __DIR__ . '/../src/Process/Restart/RestartReason.php';
require_once __DIR__ . '/../src/Process/Restart/OpcacheSettings.php';
require_once __DIR__ . '/../src/Process/Restart/ExtensionRegistry.php';
require_once __DIR__ . '/../src/Process/Restart/IniComposer.php';
require_once __DIR__ . '/../src/Process/Restart/RestartExecutor.php';
require_once __DIR__ . '/../src/Process/Restart/RestartManager.php';
RestartManager::run($_SERVER['argv']);
TurboExtensionEnabler::trustOwnTypesIfSuitable($_SERVER['argv']);

$analysisStartTime = microtime(true);
Expand Down
16 changes: 15 additions & 1 deletion build/composer-dependency-analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@
$unknownClasses[] = 'PHPStanTurbo\Runtime';
}

$unknownFunctions = [];
foreach ([
'uopz_allow_exit', // ext-uopz, only called when the extension is loaded
'sapi_windows_set_ctrl_handler', // Windows-only, guarded by function_exists()
] as $function) {
// conditional for the same reason as PHPStanTurbo\Runtime above
if (function_exists($function)) {
continue;
}

$unknownFunctions[] = $function;
}

return $config
->addPathToScan(__DIR__ . '/../bin', true)
->ignoreErrorsOnPackages(
Expand All @@ -44,4 +57,5 @@
)
->ignoreErrorsOnPackage('phpunit/phpunit', [ErrorType::DEV_DEPENDENCY_IN_PROD]) // prepared test tooling
->ignoreErrorsOnPath(__DIR__ . '/../tests', [ErrorType::UNKNOWN_CLASS, ErrorType::UNKNOWN_FUNCTION, ErrorType::SHADOW_DEPENDENCY]) // to be able to test invalid symbols
->ignoreUnknownClasses($unknownClasses);
->ignoreUnknownClasses($unknownClasses)
->ignoreUnknownFunctions($unknownFunctions);
2 changes: 1 addition & 1 deletion build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ parameters:
-
# called from bin/phpstan before the autoloader, outside the analysed paths
identifier: shipmonk.deadMethod
message: '#^Unused PHPStan\\Turbo\\TurboProcessRestarter::restartIfSuitable#'
message: '#^Unused PHPStan\\Process\\Restart\\RestartManager::run#'
reportUnmatched: false
-
# called from bin/phpstan before the autoloader, outside the analysed paths
Expand Down
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"composer-runtime-api": "^2.0",
"clue/ndjson-react": "^1.0",
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.3",
"fidry/cpu-core-counter": "^1.2",
"guzzlehttp/guzzle": "^7.9",
"hoa/compiler": "3.17.08.08",
Expand Down
Loading
Loading