From 45297541f0629187f00e6668332bb91c1de4b44f Mon Sep 17 00:00:00 2001 From: MBorne Date: Thu, 16 Nov 2023 10:59:21 +0100 Subject: [PATCH] chore(sigterm): improve SIGTERM handling in loop-validate.sh to exit nicely and restart current validation (refs #59) --- loop-validate.sh | 37 +++++++++++++++++-- src/Command/Validations/ProcessOneCommand.php | 31 +++++++++++++++- src/Validation/ValidationManager.php | 27 ++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/loop-validate.sh b/loop-validate.sh index 639d169..af60bf7 100755 --- a/loop-validate.sh +++ b/loop-validate.sh @@ -1,11 +1,40 @@ #!/bin/bash -set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -while true +RUNNING=1 + +_term(){ + echo "$BASH_SOURCE - caught signal, terminating..." + RUNNING=0 + kill -s SIGTERM $child_pid + wait +} + + +# Note that trapping SIGWINCH is due to weird usage of this "window resized" by apache2 +# (see https://bz.apache.org/bugzilla/show_bug.cgi?id=50669) +# ...propagated to STOPSIGNAL=SIGWINCH in php:8.2-apache docker image +# ...which leads to an unexpected behavior of "docker stop" +# ...and probably problems with PHP applications including console commands (Symfony, Laravel,...) +trap _term SIGTERM SIGINT SIGWINCH + +echo "$BASH_SOURCE - started with PID=$$" + +while [ $RUNNING -eq 1 ] do - php "${SCRIPT_DIR}/bin/console" ign-validator:validations:process-one -vv - sleep 15 + php "${SCRIPT_DIR}/bin/console" ign-validator:validations:process-one -vvv & + child_pid=$! + wait $child_pid + + if [ $RUNNING -eq 0 ]; + then + break; + fi + + sleep 15 & + child_pid=$! + wait $child_pid done +echo "$BASH_SOURCE - stopped" diff --git a/src/Command/Validations/ProcessOneCommand.php b/src/Command/Validations/ProcessOneCommand.php index e72329f..bcbec39 100644 --- a/src/Command/Validations/ProcessOneCommand.php +++ b/src/Command/Validations/ProcessOneCommand.php @@ -3,14 +3,16 @@ namespace App\Command\Validations; use App\Validation\ValidationManager; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\SignalableCommandInterface; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Helper command to process pending validations. */ -class ProcessOneCommand extends Command +class ProcessOneCommand extends Command implements SignalableCommandInterface { protected static $defaultName = 'ign-validator:validations:process-one'; @@ -19,11 +21,18 @@ class ProcessOneCommand extends Command */ private $validationManager; + /** + * @var LoggerInterface + */ + private $logger; + public function __construct( - ValidationManager $validationManager + ValidationManager $validationManager, + LoggerInterface $logger ) { parent::__construct(); $this->validationManager = $validationManager; + $this->logger = $logger; } protected function configure() @@ -43,4 +52,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } + public function getSubscribedSignals(): array + { + return [\SIGINT,\SIGTERM]; + } + + public function handleSignal(int $signal): void + { + $this->logger->warning("[ProcessOneCommand] received stop signal while processing validation!",[ + 'signal' => $signal + ]); + $this->validationManager->cancelProcessing(); + $exitCode = 128 + $signal; + $this->logger->info("terminate process with exitCode={exitCode}",[ + 'exitCode' => $exitCode + ]); + exit($exitCode); + } } + diff --git a/src/Validation/ValidationManager.php b/src/Validation/ValidationManager.php index 189eee7..62bda16 100644 --- a/src/Validation/ValidationManager.php +++ b/src/Validation/ValidationManager.php @@ -40,6 +40,12 @@ class ValidationManager */ private $zipArchiveValidator; + /** + * Current validation (in order to handle SIGTERM) + * @var Validation + */ + private $currentValidation = null; + public function __construct( EntityManagerInterface $em, ValidationsStorage $storage, @@ -90,12 +96,33 @@ public function archive(Validation $validation) */ public function processOne() { + sleep(100); $validation = $this->getValidationRepository()->popNextPending(); if (is_null($validation)) { $this->logger->debug("processOne : no validation pending, quitting"); return; } + $this->currentValidation = $validation; $this->doProcess($validation); + $this->currentValidation = null; + } + + /** + * Stop currently running validation (invoked when SIGTERM is received) + * + * @return void + */ + public function cancelProcessing(){ + if ( is_null($this->currentValidation) ){ + $this->logger->debug("SIGTERM received, no validation in progress"); + return ; + } + $this->logger->warning("Validation[{uid}]: SIGTERM received, changing state to pending",[ + "uid" => $this->currentValidation->getUid() + ]); + $this->currentValidation->setStatus(Validation::STATUS_PENDING); + $this->em->persist($this->currentValidation); + $this->em->flush(); } /**