From 1a4f2174e45089f928afa73d91cb4670b06bb58c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Jul 2022 17:00:20 +0200 Subject: [PATCH 1/2] Add seld/signal-handler dependency --- composer.json | 3 +- composer.lock | 63 ++++++++++++++++++- src/Composer/Command/CreateProjectCommand.php | 37 +++++------ src/Composer/Command/RequireCommand.php | 30 ++++----- src/Composer/Console/Application.php | 7 --- .../Installer/InstallationManager.php | 52 +++------------ .../functional/installed-versions.test | 8 +-- .../functional/installed-versions2.test | 12 ++-- 8 files changed, 109 insertions(+), 103 deletions(-) diff --git a/composer.json b/composer.json index 1377dccb09d2..bc9792e98933 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "react/promise": "^2.8", "composer/pcre": "^2 || ^3", "symfony/polyfill-php73": "^1.24", - "symfony/polyfill-php80": "^1.24" + "symfony/polyfill-php80": "^1.24", + "seld/signal-handler": "^2.0" }, "require-dev": { "symfony/phpunit-bridge": "^6.0", diff --git a/composer.lock b/composer.lock index cdfb70766eb9..4d0b2df9b759 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0d8cb08c58ec6422f5894ce04cff0d41", + "content-hash": "9ee8eb32dddfba816d9eb589ade6f4e4", "packages": [ { "name": "composer/ca-bundle", @@ -878,6 +878,67 @@ }, "time": "2021-12-10T11:20:11+00:00" }, + { + "name": "seld/signal-handler", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "f69d119511dc0360440cdbdaa71829c149b7be75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/f69d119511dc0360440cdbdaa71829c149b7be75", + "reference": "f69d119511dc0360440cdbdaa71829c149b7be75", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.1" + }, + "time": "2022-07-20T18:31:45+00:00" + }, { "name": "symfony/console", "version": "v5.4.10", diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 9721656b9511..ec4ad575aee8 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -35,6 +35,7 @@ use Composer\Script\ScriptEvents; use Composer\Util\Silencer; use Composer\Console\Input\InputArgument; +use Seld\Signal\SignalHandler; use Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -452,28 +453,15 @@ protected function installRootPackage(IOInterface $io, Config $config, string $p throw new \InvalidArgumentException($errorMessage .'.'); } - // handler Ctrl+C for unix-like systems - if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { - @mkdir($directory, 0777, true); - if ($realDir = realpath($directory)) { - pcntl_async_signals(true); - pcntl_signal(SIGINT, static function () use ($realDir): void { - $fs = new Filesystem(); - $fs->removeDirectory($realDir); - exit(130); - }); - } - } - // handler Ctrl+C for Windows on PHP 7.4+ - if (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli') { - @mkdir($directory, 0777, true); - if ($realDir = realpath($directory)) { - sapi_windows_set_ctrl_handler(static function () use ($realDir): void { - $fs = new Filesystem(); - $fs->removeDirectory($realDir); - exit(130); - }); - } + // handler Ctrl+C aborts gracefully + @mkdir($directory, 0777, true); + if (false !== ($realDir = realpath($directory))) { + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) { + $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $fs = new Filesystem(); + $fs->removeDirectory($realDir); + $handler->exitWithLastSignal(); + }); } // avoid displaying 9999999-dev as version if default-branch was selected @@ -512,6 +500,11 @@ protected function installRootPackage(IOInterface $io, Config $config, string $p Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion()); + // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install + if (isset($signalHandler)) { + $signalHandler->unregister(); + } + return $installedFromVcs; } } diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index b648166db475..c980cafe97aa 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -14,6 +14,7 @@ use Composer\DependencyResolver\Request; use Composer\Util\Filesystem; +use Seld\Signal\SignalHandler; use Symfony\Component\Console\Input\InputInterface; use Composer\Console\Input\InputArgument; use Composer\Console\Input\InputOption; @@ -148,12 +149,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->composerBackup = file_get_contents($this->json->getPath()); $this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null; - if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { - pcntl_async_signals(true); - pcntl_signal(SIGINT, function () { $this->revertComposerFile(); }); - pcntl_signal(SIGTERM, function () { $this->revertComposerFile(); }); - pcntl_signal(SIGHUP, function () { $this->revertComposerFile(); }); - } + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { + $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $this->revertComposerFile(); + $handler->exitWithLastSignal(); + }); // check for writability by writing to the file as is_writable can not be trusted on network-mounts // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 @@ -210,7 +210,7 @@ protected function execute(InputInterface $input, OutputInterface $output) ); } catch (\Exception $e) { if ($this->newlyCreated) { - $this->revertComposerFile(false); + $this->revertComposerFile(); throw new \RuntimeException('No composer.json present in the current directory ('.$this->file.'), this may be the cause of the following exception.', 0, $e); } @@ -293,9 +293,11 @@ protected function execute(InputInterface $input, OutputInterface $output) return $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey); } catch (\Exception $e) { if (!$this->dependencyResolutionCompleted) { - $this->revertComposerFile(false); + $this->revertComposerFile(); } throw $e; + } finally { + $signalHandler->unregister(); } } @@ -439,7 +441,7 @@ private function doUpdate(InputInterface $input, OutputInterface $output, IOInte } } } - $this->revertComposerFile(false); + $this->revertComposerFile(); } return $status; @@ -479,11 +481,7 @@ protected function interact(InputInterface $input, OutputInterface $output): voi } - /** - * @param bool $hardExit - * @return void - */ - public function revertComposerFile(bool $hardExit = true): void + private function revertComposerFile(): void { $io = $this->getIO(); @@ -504,9 +502,5 @@ public function revertComposerFile(bool $hardExit = true): void file_put_contents($this->lock, $this->lockBackup); } } - - if ($hardExit) { - exit(1); - } } } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 9964fe72c597..07f9108b335b 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -94,13 +94,6 @@ public function __construct() } if (!$shutdownRegistered) { - if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) { - pcntl_async_signals(true); - pcntl_signal(SIGINT, static function ($sig): void { - exit(130); - }); - } - $shutdownRegistered = true; register_shutdown_function(static function (): void { diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index 74a6a229e992..dce5329f47ae 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -28,6 +28,7 @@ use Composer\Util\Loop; use Composer\Util\Platform; use React\Promise\PromiseInterface; +use Seld\Signal\SignalHandler; /** * Package operation manager. @@ -222,36 +223,11 @@ public function execute(InstalledRepositoryInterface $repo, array $operations, b } }; - $handleInterruptsUnix = function_exists('pcntl_async_signals') && function_exists('pcntl_signal'); - $handleInterruptsWindows = PHP_VERSION_ID >= 70400 && function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli'; - $prevHandler = null; - $windowsHandler = null; - if ($handleInterruptsUnix) { - pcntl_async_signals(true); - $prevHandler = pcntl_signal_get_handler(SIGINT); - pcntl_signal(SIGINT, static function ($sig) use ($runCleanup, $prevHandler, $io): void { - $io->writeError('Received SIGINT, aborting', true, IOInterface::DEBUG); - $runCleanup(); - - if (!in_array($prevHandler, array(SIG_DFL, SIG_IGN), true)) { - call_user_func($prevHandler, $sig); - } - - exit(130); - }); - } - if ($handleInterruptsWindows) { - $windowsHandler = static function ($event) use ($runCleanup, $io): void { - if ($event !== PHP_WINDOWS_EVENT_CTRL_C) { - return; - } - $io->writeError('Received CTRL+C, aborting', true, IOInterface::DEBUG); - $runCleanup(); - - exit(130); - }; - sapi_windows_set_ctrl_handler($windowsHandler); - } + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], static function (string $signal, SignalHandler $handler) use ($io, $runCleanup) { + $io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + $runCleanup(); + $handler->exitWithLastSignal(); + }); try { // execute operations in batches to make sure download-modifying-plugins are installed @@ -284,21 +260,9 @@ public function execute(InstalledRepositoryInterface $repo, array $operations, b } catch (\Exception $e) { $runCleanup(); - if ($handleInterruptsUnix) { - pcntl_signal(SIGINT, $prevHandler); - } - if ($handleInterruptsWindows) { - sapi_windows_set_ctrl_handler($windowsHandler, false); - } - throw $e; - } - - if ($handleInterruptsUnix) { - pcntl_signal(SIGINT, $prevHandler); - } - if ($handleInterruptsWindows) { - sapi_windows_set_ctrl_handler($windowsHandler, false); + } finally { + $signalHandler->unregister(); } // do a last write so that we write the repository even if nothing changed diff --git a/tests/Composer/Test/Fixtures/functional/installed-versions.test b/tests/Composer/Test/Fixtures/functional/installed-versions.test index 85dfefe9dc9d..c50686711750 100644 --- a/tests/Composer/Test/Fixtures/functional/installed-versions.test +++ b/tests/Composer/Test/Fixtures/functional/installed-versions.test @@ -8,7 +8,7 @@ Checks that package versions in InstalledVersions are correct on initial install update --EXPECT-- > Hooks::preUpdate -!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string"] +!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% Loading composer repositories with package information %((Info|Warning) from .*\n)?%Updating dependencies @@ -26,12 +26,12 @@ Package operations: 6 installs, 0 updates, 0 removals%(\nAs there is no 'unzip' - Downloading symfony/filesystem (%v?[2-8]\.\d+\.\d+%) - Installing symfony/console (99999.1.2): Symlinking from symfony-console - Installing plugin/a (1.1.1): Symlinking from plugin-a -!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","root/pkg"] +!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","root/pkg"] !!PluginA:null !!PluginB:null !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% - Installing plugin/b (2.2.2): Symlinking from plugin-b -!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","root/pkg"] +!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","root/pkg"] !!PluginA:1.1.1.0 !!PluginB:null !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% @@ -42,7 +42,7 @@ Generating autoload files 2 packages you are using are looking for funding. Use the `composer fund` command to find out more! > Hooks::postUpdate -!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% --EXPECT-EXIT-CODE-- diff --git a/tests/Composer/Test/Fixtures/functional/installed-versions2.test b/tests/Composer/Test/Fixtures/functional/installed-versions2.test index a9d2c5a9e5bb..a2dc9f9b8a9e 100644 --- a/tests/Composer/Test/Fixtures/functional/installed-versions2.test +++ b/tests/Composer/Test/Fixtures/functional/installed-versions2.test @@ -7,14 +7,14 @@ Checks that package versions in InstalledVersions are correct during an upgrade. --RUN-- update plugin/* symfony/console symfony/filesystem symfony/process --EXPECT-- -!!PluginA:1.1.1.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginA:1.1.1.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginB:2.2.2.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% -!!PluginB:2.2.2.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginB:2.2.2.0["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginA:1.1.1.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% > Hooks::preUpdate -!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PreUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% Loading composer repositories with package information %((Info|Warning) from .*\n)?%Updating dependencies @@ -30,12 +30,12 @@ Package operations: 0 installs, 5 updates, 0 removals%(\nAs there is no 'unzip' - Downloading symfony/filesystem (%v?[2-8]\.\d+\.\d+%) - Upgrading symfony/console (99999.1.2 => 99999.1.3): Mirroring from symfony-console - Upgrading plugin/a (1.1.1 => 1.1.2): Mirroring from plugin-a -!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginAInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginA:1.1.1.0 !!PluginB:2.2.2.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% - Upgrading plugin/b (2.2.2 => 2.2.3): Mirroring from plugin-b -!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PluginBInit["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!PluginA:1.1.2.0 !!PluginB:2.2.2.0 !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% @@ -45,7 +45,7 @@ Generating autoload files 2 packages you are using are looking for funding. Use the `composer fund` command to find out more! > Hooks::postUpdate -!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] +!!PostUpdate:["composer/ca-bundle","composer/class-map-generator","composer/composer","composer/metadata-minifier","composer/pcre","composer/semver","composer/spdx-licenses","composer/xdebug-handler","justinrainbow/json-schema","psr/container","psr/log","psr/log-implementation","react/promise","seld/jsonlint","seld/phar-utils","seld/signal-handler","symfony/console","symfony/deprecation-contracts","symfony/filesystem","symfony/finder","symfony/polyfill-ctype","symfony/polyfill-intl-grapheme","symfony/polyfill-intl-normalizer","symfony/polyfill-mbstring","symfony/polyfill-php73","symfony/polyfill-php80","symfony/process","symfony/service-contracts","symfony/string","plugin/a","plugin/b","root/pkg"] !!Versions:console:%[2-8]\.\d+\.\d+.0%;process:%[2-8]\.\d+\.\d+.0%;filesystem:%[2-8]\.\d+\.\d+.0% !!PluginA:1.1.2.0 !!PluginB:2.2.3.0 From 07645b98952c995bf77020cb278d4e83426eccc3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 20 Jul 2022 22:08:44 +0200 Subject: [PATCH 2/2] Capture signals and wait until child process exits to also exit, fixes #6059 --- src/Composer/Console/Application.php | 11 ++++++-- .../ScriptExecutionException.php | 2 ++ src/Composer/Util/ProcessExecutor.php | 27 +++++++++++++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 07f9108b335b..315379a2c505 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -17,6 +17,7 @@ use Composer\Util\Platform; use Composer\Util\Silencer; use LogicException; +use Seld\Signal\SignalHandler; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Helper\HelperSet; @@ -93,7 +94,15 @@ public function __construct() date_default_timezone_set(Silencer::call('date_default_timezone_get')); } + $this->io = new NullIO(); + if (!$shutdownRegistered) { + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) { + $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG); + + $handler->exitWithLastSignal(); + }); + $shutdownRegistered = true; register_shutdown_function(static function (): void { @@ -107,8 +116,6 @@ public function __construct() }); } - $this->io = new NullIO(); - $this->initialWorkingDirectory = getcwd(); parent::__construct('Composer', Composer::getVersion()); diff --git a/src/Composer/EventDispatcher/ScriptExecutionException.php b/src/Composer/EventDispatcher/ScriptExecutionException.php index 3b05a8831f7d..72a4aa273156 100644 --- a/src/Composer/EventDispatcher/ScriptExecutionException.php +++ b/src/Composer/EventDispatcher/ScriptExecutionException.php @@ -13,6 +13,8 @@ namespace Composer\EventDispatcher; /** + * Thrown when a script running an external process exits with a non-0 status code + * * @author Jordi Boggiano */ class ScriptExecutionException extends \RuntimeException diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 768acbc217ff..3f57239bf0a9 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -14,6 +14,8 @@ use Composer\IO\IOInterface; use Composer\Pcre\Preg; +use Seld\Signal\SignalHandler; +use Symfony\Component\Process\Exception\ProcessSignaledException; use Symfony\Component\Process\Process; use Symfony\Component\Process\Exception\RuntimeException; use React\Promise\Promise; @@ -125,13 +127,28 @@ private function doExecute($command, ?string $cwd, bool $tty, &$output = null): $this->outputHandler($type, $buffer); }; - $process->run($callback); + $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal) { + if ($this->io !== null) { + $this->io->writeError('Received '.$signal.', aborting when child process is done', true, IOInterface::DEBUG); + } + }); - if ($this->captureOutput && !is_callable($output)) { - $output = $process->getOutput(); - } + try { + $process->run($callback); - $this->errorOutput = $process->getErrorOutput(); + if ($this->captureOutput && !is_callable($output)) { + $output = $process->getOutput(); + } + + $this->errorOutput = $process->getErrorOutput(); + } catch (ProcessSignaledException $e) { + if ($signalHandler->isTriggered()) { + // exiting as we were signaled and the child process exited too due to the signal + $signalHandler->exitWithLastSignal(); + } + } finally { + $signalHandler->unregister(); + } return $process->getExitCode(); }