基于swoole协程的守护脚本
原版:github.com/swow/dontdie
仿写的swoole版本:
#!/usr/bin/env swoole-cli
<?php
declare(strict_types=1);
namespace DontDie;
use InvalidArgumentException;
use RuntimeException;
use Swoole\Process;
use Swoole\Coroutine;
use Swoole\Coroutine\System;
use Swoole\ExitException;
use Throwable;
use function Swoole\Coroutine\run;
use function Swoole\Coroutine\go;
use function array_slice;
use function date;
use function extension_loaded;
use function feof;
use function file_put_contents;
use function fread;
use function fwrite;
use function getcwd;
use function getenv;
use function getopt;
use function json_encode;
use function proc_close;
use function proc_get_status;
use function proc_open;
use function sleep;
use function sprintf;
use function usleep;
use const FILE_APPEND;
use const JSON_THROW_ON_ERROR;
use const JSON_UNESCAPED_SLASHES;
use const STDERR;
/** @param string[] $command */
function dontDie(array $command, string $nickname = ''): void
{
$running = true;
while (true) {
$descriptorType = 'pipe'; /* PHP_OS_FAMILY === 'Windows' ? 'pipe' : 'pty'; */
$proc = @proc_open(
$command,
[0 => ['redirect', 0], 1 => ['redirect', 1], 2 => [$descriptorType, 'w']],
$pipes, null, null
);
if ($proc === false) {
sleep(1);
continue;
}
$status = proc_get_status($proc);
$pid = $status['pid'];
$cwd = getcwd();
$enableTrace = getenv('DONTDIE_TRACE') === '1';
if ($nickname === '') {
$logFilename = $cwd . '/.dontdie.log';
} else {
$logFilename = $cwd . '/.dontdie.' . $nickname . '.log';
}
$log = static function (string|array $contents) use ($pid, $logFilename): void {
$line =
json_encode([
'pid' => $pid,
'time' => date('Y-m-d H:i:s'),
'contents' => $contents,
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES) . "\n";
file_put_contents($logFilename, $line, FILE_APPEND);
};
$trace = static function (string|array $contents) use ($log, $enableTrace): void {
if ($enableTrace) {
$log($contents);
}
};
$log('process started');
$processInfo = ['command' => $command];
if ($nickname !== '') {
$processInfo['nickname'] = $nickname;
}
$log($processInfo);
// stderr 读取协程,防止管道阻塞导致子进程死锁
$stderr = $pipes[2];
$stderrWorker = go(static function () use ($stderr, $trace): void {
$trace('redirect stderr start');
try {
while (!feof($stderr)) {
$data = @fread($stderr, 8192);
if ($data) {
@fwrite(STDERR, $data);
}
}
} catch (Throwable) {}
$trace('redirect stderr done');
});
// 信号监听协程
$signalProxy = static function (int $signal) use ($pid, &$running, $trace): void {
$trace(sprintf('wait for signal %d start', $signal));
System::waitSignal($signal);
if (Coroutine::isCanceled()) {
return;
}
$trace(sprintf('wait for signal %d done', $signal));
@Process::kill($pid, $signal);
$running = false;
};
$sigintWorker = go($signalProxy, SIGINT);
$sigtermWorker = go($signalProxy, SIGTERM);
// 不监听 SIGHUP:dontdie 总是后台运行,SIGHUP 来自终端断连
// Swoole 的 waitSignal 会覆盖 nohup 的 SIG_IGN 导致误退出
$sighupWorker = false;
$trace('wait for process');
try {
$wait = System::wait();
assert($wait['pid'] === $pid);
$trace('wait status: ' . json_encode($wait));
$trace('wait for process done');
} catch (Throwable) {
$trace('wait for process canceled');
}
// 清理工作协程
foreach ([$stderrWorker, $sigintWorker, $sigtermWorker, $sighupWorker] as $workerId) {
if ($workerId !== false && Coroutine::exists($workerId)) {
Coroutine::cancel($workerId);
$trace("canceled worker: {$workerId}");
}
}
$trace('wait for process exit');
for ($i = 0; $i < 110; $i++) {
if (!$status['running']) {
$exitCode = $status['exitcode'];
break;
}
$status = proc_get_status($proc);
usleep(($i < 100 ? 1 : 10) * 1000);
}
if (!isset($exitCode)) {
Process::kill($status['pid'], SIGKILL);
$exitCode = -1;
$log('process killed');
} else {
$log('process exited');
$log($status);
}
proc_close($proc);
if (!$running) {
$log('process totally exited');
exit($exitCode);
}
$log('process restart...');
}
}
if (!extension_loaded('swoole')) {
throw new RuntimeException('Swoole extension is required');
}
if ($argc <= 1) {
throw new InvalidArgumentException('No command specified');
}
$options = getopt('', ['nickname:'], $restIndex);
$command = array_slice($argv, $restIndex);
run(function () use ($command, $options) {
try {
dontDie(
command: $command,
nickname: $options['nickname'] ?? '',
);
} catch (ExitException $e) {
echo 'exit with ' . $e->getStatus(), PHP_EOL;
}
});
做这个是为了熟悉swoole的进程管理,swow用fread(STDERR)代替wait,swoole有这块的api就直接拿来用了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
这个是swow开发的吧,还没研究过怎么样