diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index 80b5924..530ee8f 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -1,6 +1,7 @@ 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); + } } } @@ -84,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; @@ -120,8 +137,11 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ return 1; } - pcntl_signal(SIGINT, [$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; @@ -132,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])) { @@ -153,21 +179,14 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ $blacklist[$name] = $name; } else { - $pid = pcntl_fork(); - if ($pid == -1) { - die("($name) Failed to fork"); - } - elseif ($pid) { - $this->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) { @@ -202,13 +221,13 @@ protected function executeInManagedMode(InputInterface $input, OutputInterface $ return 0; } - public function onshutdown() { + public function onShutdownWatcher() { 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"); @@ -223,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 new file mode 100644 index 0000000..1feac3b --- /dev/null +++ b/src/Utils/Multiprocess.php @@ -0,0 +1,30 @@ +