* @readonly */ private $signals; /** * @var LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null * @readonly */ private $loggerOrCallback; /** @var array> */ private static $handlers = []; /** @var Closure|null */ private static $windowsHandler = null; /** * @param array $signals * @param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null $loggerOrCallback */ private function __construct(array $signals, $loggerOrCallback) { if (!is_callable($loggerOrCallback) && !$loggerOrCallback instanceof LoggerInterface && $loggerOrCallback !== null) { throw new \InvalidArgumentException('$loggerOrCallback must be a '.LoggerInterface::class.' instance, a callable, or null, '.(is_object($loggerOrCallback) ? get_class($loggerOrCallback) : gettype($loggerOrCallback)).' received.'); } $this->signals = $signals; $this->loggerOrCallback = $loggerOrCallback; } /** * @param self::SIG* $signalName */ private function trigger(string $signalName): void { $this->triggered = $signalName; if ($this->loggerOrCallback instanceof LoggerInterface) { $this->loggerOrCallback->info('Received '.$signalName); } elseif ($this->loggerOrCallback !== null) { ($this->loggerOrCallback)($signalName, $this); } } /** * Fetches the triggered state of the handler * * @phpstan-impure */ public function isTriggered(): bool { return $this->triggered !== null; } /** * Exits the process while communicating that the handled signal was what killed the process * * This is different from doing exit(SIGINT), and is also different to a successful exit(0). * * This should only be used when you received a signal and then handled it to gracefully shutdown and are now ready to shutdown. * * ``` * $signal = SignalHandler::create([SignalHandler::SIGINT], function (string $signal, SignalHandler $handler) { * // do cleanup here.. * * $handler->exitWithLastSignal(); * }); * * // or... * * $signal = SignalHandler::create([SignalHandler::SIGINT]); * * while ($doingThings) { * if ($signal->isTriggered()) { * $signal->exitWithLastSignal(); * } * * // do more things * } * ``` * * @see https://www.cons.org/cracauer/sigint.html * @return never */ public function exitWithLastSignal(): void { $signal = $this->triggered ?? 'SIGINT'; $signal = defined($signal) ? constant($signal) : 2; if (function_exists('posix_kill') && function_exists('posix_getpid')) { pcntl_signal($signal, SIG_DFL); posix_kill(posix_getpid(), $signal); } // just in case posix_kill above could not run // not strictly correct but it's the best we can do here exit(128 + $signal); } /** * Resets the state to let a handler accept a signal again */ public function reset(): void { $this->triggered = null; } public function __destruct() { $this->unregister(); } /** * @param (string|int)[] $signals array of signal names (more portable, see SignalHandler::SIG*) or constants - defaults to [SIGINT, SIGTERM] * @param LoggerInterface|callable $loggerOrCallback A PSR-3 Logger or a callback($signal, $signalName) * @return self A handler on which you can call isTriggered to know if the signal was received, and reset() to forget * * @phpstan-param list $signals * @phpstan-param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void) $loggerOrCallback */ public static function create(?array $signals = null, $loggerOrCallback = null): self { if ($signals === null) { $signals = [self::SIGINT, self::SIGTERM]; } $signals = array_map(function ($signal) { if (is_int($signal)) { return self::getSignalName($signal); } elseif (!in_array($signal, self::ALL_SIGNALS, true)) { throw new \InvalidArgumentException('$signals must be an array of SIG* constants or self::SIG* constants, got '.var_export($signal, true)); } return $signal; }, (array) $signals); $handler = new self($signals, $loggerOrCallback); if (PHP_VERSION_ID >= 80000) { array_unshift(self::$handlers, WeakReference::create($handler)); } else { array_unshift(self::$handlers, $handler); } if (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli' && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) { if (null === self::$windowsHandler) { self::$windowsHandler = Closure::fromCallable([self::class, 'handleWindowsSignal']); sapi_windows_set_ctrl_handler(self::$windowsHandler); } } if (function_exists('pcntl_signal') && function_exists('pcntl_async_signals')) { pcntl_async_signals(true); self::registerPcntlHandler($signals); } return $handler; } /** * Clears the signal handler * * On PHP 8+ this is not necessary and it will happen automatically on __destruct, but PHP 7 does not * support weak references and thus there you need to manually do this. * * If another handler was registered previously to this one, it becomes active again */ public function unregister(): void { $signals = $this->signals; $index = false; foreach (self::$handlers as $key => $handler) { if (($handler instanceof WeakReference && $handler->get() === $this) || $handler === $this) { $index = $key; break; } } if ($index === false) { // guard against double-unregistration when __destruct happens return; } unset(self::$handlers[$index]); if (self::$windowsHandler !== null && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) { if (self::getHandlerFor(self::SIGINT) === null && self::getHandlerFor(self::SIGBREAK) === null) { sapi_windows_set_ctrl_handler(self::$windowsHandler, false); self::$windowsHandler = null; } } if (function_exists('pcntl_signal')) { foreach ($signals as $signal) { // skip missing signals, for example OSX does not have all signals if (!defined($signal)) { continue; } // keep listening to signals where we have a handler registered if (self::getHandlerFor($signal) !== null) { continue; } pcntl_signal(constant($signal), SIG_DFL); } } } /** * Clears all signal handlers * * On PHP 8+ this should not be necessary as it will happen automatically on __destruct, but PHP 7 does not * support weak references and thus there you need to manually do this. * * This can be done to reset the global state, but ideally you should always call ->unregister() in a try/finally block to ensure it happens. */ public static function unregisterAll(): void { if (self::$windowsHandler !== null) { sapi_windows_set_ctrl_handler(self::$windowsHandler, false); self::$windowsHandler = null; } foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference) { $handler = $handler->get(); if ($handler === null) { unset(self::$handlers[$key]); continue; } } $handler->unregister(); } } /** * @param list $signals */ private static function registerPcntlHandler(array $signals): void { static $callable; if ($callable === null) { $callable = Closure::fromCallable([self::class, 'handlePcntlSignal']); } foreach ($signals as $signal) { // skip missing signals, for example OSX does not have all signals if (!defined($signal)) { continue; } pcntl_signal(constant($signal), $callable); } } private static function handleWindowsSignal(int $event): void { if (PHP_WINDOWS_EVENT_CTRL_C === $event) { self::callHandlerFor(self::SIGINT); } elseif (PHP_WINDOWS_EVENT_CTRL_BREAK === $event) { self::callHandlerFor(self::SIGBREAK); } } private static function handlePcntlSignal(int $signal): void { self::callHandlerFor(self::getSignalName($signal)); } /** * Calls the first handler from the top of the stack that can handle a given signal * * @param self::SIG* $signal */ private static function callHandlerFor(string $signal): void { $handler = self::getHandlerFor($signal); if ($handler !== null) { $handler->trigger($signal); } } /** * Returns the first handler from the top of the stack that can handle a given signal * * @param self::SIG* $signal * @return self|null */ private static function getHandlerFor(string $signal): ?self { foreach (self::$handlers as $key => $handler) { if ($handler instanceof WeakReference) { $handler = $handler->get(); if ($handler === null) { unset(self::$handlers[$key]); continue; } } if (in_array($signal, $handler->signals, true)) { return $handler; } } return null; } /** * @return self::SIG* */ private static function getSignalName(int $signo): string { static $signals = null; if ($signals === null) { $signals = []; foreach (self::ALL_SIGNALS as $value) { if (defined($value)) { $signals[constant($value)] = $value; } } } if (isset($signals[$signo])) { return $signals[$signo]; } throw new \InvalidArgumentException('Unknown signal #'.$signo); } } __halt_compiler();----SIGNATURE:----gMTxyl0CLu+WmRaPWLyGjBF1TKwkXAJF/X1eTeh9GzXKgdNKowlp1dBtOg1F1I+HehPU3F0W0fltmPgk5amLx1ZJe783gJntsVrwvl1MbC21qQQUcM7EHHseTsrlX9VtosNS4S4dRzHt5l+dLkmQF+EVaiHxtZ2I/GWbElRq8Uo9jmeeN3WFxCjayNO7BKz1DWUQ5TZxhHZSV2cBRY1y3pNyb910/xaj2F6z8jAYxiA41ghvRMuLxj0Q0RokxBkwH3AbTrzYOmKYWk30UeqkpVVicEd4mSL0JFkciCmwuLbNFsRuFKhI9G8ib+dzZhfXsj2uvFbndF75ISEC9RLj8+n8daBCfoglvE/yJ43R60EO2nUckkY+fgZavifOl658yciAeI6KvsItNHbm4xNsjEWhev9Tn7r9fP+yCZm0yTG0BO5C/GCN+4bQurzEFKTxR0C4pSWnnE4bqT4lNtRp1kebMmNIBZLSEsrstVCWMrHH9ZBd4BjkjlSTDU+wsdGAIOLk8OQS2eaEf7VXlgdSfUZk6/QpDXwvWciDPcGL++LryFVIG3PYzqMSDkKBzWSq7Ay24SWlsOMGMMTImwnOoKgsuhQYJyBmkOFlVxGwjdlqN27OyOE37O5pf5Lt4VAhVl+ZbeaHr9vbO271VLIJfO8xHLxFM05ll5b+2baMVoE=----ATTACHMENT:----MzQxMzQ2NjU2NzU1NTkxOSA3NDY0MzgxODA0MDAzODc4IDQ2MTkzODMwMDUwNDAyOTg=