Repository navigation
Restart once for Turbo, OPcache and Xdebug #6769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2.3.x
Are you sure you want to change the base?
Changes from all commits
64d29e9
12da374
1583686
91eb784
772a45b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| parameters: | ||
| level: 0 | ||
| paths: | ||
| - test.php |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| // Loaded through auto_prepend_file in php.ini. Writes one line for every | ||
| // start of the main "analyse" process, so the test can count the restarts. | ||
| if (in_array('analyse', $_SERVER['argv'] ?? [], true)) { | ||
| file_put_contents(__DIR__ . '/starts.log', sprintf( | ||
| "opcache=%d pcov=%d xdebug=%d\n", | ||
| (int) ini_get('opcache.enable_cli'), | ||
| (int) (extension_loaded('pcov') && ini_get('pcov.enabled') === '1'), | ||
| (int) extension_loaded('xdebug'), | ||
| ), FILE_APPEND); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| <?php | ||
|
|
||
| echo 'Hello'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,21 +4,128 @@ | |
|
|
||
| use Composer\XdebugHandler\XdebugHandler; | ||
| use Override; | ||
| use function array_merge; | ||
| use function array_slice; | ||
| use function count; | ||
| use function extension_loaded; | ||
| use function fclose; | ||
| use function file_get_contents; | ||
| use function file_put_contents; | ||
| use function function_exists; | ||
| use function fwrite; | ||
| use function getenv; | ||
| use function getmypid; | ||
| use function implode; | ||
| use function in_array; | ||
| use function ini_get; | ||
| use function pcntl_exec; | ||
| use function pcntl_fork; | ||
| use function pcntl_signal; | ||
| use function posix_getppid; | ||
| use function preg_replace; | ||
| use function sprintf; | ||
| use function strtoupper; | ||
| use function unlink; | ||
| use function usleep; | ||
| use const PHP_EOL; | ||
| use const SIG_IGN; | ||
| use const SIGHUP; | ||
| use const SIGINT; | ||
| use const SIGQUIT; | ||
| use const SIGTERM; | ||
| use const STDERR; | ||
| use const STDIN; | ||
| use const STDOUT; | ||
|
|
||
| // see https://github.com/composer/xdebug-handler/issues/174 | ||
| final class ProcessRestarter extends XdebugHandler | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would it make more sense that we consolidate the 2 restarter classes? the PR as is has the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, |
||
| { | ||
|
|
||
| private const WATCHDOG_POLL_MICROSECONDS = 100_000; | ||
|
|
||
| /** @var array<string> */ | ||
| private array $extensions = []; | ||
|
|
||
| /** @var list<string>|null */ | ||
| private ?array $execIniArgs = null; | ||
|
|
||
| private string $envAllowXdebugName; | ||
|
|
||
| public function __construct(string $envPrefix) | ||
| { | ||
| parent::__construct($envPrefix); | ||
| $this->envAllowXdebugName = strtoupper($envPrefix) . self::SUFFIX_ALLOW; | ||
| } | ||
|
|
||
| /** | ||
| * Replaces this process via pcntl_exec() instead of waiting for a child, | ||
| * with these `-d` entries on top of the stripped ini. TurboProcessRestarter | ||
| * passes its own entries, so one restart covers both of them. | ||
| * | ||
| * @param list<string> $iniArgs `name=value` entries | ||
| */ | ||
| public function setExecIniArgs(array $iniArgs): self | ||
| { | ||
| $this->execIniArgs = $iniArgs; | ||
| return $this; | ||
| } | ||
|
|
||
| /** | ||
| * Prints the lines that XdebugHandler::doRestart() prints under | ||
| * XDEBUG_HANDLER_DEBUG, which the exec skips. Its Status class is internal, | ||
| * so the format is repeated here. | ||
| * | ||
| * @param non-empty-list<string> $command | ||
| */ | ||
| private function reportRestarting(array $command): void | ||
| { | ||
| $debug = getenv(self::DEBUG); | ||
| if (in_array($debug, [false, ''], true)) { | ||
| return; | ||
| } | ||
|
|
||
| $lines = [ | ||
| sprintf('Process restarting (%s=%s)', $this->envAllowXdebugName, (string) getenv($this->envAllowXdebugName)), | ||
| sprintf('Running: [%s]', implode(', ', $command)), | ||
| ]; | ||
| foreach ($lines as $line) { | ||
| fwrite(STDERR, sprintf('xdebug-handler[%d] %s%s', (int) getmypid(), $line, PHP_EOL)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Nobody waits for the exec, so a forked process takes the role of the | ||
| * waiting parent of a proc_open() restart: it ignores Ctrl-C, outlives the | ||
| * main process and then removes the ini. It ends by exec'ing rm, so PHP's | ||
| * shutdown sequence (shutdown functions, destructors, Xdebug output) never | ||
| * runs in it. | ||
| */ | ||
| private function startTmpIniWatchdog(): bool | ||
| { | ||
| $tmpIni = (string) $this->tmpIni; | ||
| $parentPid = getmypid(); | ||
| $pid = pcntl_fork(); | ||
| if ($pid === -1) { | ||
| return false; | ||
| } | ||
| if ($pid > 0) { | ||
| return true; | ||
| } | ||
|
|
||
| foreach ([SIGINT, SIGTERM, SIGHUP, SIGQUIT] as $signal) { | ||
| pcntl_signal($signal, SIG_IGN); | ||
| } | ||
| fclose(STDIN); | ||
| fclose(STDOUT); | ||
| fclose(STDERR); | ||
| while (posix_getppid() === $parentPid) { | ||
| usleep(self::WATCHDOG_POLL_MICROSECONDS); | ||
| } | ||
| pcntl_exec('/bin/rm', ['-f', $tmpIni]); | ||
| @unlink($tmpIni); | ||
| exit(0); | ||
| } | ||
|
|
||
| #[Override] | ||
| protected function requiresRestart(bool $default): bool | ||
| { | ||
|
|
@@ -50,6 +157,17 @@ protected function restart(array $command): void | |
| } | ||
| } | ||
|
|
||
| if ($this->execIniArgs !== null && function_exists('pcntl_exec') && $this->startTmpIniWatchdog()) { | ||
| $args = []; | ||
| foreach ($this->execIniArgs as $iniArg) { | ||
| $args[] = '-d'; | ||
| $args[] = $iniArg; | ||
| } | ||
| $this->reportRestarting(array_merge([$command[0]], $args, array_slice($command, 1))); | ||
| pcntl_exec($command[0], array_merge($args, array_slice($command, 1))); | ||
| // pcntl_exec() returns only on failure, wait for a child instead | ||
| } | ||
|
|
||
| parent::restart($command); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this line now is doing the xdebug auto-restart, I think we can drop the lines in CommandHelper?
phpstan-src/src/Command/CommandHelper.php
Lines 106 to 111 in a7e2b6a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet in this version. Without pcntl (Windows, or pcntl in
disable_functions), thecheck()in CommandHelper is still the only Xdebug restart. In the restarted process it also feeds the--debugnote. Both can move into bin/phpstan, so CommandHelper keeps only the notes. I'd do that together with the class merge from the other thread.