Skip to content

Multi extension restart manager, to restart once for all known reasons - #6773

Open
AJenbo wants to merge 1 commit into
phpstan:2.3.xfrom
AJenbo:restart-manager
Open

AJenbo wants to merge 1 commit into
phpstan:2.3.xfrom
AJenbo:restart-manager

Conversation

@AJenbo

@AJenbo AJenbo commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Rewrite of #6253 that replaces composer/xdebug-handler, the xdebug/pcov/tideways ProcessRestarter from #6757 and the turbo/OPcache-only TurboProcessRestarter with one restart manager in PHPStan\Process\Restart. It runs from bin/phpstan before the Composer autoloader. It collects every reason to restart, then re-executes the process once.

It overlaps with #6769, which has the same goal; see the comparison there.

How it works

  • ExtensionRegistry is a table of reasons. Each reason says how to neutralize its extension:
    • xdebug: xdebug.mode=off, plus XDEBUG_MODE=off, because the env var overrides the ini.
    • pcov: pcov.enabled=0.
    • uopz: uopz.disable=1.
    • tideways: stripped from the ini, only when the turbo extension runs (Turbo 2.3.0 incompatible with Tideways phpstan#15407).
    • phpstan_turbo: loaded with -d extension= from next to the phar.
    • OPcache: activated with the settings the 2.3.x restarter already used, on hosts that can fork.
  • Disarm instead of unload where a switch exists. pcov and xdebug stay loaded but disarmed, so their functions stay reflectable and the analysis result doesn't change. Only Tideways is stripped: no switch is known to keep its hooks out.
  • IniComposer writes one composed php.ini: every loaded ini file, with stripped extensions commented out, then the runtime ini_get_all() diff so -d overrides survive, then the forced settings.
    • The composed ini is exported through PHPRC. Spawned workers read it, and forked workers inherit the configuration anyway.
    • A marker env var (PHPSTAN_RESTART=<reasons>) prevents a second restart.
  • RestartExecutor uses pcntl_exec(). Without it (Windows), it runs the restarted command as a child process and passes its exit code on.
  • Cleanup: the restarted process deletes the composed ini on shutdown. A small watchdog forked before the autoloader also deletes it after SIGKILL or a crash.

Behaviour changes

  • One restart instead of up to three. Restart once for Turbo, OPcache and Xdebug #6769 counted four starts of the main process for pcov/xdebug runs on 2.3.x; this makes it two, asserted in e2e.
  • pcov is disarmed rather than unloaded.
  • Tideways is only stripped when the turbo extension runs. Without turbo it doesn't crash anything.
  • --debug restarts like any other run, as on 2.3.x. --xdebug (or the deprecated PHPSTAN_ALLOW_XDEBUG) keeps xdebug armed; pcov and uopz are still disarmed, and the option description now says so.
  • No restart for the worker, diagnose, clear-result-cache and dump-parameters commands, or under Blackfire. Blackfire's probe cannot follow pcntl_exec().
  • diagnose has a "Process restart" section: what was found, what the process was restarted for, and what a run started now would restart for.
  • Reporting: -vvv prints the restart line, and so does XDEBUG_HANDLER_DEBUG, for CI configurations that already set it.
  • Dependencies: drops composer/xdebug-handler, and with it composer/pcre and psr/log.

API

  • TurboProcessRestarter::restartIfSuitable() (@api since Stop the recursion when an enum case value reads itself #6731/807d030f6f) is kept as a facade.
    • Without an app name: it restarts only for turbo, tideways and OPcache, as before.
    • With the new optional $appName: e.g. 'rector', it applies every reason and honours --xdebug and <APP>_ALLOW_XDEBUG. A program like Rector can then drop its own xdebug-handler restart and get one restart in total.
  • Everything in PHPStan\Process\Restart is internal.

Tests

  • Unit tests for the reason table, the ini composer, the eligibility rules and the OPcache settings. The OPcache tests moved over from TurboProcessRestarterTest.
  • New Process restart E2E tests job:
    • Coverage: pcov, xdebug, XDEBUG_MODE, --xdebug, xdebug and pcov together, uopz, OPcache, --debug and diagnose, plus a Windows run of the proc_open() path that checks the exit code is passed on.
    • What it asserts: a probe in bootstrapFiles records each process, forked workers included. An auto_prepend_file counts the starts of the main process. Every row asserts exactly one restart and the expected armed/disarmed state everywhere, and that no composed ini is left behind.
  • The bug-15407 e2e rows from Restart process when pcov or tideways extension is loaded #6757/Improve pcov e2e tests #6770 now assert the new restart line, and that pcov is disabled rather than unloaded.

Related:

Comment thread .github/workflows/e2e-tests.yml Fixed
Comment thread .github/workflows/e2e-tests.yml Fixed
Comment thread .github/workflows/e2e-tests.yml Fixed
persist-credentials: false

- name: "Install PHP"
uses: "shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962" # 2.40.0
php-version: "8.2"
extensions: ${{ matrix.extensions }}

- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
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.

2 participants