diff --git a/State.php b/State.php index 884a751..93ce976 100644 --- a/State.php +++ b/State.php @@ -31,9 +31,13 @@ class State extends Component */ public function isRunner(): bool { + if ($this->getPidFilePid() !== null || $this->getNamedServerPids() !== []) { + return true; + } + $ports = $this->sortService($this->servers); foreach ($ports as $config) { - if (checkPortIsAlready($config['port'])) { + if (checkPortIsAlready($config->port)) { return true; } } @@ -47,13 +51,161 @@ class State extends Component */ public function exit($port): void { - if (!($pid = checkPortIsAlready($port))) { + $pids = $this->collectStopPids((int)$port); + if ($pids === []) { return; } - while (checkPortIsAlready($port)) { - Process::kill($pid, 0) && Process::kill($pid, SIGTERM); - usleep(300); + + $this->terminatePids($pids, SIGTERM, 30); + + $remaining = array_values(array_filter($pids, [$this, 'isProcessAlive'])); + if ($remaining !== []) { + $this->terminatePids($remaining, SIGKILL, 10); + } + + if (!checkPortIsAlready((int)$port)) { + @unlink(storage('.swoole.pid')); } } + + private function collectStopPids(int $port): array + { + $pids = []; + + $pidFilePid = $this->getPidFilePid(); + if ($pidFilePid !== null) { + $pids[] = $pidFilePid; + $pids = array_merge($pids, $this->getDescendantPids($pidFilePid)); + } + + $pids = array_merge($pids, $this->getNamedServerPids()); + + $portPid = checkPortIsAlready($port); + if ($portPid !== false) { + $pids[] = (int)$portPid; + $pids = array_merge($pids, $this->getDescendantPids((int)$portPid)); + } + + $currentPid = getmypid(); + $pids = array_values(array_unique(array_filter(array_map('intval', $pids), function (int $pid) use ($currentPid) { + return $pid > 1 && $pid !== $currentPid; + }))); + rsort($pids); + + return $pids; + } + + + private function terminatePids(array $pids, int $signal, int $rounds): void + { + for ($i = 0; $i < $rounds; $i++) { + $alive = false; + foreach ($pids as $pid) { + if (!$this->isProcessAlive($pid)) { + continue; + } + $alive = true; + @Process::kill($pid, $signal); + } + + if (!$alive) { + return; + } + + usleep(100000); + } + } + + + private function getPidFilePid(): ?int + { + $pidFile = storage('.swoole.pid'); + if (!is_file($pidFile)) { + return null; + } + + $pid = (int)trim((string)file_get_contents($pidFile)); + if ($pid <= 1 || !$this->isProcessAlive($pid)) { + return null; + } + + return $pid; + } + + + private function getDescendantPids(int $parentPid): array + { + $processes = $this->listProcesses(); + $children = []; + foreach ($processes as $process) { + $children[(int)$process['ppid']][] = (int)$process['pid']; + } + + $result = []; + $stack = [$parentPid]; + while ($stack !== []) { + $pid = array_pop($stack); + foreach ($children[$pid] ?? [] as $childPid) { + $result[] = $childPid; + $stack[] = $childPid; + } + } + + return $result; + } + + + private function getNamedServerPids(): array + { + $siteId = '[' . config('site.id', 'system-service') . ']'; + $pids = []; + foreach ($this->listProcesses() as $process) { + $command = $process['command']; + if (!str_contains($command, $siteId)) { + continue; + } + if (!preg_match('/\\]\\..+\\[[0-9]+\\]/', $command)) { + continue; + } + $pids[] = (int)$process['pid']; + } + + return $pids; + } + + + private function listProcesses(): array + { + $lines = []; + exec('ps -eo pid=,ppid=,args=', $lines); + + $processes = []; + foreach ($lines as $line) { + $line = trim($line); + if ($line === '') { + continue; + } + + $parts = preg_split('/\s+/', $line, 3); + if (count($parts) < 3) { + continue; + } + + $processes[] = [ + 'pid' => (int)$parts[0], + 'ppid' => (int)$parts[1], + 'command' => $parts[2], + ]; + } + + return $processes; + } + + + private function isProcessAlive(int $pid): bool + { + return $pid > 1 && @Process::kill($pid, 0); + } + }