diff --git a/bin/phpunit-swoole b/bin/phpunit-swoole index 1bc0ee32..9a2898b1 100755 --- a/bin/phpunit-swoole +++ b/bin/phpunit-swoole @@ -9,10 +9,11 @@ use PHPUnit\Runner\Version; use PHPUnit\TextUI\TestRunner; use Swoole\Coroutine; use ZM\Annotation\Swoole\OnStart; -use ZM\Command\RunServerCommand; +use ZM\Command\Server\ServerStartCommand; use ZM\Console\Console; use ZM\ConsoleApplication; use ZM\Event\EventManager; +use ZM\Exception\ConfigException; use ZM\Framework; use ZM\Store\ZMAtomic; use ZM\Utils\ZMUtil; @@ -71,7 +72,7 @@ chdir(__DIR__ . '/../'); $options = array_map(function ($x) { return $x->getDefault(); -}, RunServerCommand::exportDefinition()->getOptions()); +}, ServerStartCommand::exportDefinition()->getOptions()); $options['disable-safe-exit'] = true; $options['worker-num'] = 1; $options['private-mode'] = true; @@ -90,7 +91,12 @@ spl_autoload_register(static function ($class) { }, true, true); Console::setLevel(0); -$framework = new Framework($options); +try { + $framework = new Framework($options); +} catch (ConfigException $e) { + echo $e->getMessage(); + exit(1); +} $start = new OnStart(); $start->method = function () { try { diff --git a/bin/start b/bin/start index d2f111be..c3eb7aaf 100755 --- a/bin/start +++ b/bin/start @@ -5,7 +5,10 @@ # author: crazywhalecc # since: 2.5.0 -if [ -f "$(pwd)/runtime/php" ]; then +if [ "$ZM_CUSTOM_PHP_PATH" != "" ]; then + echo "* Using PHP executable: ""$ZM_CUSTOM_PHP_PATH" + executable="$ZM_CUSTOM_PHP_PATH" +elif [ -f "$(pwd)/runtime/php" ]; then executable="$(pwd)/runtime/php" echo "* Framework started with built-in php." else diff --git a/composer.json b/composer.json index 85c330d4..90654437 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "symfony/routing": "~6.0 || ~5.0 || ~4.0 || ~3.0", "zhamao/connection-manager": "^1.0", "zhamao/console": "^1.0", + "zhamao/logger": "dev-master", "zhamao/request": "^1.1" }, "require-dev": { diff --git a/src/ZM/Command/Daemon/DaemonReloadCommand.php b/src/ZM/Command/Daemon/DaemonReloadCommand.php deleted file mode 100644 index 0b0fe80f..00000000 --- a/src/ZM/Command/Daemon/DaemonReloadCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -setDescription('重载框架'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - parent::execute($input, $output); - Process::kill(intval($this->daemon_file['pid']), SIGUSR1); - $output->writeln('成功重载!'); - return 0; - } -} diff --git a/src/ZM/Command/Daemon/DaemonStatusCommand.php b/src/ZM/Command/Daemon/DaemonStatusCommand.php deleted file mode 100644 index 19ab4b8d..00000000 --- a/src/ZM/Command/Daemon/DaemonStatusCommand.php +++ /dev/null @@ -1,35 +0,0 @@ -setDescription('查看框架的运行状态'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - parent::execute($input, $output); - $output->writeln('框架' . ($this->daemon_file['daemon'] ? '以守护进程模式' : '') . '运行中,pid:' . $this->daemon_file['pid'] . ''); - if ($this->daemon_file['daemon']) { - $output->writeln('----- 以下是stdout内容 -----'); - $stdout = file_get_contents($this->daemon_file['stdout']); - $stdout = explode("\n", $stdout); - for ($i = 15; $i > 0; --$i) { - if (isset($stdout[count($stdout) - $i])) { - echo $stdout[count($stdout) - $i] . PHP_EOL; - } - } - } - return 0; - } -} diff --git a/src/ZM/Command/Daemon/DaemonStopCommand.php b/src/ZM/Command/Daemon/DaemonStopCommand.php deleted file mode 100644 index f36e4e04..00000000 --- a/src/ZM/Command/Daemon/DaemonStopCommand.php +++ /dev/null @@ -1,37 +0,0 @@ -setDescription('停止运行的框架'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - parent::execute($input, $output); - Process::kill(intval($this->daemon_file['pid']), SIGTERM); - $i = 10; - while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) { - sleep(1); - --$i; - } - if ($i === 0) { - $output->writeln('停止失败,请检查进程pid #' . $this->daemon_file['pid'] . ' 是否响应!'); - } else { - $output->writeln('成功停止!'); - } - return 0; - } -} diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php index d8a4818d..15352844 100644 --- a/src/ZM/Command/PureHttpCommand.php +++ b/src/ZM/Command/PureHttpCommand.php @@ -16,7 +16,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use ZM\Config\ZMConfig; use ZM\Console\Console; -use ZM\Framework; +use ZM\Logger\TablePrinter; use ZM\Store\ZMAtomic; use ZM\Utils\DataProvider; use ZM\Utils\HttpUtil; @@ -55,7 +55,8 @@ class PureHttpCommand extends Command 'web_root' => realpath($input->getArgument('dir') ?? '.'), 'index' => implode(',', $index), ]; - Framework::printProps($out, $tty_width); + $printer = new TablePrinter($out); + $printer->printAll(); $server = new Server($host, $port); $server->set(ZMConfig::get('global', 'swoole')); Console::init(2, $server); diff --git a/src/ZM/Command/Daemon/DaemonCommand.php b/src/ZM/Command/Server/ServerCommand.php similarity index 50% rename from src/ZM/Command/Daemon/DaemonCommand.php rename to src/ZM/Command/Server/ServerCommand.php index 27d17dce..173c7efc 100644 --- a/src/ZM/Command/Daemon/DaemonCommand.php +++ b/src/ZM/Command/Server/ServerCommand.php @@ -2,23 +2,31 @@ declare(strict_types=1); -namespace ZM\Command\Daemon; +namespace ZM\Command\Server; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Framework; +use ZM\Exception\ZMKnownException; +use ZM\Utils\Manager\ProcessManager; -abstract class DaemonCommand extends Command +abstract class ServerCommand extends Command { protected $daemon_file; + /** + * @throws ZMKnownException + */ protected function execute(InputInterface $input, OutputInterface $output): int { - $file = Framework::getProcessState(ZM_PROCESS_MASTER); + $file = ProcessManager::getProcessState(ZM_PROCESS_MASTER); if ($file === false || posix_getsid(intval($file['pid'])) === false) { $output->writeln('未检测到正在运行的守护进程或框架进程!'); - Framework::removeProcessState(ZM_PROCESS_MASTER); + if (ProcessManager::isStateEmpty()) { + ProcessManager::removeProcessState(ZM_PROCESS_MASTER); + } else { + $output->writeln('检测到可能残留的守护进程或框架进程,请使用命令关闭:server:stop --force'); + } exit(1); } $this->daemon_file = $file; diff --git a/src/ZM/Command/Server/ServerReloadCommand.php b/src/ZM/Command/Server/ServerReloadCommand.php index 8d87503c..7829cd13 100644 --- a/src/ZM/Command/Server/ServerReloadCommand.php +++ b/src/ZM/Command/Server/ServerReloadCommand.php @@ -7,9 +7,8 @@ namespace ZM\Command\Server; use Swoole\Process; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Command\Daemon\DaemonCommand; -class ServerReloadCommand extends DaemonCommand +class ServerReloadCommand extends ServerCommand { protected static $defaultName = 'server:reload'; diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/Server/ServerStartCommand.php similarity index 90% rename from src/ZM/Command/RunServerCommand.php rename to src/ZM/Command/Server/ServerStartCommand.php index 6633bb6a..8fbf706d 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/Server/ServerStartCommand.php @@ -2,19 +2,22 @@ declare(strict_types=1); -namespace ZM\Command; +namespace ZM\Command\Server; -use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use ZM\Exception\ConfigException; +use ZM\Exception\ZMKnownException; use ZM\Framework; +use ZM\Utils\Manager\ProcessManager; -class RunServerCommand extends Command +class ServerStartCommand extends ServerCommand { protected static $defaultName = 'server'; - public static function exportDefinition() + public static function exportDefinition(): InputDefinition { $cmd = new self(); $cmd->configure(); @@ -54,6 +57,10 @@ class RunServerCommand extends Command $this->setHelp('直接运行可以启动'); } + /** + * @throws ZMKnownException + * @throws ConfigException + */ protected function execute(InputInterface $input, OutputInterface $output): int { if (($opt = $input->getOption('env')) !== null) { @@ -62,7 +69,7 @@ class RunServerCommand extends Command return 1; } } - $state = Framework::getProcessState(ZM_PROCESS_MASTER); + $state = ProcessManager::getProcessState(ZM_PROCESS_MASTER); if (!$input->getOption('no-state-check')) { if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) { $output->writeln("检测到已经在 pid: {$state['pid']} 进程启动了框架!"); diff --git a/src/ZM/Command/Server/ServerStatusCommand.php b/src/ZM/Command/Server/ServerStatusCommand.php index d3abb6ff..4b0fe449 100644 --- a/src/ZM/Command/Server/ServerStatusCommand.php +++ b/src/ZM/Command/Server/ServerStatusCommand.php @@ -6,9 +6,8 @@ namespace ZM\Command\Server; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Command\Daemon\DaemonCommand; -class ServerStatusCommand extends DaemonCommand +class ServerStatusCommand extends ServerCommand { protected static $defaultName = 'server:status'; diff --git a/src/ZM/Command/Server/ServerStopCommand.php b/src/ZM/Command/Server/ServerStopCommand.php index c78eeefc..d2ef5006 100644 --- a/src/ZM/Command/Server/ServerStopCommand.php +++ b/src/ZM/Command/Server/ServerStopCommand.php @@ -8,11 +8,10 @@ use Swoole\Process; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Command\Daemon\DaemonCommand; -use ZM\Framework; use ZM\Utils\DataProvider; +use ZM\Utils\Manager\ProcessManager; -class ServerStopCommand extends DaemonCommand +class ServerStopCommand extends ServerCommand { protected static $defaultName = 'server:stop'; @@ -47,7 +46,7 @@ class ServerStopCommand extends DaemonCommand Process::kill(intval($this->daemon_file['pid']), SIGTERM); } $i = 10; - while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) { + while (ProcessManager::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) { sleep(1); --$i; } diff --git a/src/ZM/Config/ZMConfig.php b/src/ZM/Config/ZMConfig.php index 8a555860..64d0c425 100644 --- a/src/ZM/Config/ZMConfig.php +++ b/src/ZM/Config/ZMConfig.php @@ -86,7 +86,7 @@ class ZMConfig self::$config[$head_name] = self::loadConfig($head_name); } // global.remote_terminal - // 根据切分来寻找子配置 + Console::debug('根据切分来寻找子配置: ' . $name); $obj = self::$config[$head_name]; foreach ($separated as $key) { if (isset($obj[$key])) { diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 1a2d9f11..ad8ef2bf 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -11,17 +11,14 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use ZM\Command\BuildCommand; use ZM\Command\CheckConfigCommand; -use ZM\Command\Daemon\DaemonReloadCommand; -use ZM\Command\Daemon\DaemonStatusCommand; -use ZM\Command\Daemon\DaemonStopCommand; use ZM\Command\Generate\SystemdGenerateCommand; use ZM\Command\InitCommand; use ZM\Command\Module\ModuleListCommand; use ZM\Command\Module\ModulePackCommand; use ZM\Command\Module\ModuleUnpackCommand; use ZM\Command\PureHttpCommand; -use ZM\Command\RunServerCommand; use ZM\Command\Server\ServerReloadCommand; +use ZM\Command\Server\ServerStartCommand; use ZM\Command\Server\ServerStatusCommand; use ZM\Command\Server\ServerStopCommand; use ZM\Exception\InitException; @@ -84,13 +81,10 @@ class ConsoleApplication extends Application } $this->addCommands([ - new DaemonStatusCommand(), - new DaemonReloadCommand(), - new DaemonStopCommand(), - new RunServerCommand(), // 运行主服务的指令控制器 new ServerStatusCommand(), - new ServerStopCommand(), new ServerReloadCommand(), + new ServerStopCommand(), + new ServerStartCommand(), // 运行主服务的指令控制器 new PureHttpCommand(), // 纯HTTP服务器指令 new SystemdGenerateCommand(), ]); diff --git a/src/ZM/Event/SwooleEvent/OnManagerStart.php b/src/ZM/Event/SwooleEvent/OnManagerStart.php index 4c324f1a..11fefe80 100644 --- a/src/ZM/Event/SwooleEvent/OnManagerStart.php +++ b/src/ZM/Event/SwooleEvent/OnManagerStart.php @@ -46,7 +46,7 @@ class OnManagerStart implements SwooleEvent if (!Framework::$argv['disable-safe-exit']) { SignalListener::signalManager(); } - Framework::saveProcessState(ZM_PROCESS_MANAGER, $server->manager_pid); + ProcessManager::saveProcessState(ZM_PROCESS_MANAGER, $server->manager_pid); ProcessManager::createUserProcess('monitor', function () use ($server) { Process::signal(SIGINT, function () { diff --git a/src/ZM/Event/SwooleEvent/OnManagerStop.php b/src/ZM/Event/SwooleEvent/OnManagerStop.php index c1b92bf7..79fed871 100644 --- a/src/ZM/Event/SwooleEvent/OnManagerStop.php +++ b/src/ZM/Event/SwooleEvent/OnManagerStop.php @@ -8,7 +8,6 @@ use Swoole\Process; use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\Console; use ZM\Event\SwooleEvent; -use ZM\Framework; use ZM\Utils\Manager\ProcessManager; /** @@ -25,6 +24,6 @@ class OnManagerStop implements SwooleEvent } } Console::verbose('进程 Manager 已停止!'); - Framework::removeProcessState(ZM_PROCESS_MANAGER); + ProcessManager::removeProcessState(ZM_PROCESS_MANAGER); } } diff --git a/src/ZM/Event/SwooleEvent/OnShutdown.php b/src/ZM/Event/SwooleEvent/OnShutdown.php index d2f108c6..3cafa7c4 100644 --- a/src/ZM/Event/SwooleEvent/OnShutdown.php +++ b/src/ZM/Event/SwooleEvent/OnShutdown.php @@ -10,8 +10,8 @@ use Swoole\Server; use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\Console; use ZM\Event\SwooleEvent; -use ZM\Framework; use ZM\Utils\DataProvider; +use ZM\Utils\Manager\ProcessManager; /** * Class OnShutdown @@ -22,7 +22,7 @@ class OnShutdown implements SwooleEvent public function onCall(Server $server) { Console::verbose('正在关闭 Master 进程,pid=' . posix_getpid()); - Framework::removeProcessState(ZM_PROCESS_MASTER); + ProcessManager::removeProcessState(ZM_PROCESS_MASTER); if (DataProvider::scanDirFiles(_zm_pid_dir()) == []) { rmdir(_zm_pid_dir()); } diff --git a/src/ZM/Event/SwooleEvent/OnStart.php b/src/ZM/Event/SwooleEvent/OnStart.php index dd863e4a..07c574ec 100644 --- a/src/ZM/Event/SwooleEvent/OnStart.php +++ b/src/ZM/Event/SwooleEvent/OnStart.php @@ -10,6 +10,7 @@ use ZM\Config\ZMConfig; use ZM\Console\Console; use ZM\Event\SwooleEvent; use ZM\Framework; +use ZM\Utils\Manager\ProcessManager; use ZM\Utils\SignalListener; /** @@ -24,7 +25,7 @@ class OnStart implements SwooleEvent if (!Framework::$argv['disable-safe-exit']) { SignalListener::signalMaster($server); } - Framework::saveProcessState(ZM_PROCESS_MASTER, $server->master_pid, [ + ProcessManager::saveProcessState(ZM_PROCESS_MASTER, $server->master_pid, [ 'stdout' => ZMConfig::get('global')['swoole']['log_file'], 'daemon' => (bool) Framework::$argv['daemon'], ]); diff --git a/src/ZM/Event/SwooleEvent/OnWorkerExit.php b/src/ZM/Event/SwooleEvent/OnWorkerExit.php index 8da90aaf..2cf6e870 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerExit.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerExit.php @@ -28,6 +28,6 @@ class OnWorkerExit implements SwooleEvent Coroutine::resume($v['coroutine']); } } - Console::info('正在结束 Worker #' . $worker_id . ',进程内可能有事务在运行...'); + Console::verbose('正在结束 Worker #' . $worker_id . ',进程内可能有事务在运行...'); } } diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStart.php b/src/ZM/Event/SwooleEvent/OnWorkerStart.php index a5762eaf..fb725c7d 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStart.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStart.php @@ -34,11 +34,13 @@ use ZM\Exception\ZMKnownException; use ZM\Framework; use ZM\MySQL\MySQLPool; use ZM\Store\LightCacheInside; +use ZM\Store\Lock\SpinLock; use ZM\Store\MySQL\SqlPoolStorage; use ZM\Store\Redis\ZMRedisPool; use ZM\Utils\DataProvider; use ZM\Utils\Manager\CronManager; use ZM\Utils\Manager\ModuleManager; +use ZM\Utils\Manager\ProcessManager; use ZM\Utils\SignalListener; /** @@ -58,7 +60,7 @@ class OnWorkerStart implements SwooleEvent $this->registerWorkerContainerBindings($server); if ($server->taskworker === false) { - Framework::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $worker_id]); + ProcessManager::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'); @@ -109,12 +111,13 @@ class OnWorkerStart implements SwooleEvent } else { Console::warning('@OnStart 执行异常!'); } - Console::success('Worker #' . $worker_id . ' started'); + Console::verbose('Worker #' . $worker_id . ' started'); + $this->gatherWorkerStartStatus(); } catch (Exception $e) { if ($e->getMessage() === 'swoole exit') { return; } - Console::error('Worker加载出错!停止服务!'); + Console::error('Worker #' . $server->worker_id . ' 加载出错!停止服务!'); Console::error(zm_internal_errcode('E00030') . 'Uncaught ' . get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString()); Process::kill($server->master_pid, SIGTERM); return; @@ -128,13 +131,13 @@ class OnWorkerStart implements SwooleEvent } } else { // 这里是TaskWorker初始化的内容部分 - Framework::saveProcessState(ZM_PROCESS_TASKWORKER, $server->worker_pid, ['worker_id' => $worker_id]); + ProcessManager::saveProcessState(ZM_PROCESS_TASKWORKER, $server->worker_pid, ['worker_id' => $worker_id]); try { Framework::$server = $server; $this->loadAnnotations(); - Console::success('TaskWorker #' . $server->worker_id . ' started'); + Console::verbose('TaskWorker #' . $server->worker_id . ' started'); } catch (Exception $e) { - Console::error('Worker加载出错!停止服务!'); + Console::error('TaskWorker #' . $server->worker_id . ' 加载出错!停止服务!'); Console::error(zm_internal_errcode('E00030') . $e->getMessage() . "\n" . $e->getTraceAsString()); Process::kill($server->master_pid, SIGTERM); return; @@ -314,4 +317,21 @@ class OnWorkerStart implements SwooleEvent $container->singleton(AdapterInterface::class, OneBot11Adapter::class); } + + private function gatherWorkerStartStatus() + { + SpinLock::lock('worker_start_status'); + $ls = LightCacheInside::get('tmp_kv', 'worker_start_status') ?? []; + $ls[\server()->worker_id] = true; + if (count($ls) === \server()->setting['worker_num']) { + LightCacheInside::set('tmp_kv', 'worker_start_status', $ls); + SpinLock::unlock('worker_start_status'); + $used = round((microtime(true) - LightCacheInside::get('tmp_kv', 'start_time')) * 1000, 3); + $worker_count = \server()->setting['worker_num']; + Console::success("{$worker_count} 个工作进程成功启动,共用时 {$used} ms"); + } else { + LightCacheInside::set('tmp_kv', 'worker_start_status', $ls); + SpinLock::unlock('worker_start_status'); + } + } } diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStop.php b/src/ZM/Event/SwooleEvent/OnWorkerStop.php index 3ecd8e06..16c994cf 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStop.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStop.php @@ -10,8 +10,8 @@ use ZM\Config\ZMConfig; use ZM\Console\Console; use ZM\Container\WorkerContainer; use ZM\Event\SwooleEvent; -use ZM\Framework; use ZM\Store\LightCache; +use ZM\Utils\Manager\ProcessManager; /** * Class OnWorkerStop @@ -27,6 +27,6 @@ class OnWorkerStop implements SwooleEvent LightCache::savePersistence(); } 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); + ProcessManager::removeProcessState($server->taskworker ? ZM_PROCESS_TASKWORKER : ZM_PROCESS_WORKER, $worker_id); } } diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 27667850..09062e58 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -18,14 +18,14 @@ use ZM\Annotation\Swoole\SwooleHandler; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; use ZM\Console\Console; -use ZM\Console\TermColor; use ZM\Exception\ConfigException; -use ZM\Exception\ZMKnownException; +use ZM\Logger\TablePrinter; use ZM\Store\LightCache; use ZM\Store\LightCacheInside; use ZM\Store\Lock\SpinLock; use ZM\Store\ZMAtomic; use ZM\Utils\DataProvider; +use ZM\Utils\Manager\ProcessManager; use ZM\Utils\Terminal; use ZM\Utils\ZMUtil; @@ -84,9 +84,9 @@ class Framework */ public function __construct(array $args = [], bool $instant_mode = false) { - $tty_width = $this->getTtyWidth(); self::$instant_mode = $instant_mode; self::$argv = $args; + Runtime::enableCoroutine(false); // 初始化配置 ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); @@ -152,7 +152,7 @@ class Framework } // 解析命令行参数 - $this->parseCliArgs(self::$argv, $add_port); + $coroutine_mode = $this->parseCliArgs(self::$argv, $add_port); // 设置默认最长等待时间 if (!isset($this->swoole_server_config['max_wait_time'])) { @@ -180,7 +180,7 @@ class Framework } $out['environment'] = ($args['env'] ?? null) === null ? 'default' : $args['env']; $out['log_level'] = Console::getLevel(); - $out['version'] = ZM_VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : ''); + $out['version'] = Framework::VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : ''); $out['master_pid'] = posix_getpid(); if (APP_VERSION !== 'unknown') { $out['app_version'] = APP_VERSION; @@ -212,7 +212,12 @@ class Framework $conf = ZMConfig::get('global', 'remote_terminal'); $out['terminal'] = $conf['host'] . ':' . $conf['port']; } - self::printProps($out, $tty_width, $args['log-theme'] === null); + if (LOAD_MODE === 0) { + echo Console::setColor("* Framework started with source mode.\n", $args['log-theme'] === null ? 'yellow' : ''); + } + // $this->mapOutput($out); // 汉化操作 + $printer = new TablePrinter($out); + $printer->setValueColor('random')->printAll(); } // 预览模式则直接提出 @@ -312,7 +317,14 @@ class Framework // 非静默模式下,打印欢迎信息 if (!self::$argv['private-mode']) { - self::printMotd($tty_width); + self::printMotd(isset($printer) ? $printer->fetchTerminalSize() : 79); + } + + $global_hook = ZMConfig::get('global', 'runtime')['swoole_coroutine_hook_flags'] ?? (SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)); + if ($coroutine_mode) { + Runtime::enableCoroutine(true, $global_hook); + } else { + Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL); } global $asd; @@ -374,157 +386,21 @@ class Framework } catch (Exception $e) { Console::error('框架初始化出现异常,请检查!'); Console::error(zm_internal_errcode('E00010') . $e->getMessage()); + if (strpos($e->getMessage(), 'Address already in use') !== false) { + if (!ProcessManager::isStateEmpty()) { + Console::error('检测到可能残留框架的工作进程,请先通过命令杀死:server:stop --force'); + } + } Console::debug($e); exit; } } - /** - * 将各进程的pid写入文件,以备后续崩溃及僵尸进程处理使用 - * - * @param int|string $pid - * @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 mixed $id_or_name - * @throws ZMKnownException - * @return false|int|mixed - * @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; - } - 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 null|int|string $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(); + LightCacheInside::set('tmp_kv', 'start_time', microtime(true)); self::$server->start(); zm_atomic('server_is_stopped')->set(1); if (!self::$argv['private-mode']) { @@ -535,63 +411,6 @@ class Framework } } - public static function printProps($out, $tty_width, $colorful = true) - { - $max_border = min($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; - - $current_line = 0; - $line_width = []; - $line_data = []; - foreach ($out as $k => $v) { - start: - if (!isset($line_width[$current_line])) { - $line_width[$current_line] = $max_border - 2; - } - // Console::info("行宽[$current_line]:".$line_width[$current_line]); - if ($max_border >= 57) { // 很宽的时候,一行能放两个短行 - if ($line_width[$current_line] === ($max_border - 2)) { // 空行 - self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border); - } else { // 不是空行,已经有东西了 - $tmp_line = $k . ': ' . $v; - // Console::info("[$current_line]即将插入后面的东西[".$tmp_line."]"); - if (strlen($tmp_line) > $line_width[$current_line]) { // 地方不够,另起一行 - $line_data[$current_line] = str_replace('| ', '', $line_data[$current_line]); - ++$current_line; - goto start; - } // 地方够,直接写到后面并另起一行 - $line_data[$current_line] .= $k . ': '; - if ($colorful) { - $line_data[$current_line] .= TermColor::color8(32); - } - $line_data[$current_line] .= $v; - if ($colorful) { - $line_data[$current_line] .= TermColor::RESET; - } - ++$current_line; - } - } else { // 不够宽,直接写单行 - self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border); - } - } - foreach ($line_data as $v) { - echo $v . PHP_EOL; - } - echo str_pad('', $max_border, '=') . PHP_EOL; - } - - public function getTtyWidth(): int - { - $size = exec('stty size'); - if (empty($size)) { - return 65; - } - return (int) explode(' ', trim($size))[1]; - } - public static function loadFrameworkState() { if (!file_exists(DataProvider::getDataFolder() . '.state.json')) { @@ -609,6 +428,24 @@ class Framework return file_put_contents(DataProvider::getDataFolder() . '.state.json', json_encode($data, 64 | 128 | 256)); } + public function mapOutput(array &$out) + { + $translate = [ + 'working_dir' => '工作目录', + 'listen' => '监听地址', + 'worker' => '工作进程数', + 'environment' => '环境类型', + 'log_level' => '日志级别', + 'version' => '框架版本', + 'master_pid' => '主进程PID', + ]; + foreach ($out as $k => $v) { + if (isset($translate[$k])) { + $this->arrayChangeKey($out, $k, $translate[$k]); + } + } + } + private static function printMotd($tty_width) { if (file_exists(DataProvider::getSourceRootDir() . '/config/motd.txt')) { @@ -776,68 +613,14 @@ class Framework } } } - $global_hook = ZMConfig::get('global', 'runtime')['swoole_coroutine_hook_flags'] ?? (SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)); - if ($coroutine_mode) { - Runtime::enableCoroutine(true, $global_hook); - } else { - Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL); - } + return $coroutine_mode; } - private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) + private function arrayChangeKey(array &$arr, $old_key, $new_key) { - $tmp_line = $k . ': ' . $v; - // Console::info("写入[".$tmp_line."]"); - if (strlen($tmp_line) > $line_width[$current_line]) { // 输出的内容太多了,以至于一行都放不下一个,要折行 - $title_strlen = strlen($k . ': '); - $content_len = $line_width[$current_line] - $title_strlen; - - $line_data[$current_line] = ' ' . $k . ': '; - if ($colorful) { - $line_data[$current_line] .= TermColor::color8(32); - } - $line_data[$current_line] .= substr($v, 0, $content_len); - if ($colorful) { - $line_data[$current_line] .= TermColor::RESET; - } - $rest = substr($v, $content_len); - ++$current_line; // 带标题的第一行满了,折到第二行 - do { - if ($colorful) { - $line_data[$current_line] = TermColor::color8(32); - } - $line_data[$current_line] .= ' ' . substr($rest, 0, $max_border - 2); - if ($colorful) { - $line_data[$current_line] .= TermColor::RESET; - } - $rest = substr($rest, $max_border - 2); - ++$current_line; - } while ($rest > $max_border - 2); // 循环,直到放完 - } else { // 不需要折行 - // Console::info("不需要折行"); - $line_data[$current_line] = ' ' . $k . ': '; - if ($colorful) { - $line_data[$current_line] .= TermColor::color8(32); - } - $line_data[$current_line] .= $v; - if ($colorful) { - $line_data[$current_line] .= TermColor::RESET; - } - - if ($max_border >= 57) { - if (strlen($tmp_line) >= intval(($max_border - 2) / 2)) { // 不需要折行,直接输出一个转下一行 - // Console::info("不需要折行,直接输出一个转下一行"); - ++$current_line; - } else { // 输出很小,写到前面并分片 - // Console::info("输出很小,写到前面并分片"); - $space = intval($max_border / 2) - 2 - strlen($tmp_line); - $line_data[$current_line] .= str_pad('', $space); - $line_data[$current_line] .= '| '; // 添加分片 - $line_width[$current_line] -= (strlen($tmp_line) + 3 + $space); - } - } else { - ++$current_line; - } - } + $keys = array_keys($arr); + $values = array_values($arr); + $keys[array_search($old_key, $keys)] = $new_key; + $arr = array_combine($keys, $values); } } diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php index edd86990..347223c2 100644 --- a/src/ZM/Store/LightCacheInside.php +++ b/src/ZM/Store/LightCacheInside.php @@ -21,6 +21,7 @@ class LightCacheInside self::createTable('connect', 3, 64); // 用于存单机器人模式下的机器人fd的 self::createTable('static_route', 64, 256); // 用于存储 self::createTable('light_array', 8, 512, 0.6); + self::createTable('tmp_kv', 3, 512, 0.6); } catch (ZMException $e) { return false; } // 用于存协程等待的状态内容的 diff --git a/src/ZM/Store/Lock/SpinLock.php b/src/ZM/Store/Lock/SpinLock.php index 5c6664fd..4990507f 100644 --- a/src/ZM/Store/Lock/SpinLock.php +++ b/src/ZM/Store/Lock/SpinLock.php @@ -6,8 +6,6 @@ declare(strict_types=1); namespace ZM\Store\Lock; -use Swoole\Coroutine; -use Swoole\Coroutine\System; use Swoole\Table; class SpinLock @@ -28,11 +26,7 @@ class SpinLock public static function lock(string $key) { while (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { // 此资源已经被锁上了 - if (Coroutine::getCid() != -1) { - System::sleep(self::$delay / 1000); - } else { - usleep(self::$delay * 1000); - } + usleep(self::$delay * 1000); } } diff --git a/src/ZM/Utils/CoMessage.php b/src/ZM/Utils/CoMessage.php index 1199f6ba..42560000 100644 --- a/src/ZM/Utils/CoMessage.php +++ b/src/ZM/Utils/CoMessage.php @@ -6,6 +6,7 @@ namespace ZM\Utils; use Exception; use Swoole\Coroutine; +use Swoole\Timer; use ZM\Store\LightCacheInside; use ZM\Store\Lock\SpinLock; use ZM\Store\ZMAtomic; @@ -19,7 +20,7 @@ class CoMessage public static function yieldByWS(array $hang, array $compare, int $timeout = 600) { $cid = Coroutine::getuid(); - $api_id = ZMAtomic::get('wait_msg_id')->add(1); + $api_id = ZMAtomic::get('wait_msg_id')->add(); $hang['compare'] = $compare; $hang['coroutine'] = $cid; $hang['worker_id'] = server()->worker_id; @@ -42,7 +43,7 @@ class CoMessage unset($sess[$api_id]); LightCacheInside::set('wait_api', 'wait_api', $sess); SpinLock::unlock('wait_api'); - swoole_timer_clear($id); + Timer::clear($id); if ($result === null) { return false; } diff --git a/src/ZM/Utils/Manager/ProcessManager.php b/src/ZM/Utils/Manager/ProcessManager.php index 143e5109..727850b1 100644 --- a/src/ZM/Utils/Manager/ProcessManager.php +++ b/src/ZM/Utils/Manager/ProcessManager.php @@ -7,6 +7,8 @@ declare(strict_types=1); namespace ZM\Utils\Manager; use Swoole\Process; +use ZM\Exception\ZMKnownException; +use ZM\Utils\DataProvider; class ProcessManager { @@ -22,4 +24,152 @@ class ProcessManager { return self::$user_process[$string] ?? null; } + + /** + * @param null|int|string $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; + } + } + + /** + * 用于框架内部获取多进程运行状态的函数 + * + * @param mixed $id_or_name + * @throws ZMKnownException + * @return false|int|mixed + * @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; + } + 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; + } + } + + /** + * 将各进程的pid写入文件,以备后续崩溃及僵尸进程处理使用 + * + * @param int|string $pid + * @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; + } + } + + public static function isStateEmpty(): bool + { + $ls = DataProvider::scanDirFiles(_zm_pid_dir(), false, true); + return empty($ls); + } } diff --git a/src/ZM/ZMServer.php b/src/ZM/ZMServer.php index cbc1203f..0df2a012 100644 --- a/src/ZM/ZMServer.php +++ b/src/ZM/ZMServer.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace ZM; -use ZM\Command\RunServerCommand; +use ZM\Command\Server\ServerStartCommand; use ZM\Console\Console; use ZM\Event\EventManager; use ZM\Exception\InitException; @@ -59,11 +59,11 @@ class ZMServer define('SOURCE_ROOT_DIR', WORKING_DIR); define('LOAD_MODE', is_dir(SOURCE_ROOT_DIR . '/src/ZM') ? 0 : 1); define('FRAMEWORK_ROOT_DIR', realpath(__DIR__ . '/../../')); - define('ZM_VERSION_ID', ConsoleApplication::VERSION_ID); - define('ZM_VERSION', ConsoleApplication::VERSION); + define('ZM_VERSION_ID', Framework::VERSION_ID); + define('ZM_VERSION', Framework::VERSION); $options = array_map(function ($x) { return $x->getDefault(); - }, RunServerCommand::exportDefinition()->getOptions()); + }, ServerStartCommand::exportDefinition()->getOptions()); (new Framework($options, true))->start(); } diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index ae5504a7..ae23087f 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -46,7 +46,7 @@ function get_class_path(string $class_name): ?string */ function _zm_env_check() { - if (!extension_loaded('swoole')) { + if (!extension_loaded('swoole') && !extension_loaded('openswoole')) { exit(zm_internal_errcode('E00001') . "Can not find swoole extension.\n"); } if (version_compare(SWOOLE_VERSION, '4.5.0') === -1) { diff --git a/zhamao b/zhamao index f8581dbd..c3eb7aaf 100755 --- a/zhamao +++ b/zhamao @@ -5,8 +5,10 @@ # author: crazywhalecc # since: 2.5.0 - -if [ -f "$(pwd)/runtime/php" ]; then +if [ "$ZM_CUSTOM_PHP_PATH" != "" ]; then + echo "* Using PHP executable: ""$ZM_CUSTOM_PHP_PATH" + executable="$ZM_CUSTOM_PHP_PATH" +elif [ -f "$(pwd)/runtime/php" ]; then executable="$(pwd)/runtime/php" echo "* Framework started with built-in php." else