diff --git a/src/ZM/Command/Daemon/DaemonCommand.php b/src/ZM/Command/Daemon/DaemonCommand.php index 4b18750e..8bf9ec8e 100644 --- a/src/ZM/Command/Daemon/DaemonCommand.php +++ b/src/ZM/Command/Daemon/DaemonCommand.php @@ -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("没有检测到正在运行的守护进程或框架进程!"); - 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("未检测到正在运行的守护进程或框架进程!"); - unlink($pid_path); + Framework::removeProcessState(ZM_PROCESS_MASTER); die(); } $this->daemon_file = $file; diff --git a/src/ZM/Command/Daemon/DaemonStopCommand.php b/src/ZM/Command/Daemon/DaemonStopCommand.php index 959def97..8e95aa56 100644 --- a/src/ZM/Command/Daemon/DaemonStopCommand.php +++ b/src/ZM/Command/Daemon/DaemonStopCommand.php @@ -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; } diff --git a/src/ZM/Event/SwooleEvent/OnManagerStop.php b/src/ZM/Event/SwooleEvent/OnManagerStop.php index e79b3bdc..e6f7926e 100644 --- a/src/ZM/Event/SwooleEvent/OnManagerStop.php +++ b/src/ZM/Event/SwooleEvent/OnManagerStop.php @@ -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); } } \ No newline at end of file diff --git a/src/ZM/Event/SwooleEvent/OnShutdown.php b/src/ZM/Event/SwooleEvent/OnShutdown.php index b997618b..367390a2 100644 --- a/src/ZM/Event/SwooleEvent/OnShutdown.php +++ b/src/ZM/Event/SwooleEvent/OnShutdown.php @@ -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()); } } } \ No newline at end of file diff --git a/src/ZM/Event/SwooleEvent/OnStart.php b/src/ZM/Event/SwooleEvent/OnStart.php index 400a8383..b4f8c881 100644 --- a/src/ZM/Event/SwooleEvent/OnStart.php +++ b/src/ZM/Event/SwooleEvent/OnStart.php @@ -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"] + ]); } - - } \ No newline at end of file diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStart.php b/src/ZM/Event/SwooleEvent/OnWorkerStart.php index 9cbd57bb..7fe16caf 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStart.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStart.php @@ -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; diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStop.php b/src/ZM/Event/SwooleEvent/OnWorkerStop.php index 0cb0f179..dba5c1bf 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStop.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStop.php @@ -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); } } \ No newline at end of file diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 2b05ec70..1e67a644 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -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)); } }