From 93a6642e42da4f2513968bfd41ce1965fce995ef Mon Sep 17 00:00:00 2001 From: Viktor Date: Wed, 23 Sep 2020 01:53:00 +0300 Subject: [PATCH] Improve signal loop (#54) * Set soft memory limit in the default loop * Add soft limit reached event and signals config * Apply fixes from StyleCI * Make signal list immutable * Add typehints * Remove redundant phpdoc * Divide loops * Apply fixes from StyleCI * Fix signals: SIGSTOP can't be dispatched programmatically * Fix tests --- config/common.php | 8 ++- src/Cli/SignalLoop.php | 101 +++++++++++++++---------------- src/Cli/SimpleLoop.php | 47 ++++++++++++++ src/Cli/SoftLimitTrait.php | 30 +++++++++ src/Event/MemoryLimitReached.php | 27 +++++++++ tests/TestCase.php | 4 +- 6 files changed, 161 insertions(+), 56 deletions(-) create mode 100644 src/Cli/SimpleLoop.php create mode 100644 src/Cli/SoftLimitTrait.php create mode 100644 src/Event/MemoryLimitReached.php diff --git a/config/common.php b/config/common.php index efa8c2d8..477fe038 100644 --- a/config/common.php +++ b/config/common.php @@ -1,7 +1,9 @@ [$params['yiisoft/yii-queue']['handlers']], ], WorkerInterface::class => QueueWorker::class, - LoopInterface::class => SignalLoop::class, + LoopInterface::class => static function (ContainerInterface $container) { + return extension_loaded('pcntl') + ? $container->get(SignalLoop::class) + : $container->get(SimpleLoop::class); + }, ]; diff --git a/src/Cli/SignalLoop.php b/src/Cli/SignalLoop.php index e8c05861..fc852e55 100644 --- a/src/Cli/SignalLoop.php +++ b/src/Cli/SignalLoop.php @@ -8,55 +8,39 @@ namespace Yiisoft\Yii\Queue\Cli; -/** - * Signal Loop. - * - * @author Roman Zhuravlev - * - * @since 2.0.2 - */ +use Psr\EventDispatcher\EventDispatcherInterface; + class SignalLoop implements LoopInterface { - /** - * @var array of signals to exit from listening of the queue. - */ - protected array $exitSignals = [ - 15, // SIGTERM - 2, // SIGINT - 1, // SIGHUP - ]; - /** - * @var array of signals to suspend listening of the queue. - * For example: SIGTSTP - */ - protected array $suspendSignals = []; - /** - * @var array of signals to resume listening of the queue. - * For example: SIGCONT - */ - protected array $resumeSignals = []; + use SoftLimitTrait; + + protected const SIGNALS_EXIT = [SIGHUP, SIGINT, SIGTERM]; + protected const SIGNALS_SUSPEND = [SIGTSTP]; + protected const SIGNALS_RESUME = [SIGCONT]; + + protected int $memorySoftLimit; + protected EventDispatcherInterface $dispatcher; + protected bool $pause; + protected bool $exit; /** - * @var bool status when exit signal was got. - */ - protected bool $exit = false; - /** - * @var bool status when suspend or resume signal was got. + * @param EventDispatcherInterface $dispatcher + * @param int $memorySoftLimit Soft RAM limit in bytes. The loop won't let you continue to execute the program if + * soft limit is reached. Zero means no limit. */ - protected bool $pause = false; - - public function __construct() + public function __construct(EventDispatcherInterface $dispatcher, int $memorySoftLimit = 0) { - if (extension_loaded('pcntl')) { - foreach ($this->exitSignals as $signal) { - pcntl_signal($signal, fn () => $this->exit = true); - } - foreach ($this->suspendSignals as $signal) { - pcntl_signal($signal, fn () => $this->pause = true); - } - foreach ($this->resumeSignals as $signal) { - pcntl_signal($signal, fn () => $this->pause = false); - } + $this->dispatcher = $dispatcher; + $this->memorySoftLimit = $memorySoftLimit; + + foreach (self::SIGNALS_EXIT as $signal) { + pcntl_signal($signal, fn () => $this->exit = true); + } + foreach (self::SIGNALS_SUSPEND as $signal) { + pcntl_signal($signal, fn () => $this->pause = true); + } + foreach (self::SIGNALS_RESUME as $signal) { + pcntl_signal($signal, fn () => $this->pause = false); } } @@ -67,25 +51,36 @@ public function __construct() */ public function canContinue(): bool { - if (extension_loaded('pcntl')) { + if ($this->memoryLimitReached()) { + return false; + } + + return $this->dispatchSignals(); + } + + protected function dispatchSignals(): bool + { + $this->pause = false; + $this->exit = false; + + pcntl_signal_dispatch(); + + // Wait for resume signal until loop is suspended + while ($this->pause && !$this->exit) { + usleep(10000); pcntl_signal_dispatch(); - // Wait for resume signal until loop is suspended - while ($this->pause && !$this->exit) { - usleep(10000); - pcntl_signal_dispatch(); - } } return !$this->exit; } - public function setResumeSignals(array $resumeSignals): void + protected function getMemoryLimit(): int { - $this->resumeSignals = $resumeSignals; + return $this->memorySoftLimit; } - public function setSuspendSignals(array $suspendSignals): void + protected function getEventDispatcher(): EventDispatcherInterface { - $this->suspendSignals = $suspendSignals; + return $this->dispatcher; } } diff --git a/src/Cli/SimpleLoop.php b/src/Cli/SimpleLoop.php new file mode 100644 index 00000000..4c886552 --- /dev/null +++ b/src/Cli/SimpleLoop.php @@ -0,0 +1,47 @@ +dispatcher = $dispatcher; + $this->memorySoftLimit = $memorySoftLimit; + } + + public function canContinue(): bool + { + return !$this->memoryLimitReached(); + } + + protected function getEventDispatcher(): EventDispatcherInterface + { + return $this->dispatcher; + } + + protected function getMemoryLimit(): int + { + return $this->memorySoftLimit; + } +} diff --git a/src/Cli/SoftLimitTrait.php b/src/Cli/SoftLimitTrait.php new file mode 100644 index 00000000..60d939bd --- /dev/null +++ b/src/Cli/SoftLimitTrait.php @@ -0,0 +1,30 @@ +getMemoryLimit(); + + if ($limit !== 0) { + $usage = memory_get_usage(true); + + if ($usage >= $limit) { + $this->getEventDispatcher()->dispatch(new MemoryLimitReached($limit, $usage)); + return true; + } + } + + return false; + } +} diff --git a/src/Event/MemoryLimitReached.php b/src/Event/MemoryLimitReached.php new file mode 100644 index 00000000..d35fedb1 --- /dev/null +++ b/src/Event/MemoryLimitReached.php @@ -0,0 +1,27 @@ +limit = $limit; + $this->actual = $actual; + } + + public function getActualUsage(): int + { + return $this->actual; + } + + public function getLimit(): int + { + return $this->limit; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 887fbb16..6a2f7321 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -18,7 +18,7 @@ use Yiisoft\Test\Support\Container\SimpleContainer; use Yiisoft\Test\Support\EventDispatcher\SimpleEventDispatcher; use Yiisoft\Yii\Queue\Cli\LoopInterface; -use Yiisoft\Yii\Queue\Cli\SignalLoop; +use Yiisoft\Yii\Queue\Cli\SimpleLoop; use Yiisoft\Yii\Queue\Driver\DriverInterface; use Yiisoft\Yii\Queue\Driver\SynchronousDriver; use Yiisoft\Yii\Queue\Event\AfterExecution; @@ -141,7 +141,7 @@ protected function createDriver(bool $realDriver = false): DriverInterface protected function createLoop(): LoopInterface { - return new SignalLoop(); + return new SimpleLoop($this->getEventDispatcher()); } protected function createWorker(): WorkerInterface