Files
kiri-http-server/State.php
T
2026-07-08 10:45:21 +08:00

231 lines
5.6 KiB
PHP

<?php
namespace Kiri\Server;
use Kiri\Abstracts\Component;
use Kiri\Server\Abstracts\TraitServer;
use Swoole\Process;
use function config;
class State extends Component
{
use TraitServer;
public array $servers = [];
/**
* @return void
*/
public function init(): void
{
$this->servers = config('servers.server.ports');
}
/**
* @return bool
* @throws
*/
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)) {
return true;
}
}
return false;
}
/**
* @param $port
* @throws
*/
public function exit($port): void
{
$pids = $this->collectStopPids((int)$port);
if ($pids === []) {
return;
}
$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) || !$this->isSameAppProcess($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;
}
if (!$this->isSameAppProcess((int)$process['pid'])) {
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 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);
}
}