onPcntlSignal(SIGINT, [$this, 'onSigint']); foreach ($signal as $sig => $value) { if (is_array($value) && is_string($value[0])) { $value[0] = \Kiri::getDi()->get($value[0]); } if (!is_callable($value, true)) { throw new Exception('Register signal callback must can exec.'); } $this->onPcntlSignal($sig, $value); } } /** * @param $no * @param array $signInfo * @return void */ public function onSigint($no, array $signInfo): void { static $handling = false; if ($handling) { return; } $handling = true; try { if (!$this->shouldHandleSigint()) { return; } Kiri::getLogger()->alert('Pid ' . getmypid() . ' get signo ' . $no); $this->shutdown(); } catch (\Throwable $exception) { \Kiri::getLogger()->json_log($exception); } } private function shouldHandleSigint(): bool { $currentPid = getmypid(); if ($this->isMasterProcess($currentPid)) { return true; } $masterPid = $this->getVerifiedPidFileMasterPid(); return $masterPid !== null && $masterPid === $currentPid; } private function getVerifiedPidFileMasterPid(): ?int { $pidFile = function_exists('storage') ? storage('.swoole.pid') : ''; if (!is_file($pidFile)) { return null; } $pid = (int)trim((string)file_get_contents($pidFile)); if ($pid <= 1 || !$this->isProcessAlive($pid) || !$this->isSameAppProcess($pid) || !$this->isMasterProcess($pid)) { return null; } return $pid; } private function isMasterProcess(int $pid): bool { $command = $this->getProcessCommand($pid); if ($command === '') { return false; } $prefix = '[' . config('site.id', 'system-service') . '].Master['; return str_contains($command, $prefix); } private function getProcessCommand(int $pid): string { $command = @file_get_contents('/proc/' . $pid . '/cmdline'); if (!is_string($command) || $command === '') { return ''; } return str_replace("\0", ' ', $command); } private function isSameAppProcess(int $pid): bool { $cwd = @readlink('/proc/' . $pid . '/cwd'); if ($cwd === false || $cwd === '') { return false; } return $this->normalizePath($cwd) === $this->normalizePath(APP_PATH); } private function normalizePath(string $path): string { $real = realpath($path) ?: $path; return rtrim(str_replace('\\', '/', $real), '/'); } private function isProcessAlive(int $pid): bool { return $pid > 1 && @Process::kill($pid, 0); } /** * @param $signal * @param $callback * @return void */ private function onPcntlSignal($signal, $callback): void { Process::signal((int)$signal, static function (int $signo) use ($callback): void { call_user_func($callback, $signo, []); }); } /** * @param array $ports * @return array */ public function sortService(array $ports): array { $array = []; foreach ($ports as $port) { $array = $this->sort($array, $port); } return $array; } /** * @param array $ports * @return array */ public function genConfigService(array $ports): array { $array = []; $ports = $ports['ports'] ?? []; foreach ($ports as $port) { $array = $this->sort($array, $port); } return $array; } /** * @param array $array * @param $port * @return array */ private function sort(array $array, $port): array { $config = instance(Config::class, [], $port); if ($port['type'] == Constant::SERVER_TYPE_WEBSOCKET) { array_unshift($array, $config); } else if ($port['type'] == Constant::SERVER_TYPE_HTTP) { if (!empty($array) && $array[0]->type == Constant::SERVER_TYPE_WEBSOCKET) { $array[] = $config; } else { array_unshift($array, $config); } } else { $array[] = $config; } return $array; } /** * @param $type * @return string|null */ public function getServerClass($type): ?string { return match ($type) { Constant::SERVER_TYPE_BASE, Constant::SERVER_TYPE_TCP, Constant::SERVER_TYPE_UDP => Server::class, Constant::SERVER_TYPE_HTTP => HServer::class, Constant::SERVER_TYPE_WEBSOCKET => WServer::class, default => null }; } }