From d754c15c5de5a2f74fafe6b9516d433112a649c4 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 7 Oct 2022 23:41:37 +0100 Subject: [PATCH 1/3] Extract util `Multprocess` --- src/Command/RunCommand.php | 14 ++++---------- src/Utils/Multiprocess.php | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 src/Utils/Multiprocess.php diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index 80b5924..f611463 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -1,6 +1,7 @@ procs[$name]['pid'] = $pid; - } - else { + $this->procs[$name]['pid'] = Multiprocess::fork($name, function() use ($name, $env, $svc) { Shell::applyEnv($env); $cmd = $env->evaluate($svc->run); $this->output->writeln("[$name] Start service: $cmd"); passthru($svc->run, $ret); $this->output->writeln("[$name] Exited ($ret)"); - exit($ret); - } + return $ret; + }); } if ($svc->message) { diff --git a/src/Utils/Multiprocess.php b/src/Utils/Multiprocess.php new file mode 100644 index 0000000..6dddfda --- /dev/null +++ b/src/Utils/Multiprocess.php @@ -0,0 +1,26 @@ + Date: Fri, 7 Oct 2022 23:49:50 +0100 Subject: [PATCH 2/3] RunCommand - Propagate more shutdown styles (SIGTERM, SIGQUIT, SIGABRT) --- src/Command/RunCommand.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index f611463..f786c0a 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -122,6 +122,9 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ } pcntl_signal(SIGINT, [$this, 'onshutdown']); + pcntl_signal(SIGTERM, [$this, 'onshutdown']); + pcntl_signal(SIGQUIT, [$this, 'onshutdown']); + pcntl_signal(SIGABRT, [$this, 'onshutdown']); register_shutdown_function([$this, 'onshutdown']); // Track which thread is responsible for shutdown. @@ -198,11 +201,11 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ public function onshutdown() { global $shutdownPid; - static $started = FALSE; - if ($started || $shutdownPid !== posix_getpid()) { + global $shutdownStarted; + if ($shutdownStarted || $shutdownPid !== posix_getpid()) { return; } - $started = 1; + $shutdownStarted = 1; $this->output->writeln("[loco] Shutdown started"); From 52c3cb782893ae2338aa3589b783168c973b82e7 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Sat, 8 Oct 2022 00:17:23 +0100 Subject: [PATCH 3/3] RunCommand - Even if the command is killed very rudely (-9), try to politely stop subprocesses. --- src/Command/RunCommand.php | 43 +++++++++++++++++++++++++++++--------- src/Utils/Multiprocess.php | 4 ++++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index f786c0a..530ee8f 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -10,6 +10,8 @@ class RunCommand extends \Symfony\Component\Console\Command\Command { + const SHUTDOWN_GRACE = 2; + use LocoCommandTrait; /** @@ -40,7 +42,18 @@ protected function execute(InputInterface $input, OutputInterface $output) { $this->executeInExecMode($input, $output); } else { - $this->executeInManagedMode($input, $output); + // Run in managed console mode. + // + // Note: Consider a case where one uses xfce-terminal to start 'loco run' which spawns + // 5 more children. They then close xfce-terminal. The console process may receive SIGKILL + // but the children left running. To resolve this, we don't do any real work in $consolePid. + $consolePid = posix_getpid(); + $watcherPid = Multiprocess::fork('loco-watcher', function() use ($input, $output, $consolePid) { + $this->executeInManagedMode($input, $output, $consolePid); + }); + while (TRUE) { + sleep(60 * 10); + } } } @@ -85,9 +98,12 @@ public function executeInExecMode(InputInterface $input, OutputInterface $output * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param int|null $consolePid + * The PID of the console (wherein the admin monitors services). + * If the expected console gets killed, then we will shutdown services. * @return int */ - protected function executeInManagedMode(InputInterface $input, OutputInterface $output) { + protected function executeInManagedMode(InputInterface $input, OutputInterface $output, ?int $consolePid) { declare(ticks = 1); $POLL_INTERVAL = 3; @@ -121,11 +137,11 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ return 1; } - pcntl_signal(SIGINT, [$this, 'onshutdown']); - pcntl_signal(SIGTERM, [$this, 'onshutdown']); - pcntl_signal(SIGQUIT, [$this, 'onshutdown']); - pcntl_signal(SIGABRT, [$this, 'onshutdown']); - register_shutdown_function([$this, 'onshutdown']); + pcntl_signal(SIGINT, [$this, 'onShutdownWatcher']); + pcntl_signal(SIGTERM, [$this, 'onShutdownWatcher']); + pcntl_signal(SIGQUIT, [$this, 'onShutdownWatcher']); + pcntl_signal(SIGABRT, [$this, 'onShutdownWatcher']); + register_shutdown_function([$this, 'onShutdownWatcher']); // Track which thread is responsible for shutdown. global $shutdownPid; @@ -136,6 +152,12 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ $postStartupMessages = []; while (TRUE) { + if ($consolePid !== NULL && !Multiprocess::isAlive($consolePid)) { + $this->output->writeln("[watcher] Console process disappeared. Shutting down."); + exit(0); + // It's up to "onShutdownWatcher" to actually shutdown child processes. + } + foreach ($services as $name => $svc) { /** @var \Loco\LocoService $svc */ if (isset($blacklist[$name])) { @@ -199,7 +221,7 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ return 0; } - public function onshutdown() { + public function onShutdownWatcher() { global $shutdownPid; global $shutdownStarted; if ($shutdownStarted || $shutdownPid !== posix_getpid()) { @@ -220,17 +242,18 @@ public function onshutdown() { } } - // print_r(['onshutdown', 'pid' => posix_getpid(), '$shutdownPid' => $shutdownPid, 'allPids' => $allPids, 'procs' => $this->procs]); + // print_r(['onShutdownWatcher', 'pid' => posix_getpid(), '$shutdownPid' => $shutdownPid, 'allPids' => $allPids, 'procs' => $this->procs]); foreach ($allPids as $pid) { posix_kill($pid, SIGTERM); } - sleep(2); + sleep(static::SHUTDOWN_GRACE); foreach ($allPids as $pid) { posix_kill($pid, SIGKILL); } $this->output->writeln("[loco] Shutdown finished"); + $this->output->writeln(""); exit(1); } diff --git a/src/Utils/Multiprocess.php b/src/Utils/Multiprocess.php index 6dddfda..1feac3b 100644 --- a/src/Utils/Multiprocess.php +++ b/src/Utils/Multiprocess.php @@ -23,4 +23,8 @@ public static function fork($name, $callback) { } } + public static function isAlive(int $pid): bool { + return (bool) posix_getpgid($pid); + } + }