From e7df73c488cb64a841ac798ec5efc9d0927cf1ef Mon Sep 17 00:00:00 2001 From: Jigarius Date: Wed, 13 Dec 2023 17:17:51 +0530 Subject: [PATCH] Handle SIGINT gracefully --- src/Command/ExecCommand.php | 46 ++++++++++++++++++++++++++-- src/Trait/SignalAwareTrait.php | 17 ++++++++++ src/Trait/SiteDetectorAwareTrait.php | 2 +- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/Command/ExecCommand.php b/src/Command/ExecCommand.php index 4ee293d..f9ae697 100644 --- a/src/Command/ExecCommand.php +++ b/src/Command/ExecCommand.php @@ -12,6 +12,7 @@ use Drall\Model\EnvironmentId; use Drall\Model\Placeholder; use Drall\Model\RawCommand; +use Drall\Trait\SignalAwareTrait; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -24,6 +25,8 @@ */ class ExecCommand extends BaseCommand { + use SignalAwareTrait; + /** * Maximum number of Drall workers. * @@ -163,11 +166,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int ); $exitCode = 0; - Loop::run(function () use ($values, $command, $placeholder, $output, $progressBar, $workers, &$exitCode) { + // Handle interruption signals to stop Drall gracefully. + $isStopping = FALSE; + $this->registerInterruptionListener(function () use (&$isStopping, $output) { + $output->writeln(''); + + // If a previous SIGINT was received, then stop immediately. + if ($isStopping) { + $this->logger->error('Interrupted by user.'); + exit(1); + } + + // Prepare to stop after the current item is processed. + $this->logger->warning('Stopping after current item.'); + $isStopping = TRUE; + }); + + Loop::run(function () use ( + $values, + $command, + $placeholder, + $output, + $progressBar, + $workers, + &$exitCode, + &$isStopping + ) { yield ConcurrentIterator\each( Iterator\fromIterable($values), new LocalSemaphore($workers), - function ($value) use ($command, $placeholder, $output, $progressBar, &$exitCode) { + function ($value) use ($command, $placeholder, $output, $progressBar, &$exitCode, &$isStopping) { + if ($isStopping) { + return; + } + $sCommand = Placeholder::replace([$placeholder->value => $value], $command); $process = new Process("($sCommand) 2>&1", getcwd()); @@ -189,9 +221,17 @@ function ($value) use ($command, $placeholder, $output, $progressBar, &$exitCode ); }); - $progressBar->finish(); + if (!$isStopping) { + $progressBar->finish(); + } + $output->writeln(''); + if ($isStopping) { + $this->logger->error('Interrupted by user.'); + return 1; + } + return $exitCode; } diff --git a/src/Trait/SignalAwareTrait.php b/src/Trait/SignalAwareTrait.php index e72edb0..ad77679 100644 --- a/src/Trait/SignalAwareTrait.php +++ b/src/Trait/SignalAwareTrait.php @@ -26,4 +26,21 @@ protected function registerSignalListener(int $signo, callable $handler): bool { return TRUE; } + /** + * Register a listener for SIGINT. + * + * @param callbale $handler + * A callable event listener. + * + * @return bool + * True if the listener was registered. + */ + protected function registerInterruptionListener(callable $handler): bool { + if (!defined('SIGINT')) { + return FALSE; + } + + return self::registerSignalListener(SIGINT, $handler); + } + } diff --git a/src/Trait/SiteDetectorAwareTrait.php b/src/Trait/SiteDetectorAwareTrait.php index 6a516d7..73e72a7 100644 --- a/src/Trait/SiteDetectorAwareTrait.php +++ b/src/Trait/SiteDetectorAwareTrait.php @@ -30,7 +30,7 @@ public function setSiteDetector(SiteDetector $siteDetector) { public function siteDetector(): SiteDetector { if (!$this->hasSiteDetector()) { throw new \BadMethodCallException( - 'A site detecetor instance must first be assigned' + 'A site detector instance must first be assigned' ); }