diff --git a/Server/Abstracts/CustomProcess.php b/Server/Abstracts/CustomProcess.php new file mode 100644 index 00000000..79e04e79 --- /dev/null +++ b/Server/Abstracts/CustomProcess.php @@ -0,0 +1,70 @@ +enableSwooleCoroutine) { + Process::signal(SIGTERM | SIGKILL, function ($signo) + use ($process) { + $this->waiteExit($process); + }); + } else { + go(function () use ($process) { + $data = Coroutine::waitSignal(SIGTERM | SIGKILL, -1); + if ($data) { + $this->waiteExit($process); + } + }); + } + } + + + public function isWorking(): bool + { + return false; + } + + + /** + * + */ + private function waiteExit(Process $process): void + { + while ($this->isWorking()) { + $this->sleep(); + } + $process->exit(0); + } + + + /** + * + */ + private function sleep(): void + { + if ($this->enableSwooleCoroutine) { + Coroutine::sleep(0.1); + } else { + usleep(100); + } + } + +} diff --git a/Server/SInterface/CustomProcess.php b/Server/SInterface/CustomProcess.php index f7fdd60b..91f9c1a5 100644 --- a/Server/SInterface/CustomProcess.php +++ b/Server/SInterface/CustomProcess.php @@ -22,11 +22,16 @@ interface CustomProcess public function getProcessName(Process $process): string; + /** + * @param Process $process + */ + public function signListen(Process $process): void; + + /** * @param Process $process */ public function onHandler(Process $process): void; - } diff --git a/Server/ServerManager.php b/Server/ServerManager.php index 8ee30312..36255085 100644 --- a/Server/ServerManager.php +++ b/Server/ServerManager.php @@ -131,6 +131,8 @@ class ServerManager $soloProcess->name($system . '.' . $customProcess->getProcessName($soloProcess) . ' start.'); } + $customProcess->signListen($soloProcess); + echo sprintf("\033[36m[" . date('Y-m-d H:i:s') . "]\033[0m Process %s start.", $customProcess->getProcessName($soloProcess)) . PHP_EOL; $customProcess->onHandler($soloProcess); }, diff --git a/System/Application.php b/System/Application.php index d6487bbd..e76f0716 100644 --- a/System/Application.php +++ b/System/Application.php @@ -23,7 +23,10 @@ use Kiri\Abstracts\Input; use Kiri\Abstracts\Kernel; use Kiri\Crontab\CrontabProviders; use Kiri\Exception\NotFindClassException; +use Kiri\FileListen\FileChangeCustomProcess; +use ReflectionException; use Server\ResponseInterface; +use Server\ServerManager; use stdClass; use Swoole\Timer; @@ -74,6 +77,18 @@ class Application extends BaseApplication } + /** + * @throws NotFindClassException + * @throws ReflectionException + * @throws Exception + */ + public function withFileChangeListen() + { + $manager = di(ServerManager::class); + $manager->addProcess(FileChangeCustomProcess::class); + } + + /** * @param Closure|array $closure * @return $this @@ -168,7 +183,7 @@ class Application extends BaseApplication * @param $data * @return Response|ResponseInterface * @throws NotFindClassException - * @throws \ReflectionException + * @throws ReflectionException * @throws Exception */ private function getBuilder($data): Response|ResponseInterface diff --git a/System/Crontab/Zookeeper.php b/System/Crontab/Zookeeper.php index e8a33ff6..6c66b2e0 100644 --- a/System/Crontab/Zookeeper.php +++ b/System/Crontab/Zookeeper.php @@ -6,7 +6,7 @@ namespace Kiri\Crontab; use Exception; use Server\ServerManager; -use Server\SInterface\CustomProcess; +use Server\Abstracts\CustomProcess; use Kiri\Abstracts\Config; use Kiri\Cache\Redis; use Kiri\Exception\ConfigException; @@ -19,7 +19,7 @@ use Throwable; * Class Zookeeper * @package Kiri\Process */ -class Zookeeper implements CustomProcess +class Zookeeper extends CustomProcess { diff --git a/System/FileListen/FileChangeCustomProcess.php b/System/FileListen/FileChangeCustomProcess.php new file mode 100644 index 00000000..d125ad2a --- /dev/null +++ b/System/FileListen/FileChangeCustomProcess.php @@ -0,0 +1,85 @@ +dirs = Config::get('inotify', [APP_PATH . 'app']); + if (!extension_loaded('inotify')) { + $driver = Kiri::getDi()->get(Scaner::class, [$this->dirs, $this]); + } else { + $driver = Kiri::getDi()->get(Inotify::class, [$this->dirs, $this]); + } + $driver->start(); + } + + + /** + * @param $code + * @param $message + * @param $file + * @param $line + * @throws Exception + */ + public function onErrorHandler($code, $message, $file, $line) + { + if (str_contains($message, 'The file descriptor is not an inotify instance')) { + return; + } + debug('Error:' . $message . ' at ' . $file . ':' . $line); + } + + + /** + * 重启 + * @throws Exception + */ + public function trigger_reload() + { + exec(PHP_BINARY . ' ' . APP_PATH . 'kiri.php runtime:builder', $output); + + print_r(implode(PHP_EOL, $output)); + + Kiri::reload(); + } +} diff --git a/System/FileListen/Inotify.php b/System/FileListen/Inotify.php new file mode 100644 index 00000000..785a3bdc --- /dev/null +++ b/System/FileListen/Inotify.php @@ -0,0 +1,155 @@ +inotify = inotify_init(); + $this->events = IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE; + foreach ($this->dirs as $dir) { + if (!is_dir($dir)) continue; + $this->watch($dir); + } + Event::add($this->inotify, [$this, 'check']); + Event::wait(); + } + + + + + /** + * 开始监听 + */ + public function check() + { + if (!($events = inotify_read($this->inotify))) { + return; + } + if ($this->process->isReloading) { + if (!$this->process->isReloadingOut) { + $this->process->isReloadingOut = true; + } + return; + } + $LISTEN_TYPE = [IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_TO, IN_MOVED_FROM]; + foreach ($events as $ev) { + if (!in_array($ev['mask'], $LISTEN_TYPE)) { + continue; + } + //非重启类型 + if (str_ends_with($ev['name'], '.php')) { + if ($this->process->int !== -1) { + return; + } + $this->process->int = @swoole_timer_after(2000, [$this, 'reload']); + + $this->process->isReloading = true; + } + } + } + + /** + * @throws Exception + */ + public function reload() + { + $this->process->isReloading = true; + $this->process->trigger_reload(); + + $this->clearWatch(); + foreach ($this->dirs as $root) { + $this->watch($root); + } + $this->process->int = -1; + $this->process->isReloading = FALSE; + $this->process->isReloadingOut = FALSE; + } + + + /** + * @throws Exception + */ + public function clearWatch() + { + foreach ($this->watchFiles as $wd) { + try { + inotify_rm_watch($this->inotify, $wd); + } catch (\Throwable $exception) { + logger()->addError($exception, 'throwable'); + } + } + $this->watchFiles = []; + } + + + /** + * @param $dir + * @return bool + * @throws Exception + */ + public function watch($dir): bool + { + //目录不存在 + if (!is_dir($dir)) { + return logger()->addError("[$dir] is not a directory."); + } + //避免重复监听 + if (isset($this->watchFiles[$dir])) { + return FALSE; + } + + if (in_array($dir, self::IG_DIR)) { + return FALSE; + } + + $wd = @inotify_add_watch($this->inotify, $dir, $this->events); + $this->watchFiles[$dir] = $wd; + + $files = scandir($dir); + foreach ($files as $f) { + if ($f == '.' || $f == '..') { + continue; + } + $path = $dir . '/' . $f; + //递归目录 + if (is_dir($path)) { + $this->watch($path); + } else if (!str_ends_with($f, '.php')) { + continue; + } + + //检测文件类型 + if (strstr($f, '.') == '.php') { + $wd = @inotify_add_watch($this->inotify, $path, $this->events); + $this->watchFiles[$path] = $wd; + } + } + return TRUE; + } +} diff --git a/System/FileListen/Scaner.php b/System/FileListen/Scaner.php new file mode 100644 index 00000000..c325b92d --- /dev/null +++ b/System/FileListen/Scaner.php @@ -0,0 +1,135 @@ +loadDirs(); + $this->tick(); + } + + + /** + * @param bool $isReload + * @throws Exception + */ + private function loadDirs(bool $isReload = false) + { + foreach ($this->dirs as $value) { + if (is_bool($path = realpath($value))) { + continue; + } + + if (!is_dir($path)) continue; + + $this->loadByDir($path, $isReload); + } + } + + + /** + * @param $path + * @param bool $isReload + * @return void + * @throws Exception + */ + private function loadByDir($path, bool $isReload = false): void + { + if (!is_string($path)) { + return; + } + $path = rtrim($path, '/'); + foreach (glob(realpath($path) . '/*') as $value) { + 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; + } + + + /** + * @throws Exception + */ + public function timerReload() + { + $this->process->isReloading = true; + $this->process->trigger_reload(); + + $this->process->int = -1; + + $this->loadDirs(); + + $this->process->isReloading = FALSE; + $this->process->isReloadingOut = FALSE; + + $this->tick(); + } + + + /** + * @throws Exception + */ + public function tick() + { + if ($this->process->isReloading) { + return; + } + + $this->loadDirs(true); + + Timer::after(2000, [$this, 'tick']); + } + +} diff --git a/System/Process/Biomonitoring.php b/System/Process/Biomonitoring.php deleted file mode 100644 index ba02c46a..00000000 --- a/System/Process/Biomonitoring.php +++ /dev/null @@ -1,49 +0,0 @@ -getSwoole(); - Timer::tick(1000, function () use ($server) { - clearstatcache(); - if (filesize($server->setting['log_file']) > 1024000000) { - @unlink($server->setting['log_file']); - Process::kill($server->master_pid, SIGRTMIN); - } - }); - } - -} diff --git a/System/Process/ISystem.php b/System/Process/ISystem.php deleted file mode 100644 index f2834507..00000000 --- a/System/Process/ISystem.php +++ /dev/null @@ -1,13 +0,0 @@ -dirs = Config::get('inotify', [APP_PATH]); - if (extension_loaded('inotify')) { - $this->inotify = inotify_init(); - $this->events = IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE; - foreach ($this->dirs as $dir) { - if (!is_dir($dir)) continue; - $this->watch($dir); - } - Event::add($this->inotify, [$this, 'check']); - Event::wait(); - } else { - $this->loadDirs(); - $this->tick(); - } - } - - - /** - * @param bool $isReload - * @throws Exception - */ - private function loadDirs(bool $isReload = false) - { - foreach ($this->dirs as $value) { - if (is_bool($path = realpath($value))) { - continue; - } - - if (!is_dir($path)) continue; - - $this->loadByDir($path, $isReload); - } - } - - - private array $md5Map = []; - - - /** - * @throws Exception - */ - public function tick() - { - if ($this->isReloading) { - return; - } - - $this->loadDirs(true); - - Timer::after(2000, [$this, 'tick']); - } - - - /** - * @param $path - * @param bool $isReload - * @return void - * @throws Exception - */ - private function loadByDir($path, bool $isReload = false): void - { - if (!is_string($path)) { - return; - } - $path = rtrim($path, '/'); - foreach (glob(realpath($path) . '/*') as $value) { - 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() - { - if (!($events = inotify_read($this->inotify))) { - return; - } - if ($this->isReloading) { - if (!$this->isReloadingOut) { - $this->isReloadingOut = true; - } - return; - } - - $LISTEN_TYPE = [IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_TO, IN_MOVED_FROM]; - foreach ($events as $ev) { - if (!in_array($ev['mask'], $LISTEN_TYPE)) { - continue; - } - //非重启类型 - if (str_ends_with($ev['name'], '.php')) { - if ($this->int !== -1) { - return; - } - $this->int = @swoole_timer_after(2000, [$this, 'reload']); - - $this->isReloading = true; - } - } - } - - /** - * @throws Exception - */ - public function reload() - { - $this->isReloading = true; - $this->trigger_reload(); - - $this->clearWatch(); - foreach ($this->dirs as $root) { - $this->watch($root); - } - $this->int = -1; - $this->isReloading = FALSE; - $this->isReloadingOut = FALSE; - $this->md5Map = []; - } - - /** - * @throws Exception - */ - public function timerReload() - { - $this->isReloading = true; - $this->trigger_reload(); - - $this->int = -1; - - $this->loadDirs(); - - $this->isReloading = FALSE; - $this->isReloadingOut = FALSE; - - $this->tick(); - } - - - /** - * 重启 - * @throws Exception - */ - public function trigger_reload() - { - exec(PHP_BINARY . ' ' . APP_PATH . 'kiri.php runtime:builder', $output); - - print_r(implode(PHP_EOL, $output)); - - Kiri::reload(); - } - - - /** - * @throws Exception - */ - public function clearWatch() - { - foreach ($this->watchFiles as $wd) { - try { - inotify_rm_watch($this->inotify, $wd); - } catch (\Throwable $exception) { - logger()->addError($exception, 'throwable'); - } - } - $this->watchFiles = []; - } - - - /** - * @param $code - * @param $message - * @param $file - * @param $line - * @throws Exception - */ - protected function onErrorHandler($code, $message, $file, $line) - { - if (str_contains($message, 'The file descriptor is not an inotify instance')) { - return; - } - debug('Error:' . $message . ' at ' . $file . ':' . $line); - } - - - const IG_DIR = [APP_PATH . 'commands', APP_PATH . '.git', APP_PATH . '.gitee']; - - - /** - * @param $dir - * @return bool - * @throws Exception - */ - public function watch($dir): bool - { - //目录不存在 - if (!is_dir($dir)) { - return logger()->addError("[$dir] is not a directory."); - } - //避免重复监听 - if (isset($this->watchFiles[$dir])) { - return FALSE; - } - - if (in_array($dir, self::IG_DIR)) { - return FALSE; - } - - $wd = @inotify_add_watch($this->inotify, $dir, $this->events); - $this->watchFiles[$dir] = $wd; - - $files = scandir($dir); - foreach ($files as $f) { - if ($f == '.' || $f == '..') { - continue; - } - $path = $dir . '/' . $f; - //递归目录 - if (is_dir($path)) { - $this->watch($path); - } else if (!str_ends_with($f, '.php')) { - continue; - } - - //检测文件类型 - if (strstr($f, '.') == '.php') { - $wd = @inotify_add_watch($this->inotify, $path, $this->events); - $this->watchFiles[$path] = $wd; - } - } - return TRUE; - } -}