This commit is contained in:
xl
2023-07-06 16:00:02 +08:00
parent 136b0dc1a6
commit 6aa764fb86
2 changed files with 310 additions and 327 deletions
+289 -322
View File
@@ -5,371 +5,338 @@ namespace Kiri\Server;
use Exception; use Exception;
use Kiri\Di\Context; use Kiri\Di\Context;
use Kiri\Server\Abstracts\BaseProcess;
use Swoole\Coroutine;
use Swoole\Event; use Swoole\Event;
use Swoole\Process; use Swoole\Process;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
class HotReload extends Command class HotReload extends BaseProcess
{ {
public string $name = 'hot:reload'; /**
* @var array|mixed
*/
private array $watchFiles = [];
private array $md5Map = []; /**
* @var array|string[]
*/
private array $dirs = [APP_PATH . 'app/', APP_PATH . 'config/', APP_PATH . 'routes/'];
/** /**
* @var Process * @return string
*/ */
private mixed $process; public function getName(): string
/** {
* @var array|mixed return 'hot.load'; // TODO: Change the autogenerated stub
*/ }
private array $watchFiles = [];
/** /**
* @return void * @return $this
*/ */
protected function configure() public function onSigterm(): static
{ {
$this->setName('hot:load'); // TODO: Implement onSigterm() method.
parent::configure(); // TODO: Change the autogenerated stub if (Context::inCoroutine()) {
} Coroutine::create(fn() => $this->onShutdown(Coroutine::waitSignal(SIGTERM | SIGINT)));
} else {
Process::signal(SIGTERM | SIGINT, fn($data) => $this->onShutdown($data));
}
return $this;
}
private array $dirs = [APP_PATH . 'app/', APP_PATH . 'config/', APP_PATH . 'routes/']; /**
* @param Process|null $process
* @return void
* @throws Exception
*/
public function process(?Process $process): void
{
// TODO: Implement process() method.
if (extension_loaded('inotify')) {
$this->onInotifyReload();
} else {
$this->onCrontabReload();
}
}
/** /**
* @param InputInterface $input * @return void
* @param OutputInterface $output * @throws Exception
* @return int */
* @throws Exception private function onCrontabReload(): void
*/ {
protected function execute(InputInterface $input, OutputInterface $output): int $this->loadDirs();
{ $this->tick();
$this->startProcess(); }
Process::signal(SIGINT, fn() => $this->exit());
Process::signal(SIGTERM, fn() => $this->exit());
sleep(3);
// if (extension_loaded('inotify')) {
// $this->onInotifyReload();
// } else {
$this->onCrontabReload();
// }
return 1;
}
/** /**
* @return void * @return void
* @throws Exception * @throws Exception
*/ */
public function exit(): void private function onInotifyReload(): void
{ {
$this->stopProcess(); $init = inotify_init();
} foreach ($this->dirs as $dir) {
if (!is_dir($dir)) {
continue;
}
$this->watch($init, $dir);
}
Event::add($init, fn() => $this->check($init));
Event::cycle(function () use ($init) {
$pid = (int)file_get_contents(storage('.swoole.pid'));
if ($pid <= 0 || !Process::kill($pid, 0) || $this->isStop()) {
Event::del($init);
}
}, true);
Event::wait();
}
/** /**
* @return void * @param bool $isReload
*/ * @throws Exception
private function startProcess(): void */
{ private function loadDirs(bool $isReload = false): void
$this->process = proc_open([PHP_BINARY, APP_PATH . 'kiri.php', 'sw:server', 'restart'], [], $pipes); {
} foreach ($this->dirs as $value) {
if (is_bool($path = realpath($value))) {
continue;
}
/** if (!is_dir($path)) continue;
* @return void
* @throws Exception $this->loadByDir($path, $isReload);
*/ }
private function stopProcess(): void }
{
if (!is_resource($this->process)) {
return;
}
$pid = (int)file_get_contents(storage('.swoole.pid'));
if (posix_kill($pid, 0)) {
posix_kill($pid, SIGTERM);
}
proc_close($this->process);
$this->process = null;
}
/** /**
* @return void * @throws Exception
* @throws Exception */
*/ public function tick(): void
private function onCrontabReload(): void {
{ $isReloading = Context::get('isReloading', false);
$this->loadDirs(); if ($isReloading) {
$this->tick(); 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();
}
/** /**
* @return void * @param $path
* @throws Exception * @param bool $isReload
*/ * @return void
private function onInotifyReload(): void * @throws Exception
{ */
$init = inotify_init(); private function loadByDir($path, bool $isReload = false): void
foreach ($this->dirs as $dir) { {
if (!is_dir($dir)) { if (!is_string($path)) {
continue; return;
} }
$this->watch($init, $dir); $path = rtrim($path, '/');
} foreach (glob(realpath($path) . '/*') as $value) {
Event::add($init, fn() => $this->check($init)); if (is_dir($value)) {
Event::cycle(function () use ($init) { $this->loadByDir($value, $isReload);
$pid = (int)file_get_contents(storage('.swoole.pid')); }
if ($pid <= 0 || !Process::kill($pid, 0)) { if (is_file($value)) {
Event::del($init); if ($this->checkFile($value, $isReload)) {
} $this->timerReload();
}, true); break;
Event::wait(); }
} }
}
}
/** /**
* @param bool $isReload * @param $value
* @throws Exception * @param $isReload
*/ * @return bool
private function loadDirs(bool $isReload = false): void */
{ private function checkFile($value, $isReload): bool
foreach ($this->dirs as $value) { {
if (is_bool($path = realpath($value))) { $md5 = md5($value);
continue; $mTime = filectime($value);
} if (!isset($this->md5Map[$md5])) {
if ($isReload) {
if (!is_dir($path)) continue; return true;
}
$this->loadByDir($path, $isReload); $this->md5Map[$md5] = $mTime;
} } else {
} if ($this->md5Map[$md5] != $mTime) {
if ($isReload) {
return true;
}
$this->md5Map[$md5] = $mTime;
}
}
return false;
}
/** /**
* @throws Exception * 开始监听
*/ */
public function tick(): void public function check($inotify): void
{ {
$isReloading = Context::get('isReloading', false); if (!($events = inotify_read($inotify))) {
if ($isReloading) { return;
return; }
} $isReloading = Context::get('isReloading', false);
if ($isReloading) {
return;
}
$pid = (int)file_get_contents(storage('.swoole.pid')); $eventList = [IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_TO, IN_MOVED_FROM];
if ($pid <= 0 || !Process::kill($pid, 0)) { foreach ($events as $ev) {
return; 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::get('swoole_timer_after') !== -1) {
return;
}
$int = @swoole_timer_after(2000, fn() => $this->reload($inotify));
Context::set('swoole_timer_after', $int);
Context::set('isReloading', true);
}
}
$this->loadDirs(true); /**
* @throws Exception
*/
public function reload($inotify): void
{
Context::set('isReloading', true);
$this->trigger_reload();
sleep(2); $this->clearWatch($inotify);
foreach ($this->dirs as $root) {
$this->watch($inotify, $root);
}
Context::set('swoole_timer_after', -1);
Context::set('isReloading', false);
$this->md5Map = [];
}
$this->tick(); /**
} * @throws Exception
*/
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();
}
/** /**
* @param $path * 重启
* @param bool $isReload * @throws Exception
* @return void */
* @throws Exception public function trigger_reload(): void
*/ {
private function loadByDir($path, bool $isReload = false): void echo 'tigger server Reload' . PHP_EOL;
{ di(ServerInterface::class)->reload(false);
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 * @throws Exception
* @param $isReload */
* @return bool public function clearWatch($inotify): void
*/ {
private function checkFile($value, $isReload): bool foreach ($this->watchFiles as $wd) {
{ try {
$md5 = md5($value); inotify_rm_watch($inotify, $wd);
$mTime = filectime($value); } catch (\Throwable $exception) {
if (!isset($this->md5Map[$md5])) { logger()->addError($exception, 'throwable');
if ($isReload) { }
return true; }
} $this->watchFiles = [];
$this->md5Map[$md5] = $mTime; }
} else {
if ($this->md5Map[$md5] != $mTime) {
if ($isReload) {
return true;
}
$this->md5Map[$md5] = $mTime;
}
}
return false;
}
/** /**
* 开始监听 * @param $inotify
*/ * @param $dir
public function check($inotify): void * @return bool
{ * @throws Exception
if (!($events = inotify_read($inotify))) { */
return; public function watch($inotify, $dir): bool
} {
$isReloading = Context::get('isReloading', false); //目录不存在
if ($isReloading) { if (!is_dir($dir)) {
return; return logger()->addError("[$dir] is not a directory.");
} }
//避免重复监听
if (isset($this->watchFiles[$dir])) {
return FALSE;
}
$eventList = [IN_CREATE, IN_DELETE, IN_MODIFY, IN_MOVED_TO, IN_MOVED_FROM]; if (in_array($dir, [APP_PATH . 'commands', APP_PATH . '.git', APP_PATH . '.gitee'])) {
foreach ($events as $ev) { return FALSE;
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::get('swoole_timer_after') !== -1) {
return;
}
$int = @swoole_timer_after(2000, fn() => $this->reload($inotify));
Context::set('swoole_timer_after', $int);
Context::set('isReloading', true);
}
}
/** $wd = @inotify_add_watch($inotify, $dir, IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE);
* @throws Exception $this->watchFiles[$dir] = $wd;
*/
public function reload($inotify): void
{
Context::set('isReloading', true);
$this->trigger_reload();
$this->clearWatch($inotify); $files = scandir($dir);
foreach ($this->dirs as $root) { foreach ($files as $f) {
$this->watch($inotify, $root); if ($f == '.' or $f == '..' or $f == 'runtime' or preg_match('/\.txt/', $f) or preg_match('/\.sql/', $f) or preg_match('/\.log/', $f)) {
} continue;
Context::set('swoole_timer_after', -1); }
Context::set('isReloading', false); $path = $dir . '/' . $f;
$this->md5Map = []; //递归目录
} if (is_dir($path)) {
$this->watch($inotify, $path);
}
/** //检测文件类型
* @throws Exception if (strstr($f, '.') == '.php') {
*/ $wd = @inotify_add_watch($inotify, $path, IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE);
public function timerReload(): void $this->watchFiles[$path] = $wd;
{ }
Context::set('isReloading', true); }
$this->trigger_reload(); return TRUE;
}
Context::set('swoole_timer_after', -1);
$this->loadDirs();
Context::set('isReloading', false);
$this->tick();
}
/**
* 重启
* @throws Exception
*/
public function trigger_reload(): void
{
echo 'tigger server Reload' . PHP_EOL;
$this->stopProcess();
$this->startProcess();
}
/**
* @throws Exception
*/
public function clearWatch($inotify): void
{
foreach ($this->watchFiles as $wd) {
try {
inotify_rm_watch($inotify, $wd);
} catch (\Throwable $exception) {
logger()->addError($exception, 'throwable');
}
}
$this->watchFiles = [];
}
/**
* @param $inotify
* @param $dir
* @return bool
* @throws Exception
*/
public function watch($inotify, $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, [APP_PATH . 'commands', APP_PATH . '.git', APP_PATH . '.gitee'])) {
return FALSE;
}
$wd = @inotify_add_watch($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($inotify, $path);
}
//检测文件类型
if (strstr($f, '.') == '.php') {
$wd = @inotify_add_watch($inotify, $path, IN_MODIFY | IN_DELETE | IN_CREATE | IN_MOVE);
$this->watchFiles[$path] = $wd;
}
}
return TRUE;
}
} }
+21 -5
View File
@@ -78,15 +78,30 @@ class Server
on(OnWorkerStart::class, [$this, 'setWorkerName']); on(OnWorkerStart::class, [$this, 'setWorkerName']);
on(OnTaskerStart::class, [$this, 'setTaskerName']); on(OnTaskerStart::class, [$this, 'setTaskerName']);
$manager = Kiri::getDi()->get(Router::class); if (\config('reload.hot') === false) {
$manager->scan_build_route(); $this->hotLoad();
} else {
on(OnWorkerStart::class, [$this, 'hotLoad']);
$this->addProcess(HotReload::class);
}
$manager = $this->manager(); $manager = $this->manager();
$manager->initCoreServers(\config('server', [], true), $this->daemon); $manager->initCoreServers(\config('server', []), $this->daemon);
$manager->start(); $manager->start();
} }
/**
* @return void
* @throws ReflectionException
*/
public function hotLoad(): void
{
$manager = Kiri::getDi()->get(Router::class);
$manager->scan_build_route();
}
/** /**
* @param OnWorkerStart $onWorkerStart * @param OnWorkerStart $onWorkerStart
*/ */
@@ -125,10 +140,11 @@ class Server
*/ */
public function shutdown(): void public function shutdown(): void
{ {
$configs = \config('server', [], true); $configs = \config('server', []);
$state = Kiri::getDi()->get(State::class); $state = Kiri::getDi()->get(State::class);
foreach ($this->manager()->sortService($configs['ports'] ?? []) as $config) { $instances = $this->manager()->sortService($configs['ports'] ?? []);
foreach ($instances as $config) {
$state->exit($config->port); $state->exit($config->port);
} }