forceFile = \config('reload.forceFile', false); } /** * @return string */ public function getName(): string { // TODO: Change the autogenerated stub return 'hot.load'; } /** * @return $this */ public function onSigterm(): static { // TODO: Implement onSigterm() method. if (Context::inCoroutine()) { Coroutine::create(fn () => $this->onShutdown(Coroutine::waitSignal(SIGTERM | SIGINT))); } else { \pcntl_signal(SIGTERM, [$this, 'onStop']); } return $this; } /** * @param $data * @return void * @throws */ public function onStop($data): void { $this->clearWatch(); if (is_resource($this->inotify)) { Event::del($this->inotify); } $this->onShutdown($data); } /** * @param Process|null $process * @return void * @throws */ public function process(?Process $process): void { // TODO: Implement process() method. if (!$this->forceFile && extension_loaded('inotify')) { $this->onInotifyReload(); } else { $this->onCrontabReload(); } $process->exit(0); } /** * @return void * @throws */ private function onCrontabReload(): void { $this->loadDirs(); $this->tick(); } /** * @return void * @throws */ private function onInotifyReload(): void { $this->inotify = inotify_init(); foreach ($this->dirs as $dir) { if (!is_dir($dir)) { continue; } $this->watch(rtrim($dir, '/')); } Event::add($this->inotify, fn () => $this->check()); Event::cycle(fn () => function () { Event::dispatch(); }, true); Event::wait(); } /** * @param bool $isReload * @throws */ private function loadDirs(bool $isReload = false): void { foreach ($this->dirs as $value) { if (is_bool($path = realpath($value))) { continue; } if (!is_dir($path)) continue; $this->loadByDir($path, $isReload); } } /** * @throws */ public function tick(): void { $isReloading = Context::get('isReloading', false); if ($isReloading) { return; } $pid = (int)file_get_contents(storage('.swoole.pid')); if ($pid <= 0 || !Process::kill($pid, 0) || $this->isStop()) { return; } $this->loadDirs(true); sleep(2); $this->tick(); } /** * @param $path * @param bool $isReload * @return void * @throws */ private function loadByDir($path, bool $isReload = false): void { if (!is_string($path) || $this->isStop()) { return; } $path = rtrim($path, '/'); foreach (glob(realpath($path) . '/*') as $value) { if ($this->isStop()) { break; } if (is_dir($value)) { $this->loadByDir($value, $isReload); } if (is_file($value)) { if ($this->checkFile($value, $isReload)) { $this->timerReload(); break; } } } } /** * @param $value * @param $isReload * @return bool */ private function checkFile($value, $isReload): bool { $md5 = md5($value); $mTime = filectime($value); if (!isset($this->md5Map[$md5])) { if ($isReload) { return true; } $this->md5Map[$md5] = $mTime; } else { if ($this->md5Map[$md5] != $mTime) { if ($isReload) { return true; } $this->md5Map[$md5] = $mTime; } } return false; } /** * 开始监听 */ public function check(): void { if (!($events = inotify_read($this->inotify))) { return; } if (Context::exists('isReloading')) { return; } $eventList = [IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_TO, IN_MOVED_FROM]; foreach ($events as $ev) { if (empty($ev['name'])) { continue; } if ($ev['mask'] == IN_IGNORED) { continue; } if (!in_array($ev['mask'], $eventList)) { continue; } $fileType = strstr($ev['name'], '.'); //非重启类型 if ($fileType !== '.php') { continue; } if (Context::exists('swoole_timer_after')) { return; } $int = @swoole_timer_after(2000, fn () => $this->reload()); Context::set('swoole_timer_after', $int); Context::set('isReloading', true); } } /** * @throws */ public function reload(): void { $this->trigger_reload(); $this->clearWatch(); foreach ($this->dirs as $root) { $this->watch($root); } Context::remove('swoole_timer_after'); Context::remove('isReloading'); $this->md5Map = []; } /** * @throws */ public function timerReload(): void { Context::set('isReloading', true); $this->trigger_reload(); Context::set('swoole_timer_after', -1); $this->loadDirs(); Context::set('isReloading', false); $this->tick(); } /** * 重启 * @throws */ public function trigger_reload(): void { \Kiri::getLogger()->failure('Wait trigger server Reload' . PHP_EOL); di(ServerInterface::class)->reload(false); } /** * @throws */ public function clearWatch(): void { foreach ($this->watchFiles as $wd) { try { inotify_rm_watch($this->inotify, $wd); } catch (\Throwable $exception) { trigger_print_error($exception, 'throwable'); } } $this->watchFiles = []; } /** * @param $dir * @return bool * @throws */ public function watch($dir): bool { //目录不存在 if (!is_dir($dir)) { return trigger_print_error("[$dir] is not a directory."); } //避免重复监听 if (isset($this->watchFiles[$dir])) { return FALSE; } if (in_array($dir, [APP_PATH . 'commands', APP_PATH . '.git', APP_PATH . '.gitee'])) { return FALSE; } $wd = @inotify_add_watch($this->inotify, $dir, IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE); $this->watchFiles[$dir] = $wd; $files = scandir($dir); foreach ($files as $f) { if ($f == '.' or $f == '..' or $f == 'runtime' or preg_match('/\.txt/', $f) or preg_match('/\.sql/', $f) or preg_match('/\.log/', $f)) { continue; } $path = $dir . '/' . $f; //递归目录 if (is_dir($path)) { $this->watch($path); continue; } //检测文件类型 if (strstr($f, '.') == '.php') { $wd = @inotify_add_watch($this->inotify, $path, IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE); $this->watchFiles[$path] = $wd; } } return TRUE; } }