setName('sw:server') ->setDescription('server start|stop|reload|restart') ->addArgument('action', InputArgument::OPTIONAL, 'run action', 'start') ->addOption('daemon', 'd', InputOption::VALUE_NONE, 'is run daemonize'); } /** * @param InputInterface $input * @param OutputInterface $output * @return int * @throws */ public function execute(InputInterface $input, OutputInterface $output): int { return match ($input->getArgument('action')) { 'restart' => $this->restart($input), 'stop' => $this->stop(), 'start' => $this->start($input), default => throw new Exception('I don\'t know what I want to do.') }; } /** * @param InputInterface $input * @return int * @throws */ protected function restart(InputInterface $input): int { $this->stop(); $this->start($input); return 1; } /** * @return int * @throws */ protected function stop(): int { $configs = config('servers.server', []); $instances = $this->asyncServer->sortService($configs['ports'] ?? []); foreach ($instances as $config) { $this->state->exit($config->port); } $this->eventDispatch->dispatch(new OnShutdown()); return 1; } /** * @param InputInterface $input * @return int * @throws */ protected function start(InputInterface $input): int { $this->asyncServer->addProcess(config('process', [])); $hotReload = \config('servers.reload.hot', false) === true; if ($hotReload && !$this->buildHotReloadArtifacts()) { throw new Exception('Hot reload artifact build failed.'); } if ($hotReload) { $this->asyncServer->addProcess([FileWatcher::class]); } if (!$hotReload) { // 非热更模式可在 Master 进程预扫描,Worker 通过 fork 继承扫描结果。 // 热更模式必须让新 Worker 自己加载业务类,避免继承旧类定义后无法重新声明。 di(Router::class)->scan_build_route(); } $this->asyncServer->initCoreServers(config('servers.server', []), (int)$input->getOption('daemon')); $this->asyncServer->start(); return 1; } /** * 热更模式下不要在 Master 进程扫描业务类;先用独立 CLI 进程构建路由 artifact, * Worker 启动/重载时直接导入 artifact,避免多个 Worker 同时全量扫描导致 OOM。 * @return bool */ private function buildHotReloadArtifacts(): bool { $entry = $this->detectConsoleEntry(); if ($entry === null) { \Kiri::getLogger()->println('hot reload build failed: entry script not found'); return false; } $php = PHP_BINARY ?: 'php'; $command = escapeshellarg($php) . ' ' . escapeshellarg($entry) . ' sw:file-build'; $output = []; $returnCode = 0; exec($command . ' 2>&1', $output, $returnCode); foreach ($output as $line) { if ($line !== '') { \Kiri::getLogger()->println('[file-build] ' . $line); } } return $returnCode === 0; } /** * @return string|null */ private function detectConsoleEntry(): ?string { $candidates = [ $_SERVER['SCRIPT_FILENAME'] ?? '', defined('APP_PATH') ? APP_PATH . 'kiri.php' : '', defined('APP_PATH') ? APP_PATH . 'bin/kiri' : '', defined('APP_PATH') ? APP_PATH . 'artisan' : '', ]; foreach ($candidates as $candidate) { if (is_string($candidate) && $candidate !== '' && is_file($candidate)) { return $candidate; } } return null; } }