enhancement for process state

This commit is contained in:
crazywhalecc 2022-03-13 22:46:22 +08:00
parent e95925c129
commit 3c87abc6e8
8 changed files with 179 additions and 55 deletions

View File

@ -7,22 +7,17 @@ namespace ZM\Command\Daemon;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
use ZM\Framework;
abstract class DaemonCommand extends Command
{
protected $daemon_file = null;
protected function execute(InputInterface $input, OutputInterface $output): int {
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (!file_exists($pid_path)) {
$output->writeln("<comment>没有检测到正在运行的守护进程或框架进程!</comment>");
die();
}
$file = json_decode(file_get_contents($pid_path), true);
if ($file === null || posix_getsid(intval($file["pid"])) === false) {
$file = Framework::getProcessState(ZM_PROCESS_MASTER);
if ($file === false || posix_getsid(intval($file["pid"])) === false) {
$output->writeln("<comment>未检测到正在运行的守护进程或框架进程!</comment>");
unlink($pid_path);
Framework::removeProcessState(ZM_PROCESS_MASTER);
die();
}
$this->daemon_file = $file;

View File

@ -6,7 +6,7 @@ namespace ZM\Command\Daemon;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
use ZM\Framework;
class DaemonStopCommand extends DaemonCommand
{
@ -20,7 +20,7 @@ class DaemonStopCommand extends DaemonCommand
parent::execute($input, $output);
Process::kill(intval($this->daemon_file["pid"]), SIGTERM);
$i = 10;
while (file_exists(DataProvider::getWorkingDir() . "/.daemon_pid") && $i > 0) {
while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) {
sleep(1);
--$i;
}

View File

@ -8,7 +8,8 @@ use Swoole\Process;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Utils\DataProvider;
use ZM\Framework;
use ZM\Utils\Manager\ProcessManager;
/**
* Class OnManagerStop
@ -17,15 +18,12 @@ use ZM\Utils\DataProvider;
*/
class OnManagerStop implements SwooleEvent
{
public function onCall() {
if (OnManagerStart::$process !== null) {
if (Process::kill(OnManagerStart::$process->pid, 0)) {
Process::kill(OnManagerStart::$process->pid, SIGTERM);
}
public function onCall()
{
foreach (ProcessManager::$user_process as $v) {
if (posix_getsid($v->pid) !== false) Process::kill($v->pid, SIGTERM);
}
Console::verbose("进程 Manager 已停止!");
if (file_exists(DataProvider::getWorkingDir()."/.daemon_pid")) {
unlink(DataProvider::getWorkingDir()."/.daemon_pid");
}
Framework::removeProcessState(ZM_PROCESS_MANAGER);
}
}

View File

@ -8,6 +8,7 @@ use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Utils\DataProvider;
/**
@ -19,9 +20,9 @@ class OnShutdown implements SwooleEvent
{
public function onCall(Server $server) {
Console::verbose("正在关闭 Master 进程pid=" . posix_getpid());
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (file_exists($pid_path)) {
unlink($pid_path);
Framework::removeProcessState(ZM_PROCESS_MASTER);
if (DataProvider::scanDirFiles(_zm_pid_dir()) == []) {
rmdir(_zm_pid_dir());
}
}
}

View File

@ -25,13 +25,9 @@ class OnStart implements SwooleEvent
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalMaster($server);
}
$daemon_data = json_encode([
"pid" => $server->master_pid,
"stdout" => ZMConfig::get("global")["swoole"]["log_file"],
"daemon" => (bool)Framework::$argv["daemon"]
], 128 | 256);
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
Framework::saveProcessState(ZM_PROCESS_MASTER, $server->master_pid, [
'stdout' => ZMConfig::get("global")["swoole"]["log_file"],
'daemon' => (bool)Framework::$argv["daemon"]
]);
}
}

View File

@ -43,13 +43,15 @@ use ZM\Utils\SignalListener;
*/
class OnWorkerStart implements SwooleEvent
{
public function onCall(Server $server, $worker_id) {
public function onCall(Server $server, $worker_id)
{
Console::debug("Calling onWorkerStart event(1)");
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalWorker($server, $worker_id);
}
unset(Context::$context[Coroutine::getCid()]);
if ($server->taskworker === false) {
Framework::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $worker_id]);
zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid);
if (LightCacheInside::get("wait_api", "wait_api") !== null) {
LightCacheInside::unset("wait_api", "wait_api");
@ -105,10 +107,13 @@ class OnWorkerStart implements SwooleEvent
Console::error(zm_internal_errcode("E00030") . "PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM);
if (!Framework::$argv["watch"]) { // 在热更新模式下不能退出
Process::kill($server->master_pid, SIGTERM);
}
}
} else {
// 这里是TaskWorker初始化的内容部分
Framework::saveProcessState(ZM_PROCESS_TASKWORKER, $server->worker_pid, ['worker_id' => $worker_id]);
try {
Framework::$server = $server;
$this->loadAnnotations();
@ -131,13 +136,15 @@ class OnWorkerStart implements SwooleEvent
* @throws ReflectionException
* @throws Exception
*/
private function loadAnnotations() {
private function loadAnnotations()
{
if (Framework::$instant_mode) goto skip;
//加载各个模块的注解类,以及反射
Console::debug("Mapping annotations");
$parser = new AnnotationParser();
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true);
foreach ($composer["autoload"]["psr-4"] as $k => $v) {
$merge = array_merge($composer["autoload"]["psr-4"] ?? [], $composer["autoload-dev"]["psr-4"] ?? []);
foreach ($merge as $k => $v) {
if (is_dir(DataProvider::getSourceRootDir() . "/" . $v)) {
if (in_array(trim($k, "\\") . "\\", $composer["extra"]["exclude_annotate"] ?? [])) continue;
if (trim($k, "\\") == "ZM") continue;
@ -150,12 +157,12 @@ class OnWorkerStart implements SwooleEvent
$plugin_enable_hotload = ZMConfig::get("global", "module_loader")["enable_hotload"] ?? false;
if ($plugin_enable_hotload) {
$list = ModuleManager::getPackedModules();
foreach($list as $k => $v) {
if (\server()->worker_id === 0) Console::info("Loading packed module: ".$k);
foreach ($list as $k => $v) {
if (\server()->worker_id === 0) Console::info("Loading packed module: " . $k);
require_once $v["phar-path"];
$func = "loader".$v["generated-id"];
$func = "loader" . $v["generated-id"];
$func();
$parser->addRegisterPath("phar://".$v["phar-path"]."/".$v["module-root-path"], $v["namespace"]);
$parser->addRegisterPath("phar://" . $v["phar-path"] . "/" . $v["module-root-path"], $v["namespace"]);
}
}
@ -189,7 +196,8 @@ class OnWorkerStart implements SwooleEvent
}
}
private function initMySQLPool() {
private function initMySQLPool()
{
if (SqlPoolStorage::$sql_pool !== null) {
SqlPoolStorage::$sql_pool->close();
SqlPoolStorage::$sql_pool = null;

View File

@ -9,7 +9,9 @@ use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Store\LightCache;
use ZM\Utils\DataProvider;
/**
* Class OnWorkerStop
@ -22,6 +24,7 @@ class OnWorkerStop implements SwooleEvent
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
LightCache::savePersistence();
}
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止: ".$server->getWorkerStatus($worker_id));
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止 (Worker 状态码: ".$server->getWorkerStatus($worker_id).")");
Framework::removeProcessState($server->taskworker ? ZM_PROCESS_TASKWORKER : ZM_PROCESS_WORKER, $worker_id);
}
}

View File

@ -12,6 +12,7 @@ use Throwable;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor;
use ZM\Exception\ZMKnownException;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
@ -78,7 +79,8 @@ class Framework
public static $instant_mode = false;
public function __construct($args = [], $instant_mode = false) {
public function __construct($args = [], $instant_mode = false)
{
$tty_width = $this->getTtyWidth();
self::$instant_mode = $instant_mode;
self::$argv = $args;
@ -129,9 +131,9 @@ class Framework
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
if ($instant_mode) {
$this->loadServerEvents();
}
//if ($instant_mode) {
$this->loadServerEvents();
//}
$this->parseCliArgs(self::$argv, $add_port);
@ -332,7 +334,8 @@ class Framework
}
}
private static function printMotd($tty_width) {
private static function printMotd($tty_width)
{
if (file_exists(DataProvider::getSourceRootDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getSourceRootDir() . "/config/motd.txt");
} else {
@ -344,7 +347,118 @@ class Framework
echo $motd;
}
public function start() {
/**
* 将各进程的pid写入文件以备后续崩溃及僵尸进程处理使用
* @param int $type
* @param int|string $pid
* @param array $data
* @internal
*/
public static function saveProcessState(int $type, $pid, array $data = [])
{
switch ($type) {
case ZM_PROCESS_MASTER:
$file = _zm_pid_dir() . '/master.json';
$json = [
'pid' => intval($pid),
'stdout' => $data['stdout'],
'daemon' => $data['daemon']
];
file_put_contents($file, json_encode($json, JSON_UNESCAPED_UNICODE));
return;
case ZM_PROCESS_MANAGER:
$file = _zm_pid_dir() . '/manager.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_WORKER:
$file = _zm_pid_dir() . '/worker.' . $data['worker_id'] . '.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_USER:
$file = _zm_pid_dir() . '/user.' . $data['process_name'] . '.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_TASKWORKER:
$file = _zm_pid_dir() . '/taskworker.' . $data['worker_id'] . '.pid';
file_put_contents($file, strval($pid));
return;
}
}
/**
* 用于框架内部获取多进程运行状态的函数
* @param int $type
* @param null $id_or_name
* @return false|int|mixed
* @throws ZMKnownException
* @internal
*/
public static function getProcessState(int $type, $id_or_name = null)
{
$file = _zm_pid_dir();
switch ($type) {
case ZM_PROCESS_MASTER:
if (!file_exists($file . '/master.json')) return false;
$json = json_decode(file_get_contents($file . '/master.json'), true);
if ($json !== null) return $json;
else return false;
case ZM_PROCESS_MANAGER:
if (!file_exists($file . '/manager.pid')) return false;
return intval(file_get_contents($file . '/manager.pid'));
case ZM_PROCESS_WORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
if (!file_exists($file . '/worker.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/worker.' . $id_or_name . '.pid'));
case ZM_PROCESS_USER:
if (!is_string($id_or_name)) throw new ZMKnownException('E99999', 'process_name必须为字符串');
if (!file_exists($file . '/user.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/user.' . $id_or_name . '.pid'));
case ZM_PROCESS_TASKWORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
if (!file_exists($file . '/taskworker.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/taskworker.' . $id_or_name . '.pid'));
default:
return false;
}
}
/**
* @param int $type
* @param null $id_or_name
* @throws ZMKnownException
* @internal
*/
public static function removeProcessState(int $type, $id_or_name = null)
{
switch ($type) {
case ZM_PROCESS_MASTER:
$file = _zm_pid_dir() . '/master.json';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_MANAGER:
$file = _zm_pid_dir() . '/manager.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_WORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
$file = _zm_pid_dir() . '/worker.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_USER:
if (!is_string($id_or_name)) throw new ZMKnownException('E99999', 'process_name必须为字符串');
$file = _zm_pid_dir() . '/user.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_TASKWORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
$file = _zm_pid_dir() . '/taskworker.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
}
}
public function start()
{
try {
self::$loaded_files = get_included_files();
self::$server->start();
@ -358,7 +472,8 @@ class Framework
/**
* @noinspection PhpIncludeInspection
*/
private function loadServerEvents() {
private function loadServerEvents()
{
if (Phar::running() !== "") {
ob_start();
include_once DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php";
@ -383,7 +498,8 @@ class Framework
* @throws ReflectionException
* @throws ReflectionException
*/
private function registerServerEvents() {
private function registerServerEvents()
{
$reader = new AnnotationReader();
$all = ZMUtil::getClassesPsr4(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
foreach ($all as $v) {
@ -400,6 +516,7 @@ class Framework
}
foreach (($this->setup_events["setup"] ?? []) as $v) {
Console::debug('Calling @OnSetup: ' . $v["class"]);
$c = ZMUtil::getModInstance($v["class"]);
$method = $v["method"];
$c->$method();
@ -417,7 +534,8 @@ class Framework
* @param $args
* @param $add_port
*/
private function parseCliArgs($args, &$add_port) {
private function parseCliArgs($args, &$add_port)
{
$coroutine_mode = true;
global $terminal_id;
$terminal_id = uuidgen();
@ -494,7 +612,8 @@ class Framework
else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
}
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border)
{
$tmp_line = $k . ": " . $v;
//Console::info("写入[".$tmp_line."]");
if (strlen($tmp_line) > $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
@ -538,7 +657,8 @@ class Framework
}
}
public static function printProps($out, $tty_width, $colorful = true) {
public static function printProps($out, $tty_width, $colorful = true)
{
$max_border = $tty_width < 65 ? $tty_width : 65;
if (LOAD_MODE == 0) echo Console::setColor("* Framework started with source mode.\n", $colorful ? "yellow" : "");
echo str_pad("", $max_border, "=") . PHP_EOL;
@ -580,20 +700,23 @@ class Framework
echo str_pad("", $max_border, "=") . PHP_EOL;
}
public static function getTtyWidth(): string {
public static function getTtyWidth(): string
{
$size = exec("stty size");
if (empty($size)) return 65;
return explode(" ", trim($size))[1];
}
public static function loadFrameworkState() {
public static function loadFrameworkState()
{
if (!file_exists(DataProvider::getDataFolder() . ".state.json")) return [];
$r = json_decode(file_get_contents(DataProvider::getDataFolder() . ".state.json"), true);
if ($r === null) $r = [];
return $r;
}
public static function saveFrameworkState($data) {
public static function saveFrameworkState($data)
{
return file_put_contents(DataProvider::getDataFolder() . ".state.json", json_encode($data, 64 | 128 | 256));
}
}