From bacf067f87d4f250a2ef649123d482913fd1b511 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 21 May 2025 19:04:45 +0900 Subject: [PATCH] Implement graceful shutdown for background task runner #2451 --- common/framework/Queue.php | 44 ++++++++++++++++++++++++++++++++++++++ common/scripts/cron.php | 15 ++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/common/framework/Queue.php b/common/framework/Queue.php index 361e4bfeb..1e33c1adb 100644 --- a/common/framework/Queue.php +++ b/common/framework/Queue.php @@ -11,6 +11,7 @@ class Queue * Static properties. */ protected static $_drivers = []; + protected static $_signal = 0; /** * Priority constants. @@ -346,6 +347,11 @@ class Queue foreach ($tasks as $task) { self::_executeTask($task); + + if (self::signalReceived()) + { + return; + } } } if ($index === 1 || $count < 2) @@ -356,6 +362,11 @@ class Queue { $db_driver->updateLastRunTimestamp($task); self::_executeTask($task); + + if (self::signalReceived()) + { + return; + } } } @@ -368,6 +379,11 @@ class Queue if ($task) { self::_executeTask($task); + + if (self::signalReceived()) + { + return; + } } // If the timeout is imminent, break the loop. @@ -383,6 +399,12 @@ class Queue { usleep(intval((1 - $loop_elapsed_time) * 1000000)); } + + // Check for a signal again after the sleep. + if (self::signalReceived()) + { + break; + } } } @@ -488,4 +510,26 @@ class Queue ])); } } + + /** + * Signal handler. + * + * @param int $signal + * @param mixed $siginfo + * @return void + */ + public static function signalHandler(int $signal, $siginfo): void + { + self::$_signal = $signal; + } + + /** + * Has a signal been received? + * + * @return int + */ + public static function signalReceived(): int + { + return self::$_signal; + } } diff --git a/common/scripts/cron.php b/common/scripts/cron.php index 971f889a4..5d3ff146d 100644 --- a/common/scripts/cron.php +++ b/common/scripts/cron.php @@ -52,6 +52,19 @@ if (PHP_SAPI !== 'cli') } } +// Set up a signal handler. +if (PHP_SAPI === 'cli' && function_exists('pcntl_signal')) +{ + if (function_exists('pcntl_async_signals')) + { + pcntl_async_signals(true); + } + foreach ([SIGINT, SIGHUP, SIGTERM, SIGQUIT, SIGUSR1, SIGUSR2] as $signal) + { + pcntl_signal($signal, [Rhymix\Framework\Queue::class, 'signalHandler']); + } +} + // Create multiple processes if configured. if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') && function_exists('pcntl_waitpid')) { @@ -84,7 +97,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') && } // The parent process waits for its children to finish. - while (count($pids)) + while (count($pids) && !Rhymix\Framework\Queue::signalReceived()) { $pid = pcntl_waitpid(-1, $status, \WNOHANG); if ($pid)