Skip to content

Spell out the ini files, sys_temp_dir and xdebug.mode of the current process on spawned PHP command lines - #6401

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-q8y5ayq
Open

Spell out the ini files, sys_temp_dir and xdebug.mode of the current process on spawned PHP command lines#6401
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-q8y5ayq

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

php -d xdebug.mode=off vendor/bin/phpstan turned Xdebug off for the main process only. Every parallel worker loaded Xdebug again from the ini scan directory in whatever mode the ini says, so the analysis ran under an active Xdebug — several times slower, with nothing on screen saying so.

The cause is that a child process inherits the environment but nothing of the command line. The worker command repeated -c <php.ini> and stopped there: -d entries were dropped, and the ini scan directory was read again even when the spawning process had not read it.

Changes

  • New src/Process/InheritedPhpConfig.php: resolves the PHP CLI options a child process needs to run with the PHP configuration of the process starting it — -n when this process read no additional ini files, -c for the loaded php.ini, -d sys_temp_dir= and -d xdebug.mode=. Xdebug's mode is read with ini_get(), falling back to get_cfg_var() for a PHP that has no Xdebug loaded and therefore no registered directive.
  • src/Process/ProcessHelper.php: the worker command (parallel analysis workers and the fixer worker) is built from those options instead of -c php_ini_loaded_file() alone. sys_temp_dir moved into the shared class.
  • src/Turbo/TurboProcessRestarter.php: the same gap on the process it re-executes. The command line assembly moved into a testable resolveRestartArgs() and now starts from the inherited options.
  • src/Command/FixerApplication.php: the PHPStan Pro process, which passed no -c at all.
  • src/Command/BisectCommand.php: each bisect step, which runs a full PHPStan phar of its own.
  • src/Parallel/ForkParallelChecker.php: phpstan diagnose prints the php options a spawned worker gets, next to the -d entries it already listed.
  • bin/phpstan: requires the new file before TurboProcessRestarter::restartIfSuitable(), which runs before the autoloader.

Probed and left alone: ForkedProcess / ForkParallelChecker (a forked worker inherits the whole process, including its ini state, so there is nothing to repeat), LevelsTestCase (extension-developer test infrastructure, not a PHPStan run), and memory_limit on the bisect child (the three other spawn sites forward it, but bisect deliberately builds its analyse args from the user's own --memory-limit option, so changing that is a separate decision).

Root cause

The pattern is "PHP configuration a child process does not inherit". PHP_INI_SCAN_DIR and PHPRC — what composer/xdebug-handler sets up for its persistent restart — travel in the environment and need no help. Everything given on the command line travels nowhere, and PHPStan starts four PHP processes:

site before
ProcessHelper::getWorkerCommand() -c <php.ini>, -d sys_temp_dir=
TurboProcessRestarter::restartIfSuitable() -c <php.ini>
FixerApplication (PHPStan Pro) nothing
BisectCommand (phar per step) nothing

Two consequences followed from that, and both are fixed by resolving the options in one place:

  • -d entries are dropped. With xdebug.mode=off on the command line, XdebugHandler sees an inactive Xdebug and rightly does not restart, so no PHPRC is set up either and nothing else keeps Xdebug out of the children. Each worker then started with an active Xdebug, and — because the worker runs CommandHelper::begin() too — restarted itself through xdebug-handler, spawning another process per worker and losing the sys_temp_dir, OPcache and turbo-extension entries of the spawn in the process.
  • The ini scan directory is read again. Right when the spawning process read it too, wrong when it did not: php -n, or a -c pointing elsewhere, gave the child every extension and setting the main process was deliberately started without. -n next to an explicit -c is what xdebug-handler's own standard restart does.

sys_temp_dir is the same bug seen from the other side: only the worker command repeated it, so a -d sys_temp_dir= was lost as soon as TurboProcessRestarter re-executed the main process.

Test

  • tests/PHPStan/Process/InheritedPhpConfigTest.php
    • testResolveArgs() pins the resolved options for each combination of loaded/scanned ini files and Xdebug mode, including the empty string the ini parser makes of xdebug.mode=off.
    • testChildProcessRepeatsThePhpConfigurationOfItsParent() is the regression test for the report: it starts a real PHP process (as started, -d xdebug.mode=off, -n), which starts a child of its own with the resolved options, and compares the loaded ini file, whether additional ini files were scanned, xdebug.mode and the temp directory of the two. With the previous -c-only command line the -d xdebug.mode=off case reports coverage in the child against `` in the parent, and the -n case reports the whole scanned ini set in the child against none in the parent. It needs no Xdebug installed to run.
  • tests/PHPStan/Process/ProcessHelperTest.php::testWorkerCommandRepeatsThePhpConfigurationOfTheSpawningProcess() — the worker command starts with those options.
  • tests/PHPStan/Turbo/TurboProcessRestarterTest.php — two tests for resolveRestartArgs(), covering the inherited options and the turbo extension entries.

Fixes phpstan/phpstan#15189

…ent process on spawned PHP command lines

- Add `PHPStan\Process\InheritedPhpConfig`, which resolves the PHP CLI options
  a child process needs to run with the PHP configuration of the process
  starting it: `-n` when this process read no additional ini files, `-c` for
  the loaded php.ini, `-d sys_temp_dir=` and `-d xdebug.mode=`.
- `ProcessHelper::getWorkerCommand()` builds the worker command from it instead
  of passing `-c php_ini_loaded_file()` alone, so a worker no longer re-reads
  the ini scan directory the main process was started without, and no longer
  loses an `xdebug.mode` set on the command line.
- `TurboProcessRestarter` had the same gap on the process it re-executes -
  extracted `resolveRestartArgs()` and prepended the inherited options there,
  so `php -d xdebug.mode=off vendor/bin/phpstan` no longer restarts into a
  process with Xdebug active again (and `-d sys_temp_dir=` no longer gets lost
  across the restart, which only the worker command used to repeat).
- Same fix for the two other PHP processes PHPStan starts: the PHPStan Pro
  process in `FixerApplication` (which passed no `-c` at all) and the phar of
  each `bisect` step in `BisectCommand`.
- `phpstan diagnose` prints the resulting php options for spawned workers.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant