eee
This commit is contained in:
@@ -67,6 +67,8 @@ class AsyncServer extends Component implements ServerInterface
|
|||||||
}
|
}
|
||||||
$this->shuttingDown = true;
|
$this->shuttingDown = true;
|
||||||
|
|
||||||
|
$this->terminateProcesses();
|
||||||
|
|
||||||
if ($this->server !== null) {
|
if ($this->server !== null) {
|
||||||
$this->server->shutdown();
|
$this->server->shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
+76
-18
@@ -13,6 +13,8 @@ use Kiri\Server\Processes\OnProcessInterface;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Swoole\Event;
|
use Swoole\Event;
|
||||||
use Swoole\Process;
|
use Swoole\Process;
|
||||||
|
use const SIGKILL;
|
||||||
|
use const SIGTERM;
|
||||||
use const SIGUSR1;
|
use const SIGUSR1;
|
||||||
|
|
||||||
class FileWatcher extends AbstractProcess implements OnProcessInterface
|
class FileWatcher extends AbstractProcess implements OnProcessInterface
|
||||||
@@ -35,6 +37,7 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
private array $extensions = ['php'];
|
private array $extensions = ['php'];
|
||||||
private string $strategy;
|
private string $strategy;
|
||||||
private bool $running = false;
|
private bool $running = false;
|
||||||
|
private bool $scanning = false;
|
||||||
|
|
||||||
private $inotifyFd = null;
|
private $inotifyFd = null;
|
||||||
private array $inotifyWatchMap = [];
|
private array $inotifyWatchMap = [];
|
||||||
@@ -132,6 +135,10 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
|
|
||||||
public function process(Process|null $process): void
|
public function process(Process|null $process): void
|
||||||
{
|
{
|
||||||
|
if (class_exists('\\Swoole\\Coroutine')) {
|
||||||
|
\Swoole\Coroutine::set(['enable_deadlock_check' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->running) {
|
if ($this->running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -163,20 +170,14 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
$this->inotifyFd = null;
|
$this->inotifyFd = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->fswatchPipes[1]) && is_resource($this->fswatchPipes[1])) {
|
foreach ($this->fswatchPipes as $pipe) {
|
||||||
@Event::del($this->fswatchPipes[1]);
|
if (is_resource($pipe)) {
|
||||||
@fclose($this->fswatchPipes[1]);
|
@Event::del($pipe);
|
||||||
}
|
@fclose($pipe);
|
||||||
|
}
|
||||||
if (isset($this->fswatchPipes[2]) && is_resource($this->fswatchPipes[2])) {
|
|
||||||
@fclose($this->fswatchPipes[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->fswatchProcess && is_resource($this->fswatchProcess)) {
|
|
||||||
@proc_terminate($this->fswatchProcess);
|
|
||||||
@proc_close($this->fswatchProcess);
|
|
||||||
$this->fswatchProcess = null;
|
|
||||||
}
|
}
|
||||||
|
$this->fswatchPipes = [];
|
||||||
|
$this->stopFswatchProcess();
|
||||||
|
|
||||||
if ($this->pollTimer) {
|
if ($this->pollTimer) {
|
||||||
\Swoole\Timer::clear($this->pollTimer);
|
\Swoole\Timer::clear($this->pollTimer);
|
||||||
@@ -191,6 +192,43 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
@Event::exit();
|
@Event::exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function stopFswatchProcess(): void
|
||||||
|
{
|
||||||
|
if (!$this->fswatchProcess || !is_resource($this->fswatchProcess)) {
|
||||||
|
$this->fswatchProcess = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = @proc_get_status($this->fswatchProcess);
|
||||||
|
if (($status['running'] ?? false) === true) {
|
||||||
|
@proc_terminate($this->fswatchProcess, SIGTERM);
|
||||||
|
for ($i = 0; $i < 10; $i++) {
|
||||||
|
usleep(100000);
|
||||||
|
$status = @proc_get_status($this->fswatchProcess);
|
||||||
|
if (($status['running'] ?? false) !== true) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = @proc_get_status($this->fswatchProcess);
|
||||||
|
if (($status['running'] ?? false) === true) {
|
||||||
|
@proc_terminate($this->fswatchProcess, SIGKILL);
|
||||||
|
for ($i = 0; $i < 5; $i++) {
|
||||||
|
usleep(100000);
|
||||||
|
$status = @proc_get_status($this->fswatchProcess);
|
||||||
|
if (($status['running'] ?? false) !== true) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = @proc_get_status($this->fswatchProcess);
|
||||||
|
if (($status['running'] ?? false) !== true) {
|
||||||
|
@proc_close($this->fswatchProcess);
|
||||||
|
}
|
||||||
|
$this->fswatchProcess = null;
|
||||||
|
}
|
||||||
private function isExcluded(string $path): bool
|
private function isExcluded(string $path): bool
|
||||||
{
|
{
|
||||||
foreach ($this->excludePatterns as $pattern) {
|
foreach ($this->excludePatterns as $pattern) {
|
||||||
@@ -377,6 +415,10 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
Event::add($this->inotifyFd, function ($fd) {
|
Event::add($this->inotifyFd, function ($fd) {
|
||||||
|
if (!$this->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$events = inotify_read($fd);
|
$events = inotify_read($fd);
|
||||||
if ($events === false) {
|
if ($events === false) {
|
||||||
return;
|
return;
|
||||||
@@ -466,6 +508,10 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
stream_set_blocking($this->fswatchPipes[1], false);
|
stream_set_blocking($this->fswatchPipes[1], false);
|
||||||
|
|
||||||
Event::add($this->fswatchPipes[1], function ($pipe) {
|
Event::add($this->fswatchPipes[1], function ($pipe) {
|
||||||
|
if (!$this->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$data = fread($pipe, 8192);
|
$data = fread($pipe, 8192);
|
||||||
if ($data === false || $data === '') {
|
if ($data === false || $data === '') {
|
||||||
return;
|
return;
|
||||||
@@ -486,13 +532,25 @@ class FileWatcher extends AbstractProcess implements OnProcessInterface
|
|||||||
|
|
||||||
$intervalMs = $this->pollInterval * 1000;
|
$intervalMs = $this->pollInterval * 1000;
|
||||||
$this->pollTimer = \Swoole\Timer::tick($intervalMs, function () {
|
$this->pollTimer = \Swoole\Timer::tick($intervalMs, function () {
|
||||||
$newSnapshot = [];
|
if (!$this->running || $this->scanning) {
|
||||||
$changedFiles = $this->compareSnapshots($newSnapshot);
|
return;
|
||||||
if (!empty($changedFiles)) {
|
|
||||||
$this->triggerCallback($changedFiles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->fileSnapshot = $newSnapshot;
|
$this->scanning = true;
|
||||||
|
try {
|
||||||
|
$newSnapshot = [];
|
||||||
|
$changedFiles = $this->compareSnapshots($newSnapshot);
|
||||||
|
if (!$this->running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!empty($changedFiles)) {
|
||||||
|
$this->triggerCallback($changedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fileSnapshot = $newSnapshot;
|
||||||
|
} finally {
|
||||||
|
$this->scanning = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -76,10 +76,20 @@ class HotReload extends AbstractProcess
|
|||||||
*/
|
*/
|
||||||
public function onSigterm(): void
|
public function onSigterm(): void
|
||||||
{
|
{
|
||||||
// TODO: Implement onSigterm() method.
|
|
||||||
$this->stop();
|
$this->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function stop(): void
|
||||||
|
{
|
||||||
|
parent::stop();
|
||||||
|
if (isset($this->pipe) && is_resource($this->pipe)) {
|
||||||
|
@Event::del($this->pipe);
|
||||||
|
@fclose($this->pipe);
|
||||||
|
}
|
||||||
|
@Event::exit();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ?Process $process
|
* @param ?Process $process
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -49,9 +49,7 @@ trait TraitServer
|
|||||||
$handling = true;
|
$handling = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pidFile = function_exists('storage') ? storage('.swoole.pid') : '';
|
if (!$this->shouldHandleSigint()) {
|
||||||
$masterPid = is_file($pidFile) ? (int)trim((string)file_get_contents($pidFile)) : 0;
|
|
||||||
if ($masterPid > 1 && $masterPid !== getmypid()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +60,81 @@ trait TraitServer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function shouldHandleSigint(): bool
|
||||||
|
{
|
||||||
|
$currentPid = getmypid();
|
||||||
|
if ($this->isMasterProcess($currentPid)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$masterPid = $this->getVerifiedPidFileMasterPid();
|
||||||
|
return $masterPid !== null && $masterPid === $currentPid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getVerifiedPidFileMasterPid(): ?int
|
||||||
|
{
|
||||||
|
$pidFile = function_exists('storage') ? storage('.swoole.pid') : '';
|
||||||
|
if (!is_file($pidFile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = (int)trim((string)file_get_contents($pidFile));
|
||||||
|
if ($pid <= 1 || !$this->isProcessAlive($pid) || !$this->isSameAppProcess($pid) || !$this->isMasterProcess($pid)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function isMasterProcess(int $pid): bool
|
||||||
|
{
|
||||||
|
$command = $this->getProcessCommand($pid);
|
||||||
|
if ($command === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix = '[' . config('site.id', 'system-service') . '].Master[';
|
||||||
|
return str_contains($command, $prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getProcessCommand(int $pid): string
|
||||||
|
{
|
||||||
|
$command = @file_get_contents('/proc/' . $pid . '/cmdline');
|
||||||
|
if (!is_string($command) || $command === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_replace("\0", ' ', $command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function isSameAppProcess(int $pid): bool
|
||||||
|
{
|
||||||
|
$cwd = @readlink('/proc/' . $pid . '/cwd');
|
||||||
|
if ($cwd === false || $cwd === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->normalizePath($cwd) === $this->normalizePath(APP_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function normalizePath(string $path): string
|
||||||
|
{
|
||||||
|
$real = realpath($path) ?: $path;
|
||||||
|
return rtrim(str_replace('\\', '/', $real), '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function isProcessAlive(int $pid): bool
|
||||||
|
{
|
||||||
|
return $pid > 1 && @Process::kill($pid, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $signal
|
* @param $signal
|
||||||
* @param $callback
|
* @param $callback
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ abstract class AbstractProcess implements OnProcessInterface
|
|||||||
|
|
||||||
private bool $stop = false;
|
private bool $stop = false;
|
||||||
|
|
||||||
|
private bool $stopping = false;
|
||||||
|
|
||||||
|
|
||||||
public Process $process;
|
public Process $process;
|
||||||
|
|
||||||
@@ -132,10 +134,11 @@ abstract class AbstractProcess implements OnProcessInterface
|
|||||||
$array['deadlock_check_disable_trace'] = false;
|
$array['deadlock_check_disable_trace'] = false;
|
||||||
$array['exit_condition'] = [$this, 'exit_condition'];
|
$array['exit_condition'] = [$this, 'exit_condition'];
|
||||||
Coroutine::set($array);
|
Coroutine::set($array);
|
||||||
|
$process::signal(SIGINT, static function (): void {});
|
||||||
Coroutine::create(fn() => $this->coroutineWaitSignal());
|
Coroutine::create(fn() => $this->coroutineWaitSignal());
|
||||||
} else {
|
} else {
|
||||||
$process::signal(SIGTERM, [$this, 'pointWaitSignal']);
|
$process::signal(SIGTERM, [$this, 'pointWaitSignal']);
|
||||||
$process::signal(SIGINT, [$this, 'pointWaitSignal']);
|
$process::signal(SIGINT, static function (): void {});
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -153,9 +156,7 @@ abstract class AbstractProcess implements OnProcessInterface
|
|||||||
*/
|
*/
|
||||||
public function pointWaitSignal($signal): void
|
public function pointWaitSignal($signal): void
|
||||||
{
|
{
|
||||||
$this->stop = true;
|
$this->requestStop();
|
||||||
|
|
||||||
$this->onSigterm();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -164,10 +165,20 @@ abstract class AbstractProcess implements OnProcessInterface
|
|||||||
*/
|
*/
|
||||||
public function coroutineWaitSignal(): void
|
public function coroutineWaitSignal(): void
|
||||||
{
|
{
|
||||||
$value = Coroutine::waitSignal(SIGTERM);
|
Coroutine::waitSignal(SIGTERM);
|
||||||
if ($value) {
|
$this->requestStop();
|
||||||
$this->stop = true;
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private function requestStop(): void
|
||||||
|
{
|
||||||
|
if ($this->stopping) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->stopping = true;
|
||||||
|
$this->stop = true;
|
||||||
$this->onSigterm();
|
$this->onSigterm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace Kiri\Server\Processes;
|
|||||||
use Exception;
|
use Exception;
|
||||||
use Kiri;
|
use Kiri;
|
||||||
use Swoole\Process;
|
use Swoole\Process;
|
||||||
|
use const SIGKILL;
|
||||||
|
use const SIGTERM;
|
||||||
|
|
||||||
trait TraitProcess
|
trait TraitProcess
|
||||||
{
|
{
|
||||||
@@ -66,4 +68,39 @@ trait TraitProcess
|
|||||||
{
|
{
|
||||||
return $this->_process;
|
return $this->_process;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function terminateProcesses(): void
|
||||||
|
{
|
||||||
|
$pids = [];
|
||||||
|
foreach ($this->_process as $process) {
|
||||||
|
$pid = (int)($process->pid ?? 0);
|
||||||
|
if ($pid > 1 && $pid !== getmypid()) {
|
||||||
|
$pids[] = $pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pids = array_values(array_unique($pids));
|
||||||
|
foreach ($pids as $pid) {
|
||||||
|
if (@Process::kill($pid, 0)) {
|
||||||
|
@Process::kill($pid, SIGTERM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; $i++) {
|
||||||
|
$alive = array_values(array_filter($pids, static fn(int $pid): bool => @Process::kill($pid, 0)));
|
||||||
|
if ($alive === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($pids as $pid) {
|
||||||
|
if (@Process::kill($pid, 0)) {
|
||||||
|
@Process::kill($pid, SIGKILL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user