From a9a5e71f601b476ebea6b8dc6a7fcbc0fe9ff535 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 8 Nov 2024 16:49:50 +0800 Subject: [PATCH 1/6] feat Utils/Pool --- composer.json | 3 +- src/Events/SwooleEvent.php | 2 + src/Exceptions/KilledException.php | 41 ++++++++++++++++ src/Factory.php | 32 +++++++++++- src/Handlers/DefaultHandler.php | 12 +++-- src/Handlers/HandlerInterface.php | 19 +++++++ src/Handlers/HandlerMethods.php | 35 +++++++++++++ src/Handlers/RevoltHandler.php | 21 ++++++++ src/Handlers/RippleHandler.php | 24 ++++++++- src/Handlers/SwooleHandler.php | 38 ++++++++++++++ src/Handlers/SwowHandler.php | 27 ++++++++-- src/Utils/Coroutine/Coroutine.php | 49 +++++++++++++++++++ .../Coroutine/Handlers/CoroutineInterface.php | 10 ++++ .../Coroutine/Handlers/DefaultCoroutine.php | 7 +++ .../Coroutine/Handlers/RevoltCoroutine.php | 6 +++ .../Coroutine/Handlers/RippleCoroutine.php | 10 ++-- .../Coroutine/Handlers/SwooleCoroutine.php | 14 ++++-- .../Coroutine/Handlers/SwowCoroutine.php | 6 +++ src/helpers.php | 21 ++------ tests/FactoryTest.php | 6 +++ tests/mock/TestByFailHandler.php | 35 ------------- tests/mock/TestHandler.php | 8 +++ tests/mock/ripple.php | 4 +- 23 files changed, 357 insertions(+), 73 deletions(-) create mode 100644 src/Exceptions/KilledException.php delete mode 100644 tests/mock/TestByFailHandler.php diff --git a/composer.json b/composer.json index 75197ed..3d75dc4 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "friendsofphp/php-cs-fixer": "^3.0", "symfony/var-dumper": "^6.0", "swoole/ide-helper": "^5.1", - "swow/swow": "^1.5" + "swow/swow": "^1.5", + "revolt/event-loop": "^1.0" }, "autoload": { "psr-4": { diff --git a/src/Events/SwooleEvent.php b/src/Events/SwooleEvent.php index 190756c..1277031 100644 --- a/src/Events/SwooleEvent.php +++ b/src/Events/SwooleEvent.php @@ -190,6 +190,8 @@ public function destroy() foreach (Coroutine::listCoroutines() as $coroutine) { Coroutine::cancel($coroutine); } + // Wait for coroutines to exit + usleep(100000); // 退出event loop Event::exit(); } diff --git a/src/Exceptions/KilledException.php b/src/Exceptions/KilledException.php new file mode 100644 index 0000000..2ed56e1 --- /dev/null +++ b/src/Exceptions/KilledException.php @@ -0,0 +1,41 @@ +event = $event; + parent::__construct($message, $code, $previous); + } + + /** + * @return string|null + */ + public function getEvent(): ?string + { + return $this->event; + } +} diff --git a/src/Factory.php b/src/Factory.php index a807216..ac467c9 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -7,6 +7,7 @@ namespace Workbunny\WebmanCoroutine; +use WeakMap; use Workbunny\WebmanCoroutine\Events\SwooleEvent; use Workbunny\WebmanCoroutine\Events\SwowEvent; use Workbunny\WebmanCoroutine\Handlers\DefaultHandler; @@ -20,7 +21,14 @@ use Workbunny\WebmanCoroutine\Handlers\SwowWorkerman5Handler; /** - * 工厂化启动器 + * 工厂化启动器 + * @method static void isAvailable() + * @method static void initEnv() + * @method static void waitFor(?\Closure $action = null, int|float $timeout = -1, ?string $event = null) + * @method static void wakeup(string $event) + * @method static void sleep(int|float $timeout = 0, ?string $event = null) + * @method static void kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0) + * @method static null|WeakMap listSuspensionsWeakMap() */ class Factory { @@ -47,7 +55,7 @@ class Factory self::WORKERMAN_SWOOLE => SwooleWorkerman5Handler::class, self::WORKBUNNY_SWOOLE => SwooleHandler::class, self::REVOLT_FIBER => RevoltHandler::class, - self::RIPPLE_FIBER => RippleHandler::class, + self::RIPPLE_FIBER_4 => RippleHandler::class, self::RIPPLE_FIBER_5 => RippleWorkerman5Handler::class, ]; @@ -167,4 +175,24 @@ public static function init(?string $eventLoopClass): void self::$_currentEventLoop = $eventLoopClass; } } + + /** + * 代理调用HandlerInterface方法 + * + * @param string $name + * @param array $arguments + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + if (($handler = Factory::getCurrentHandler()) === null) { + Factory::init(null); + /** @var HandlerInterface $handler */ + $handler = Factory::getCurrentHandler(); + } + if (method_exists($handler, $name)) { + return $handler::$name(...$arguments); + } + throw new \BadMethodCallException("Method $name not exists. "); + } } diff --git a/src/Handlers/DefaultHandler.php b/src/Handlers/DefaultHandler.php index a3762cf..149cfe9 100644 --- a/src/Handlers/DefaultHandler.php +++ b/src/Handlers/DefaultHandler.php @@ -14,6 +14,8 @@ */ class DefaultHandler implements HandlerInterface { + use HandlerMethods; + /** * 测试用,为保证覆盖生成时不会无限等待 * @@ -73,12 +75,14 @@ public static function wakeup(string $event): void { } - /** @inheritDoc - * @param float|int $timeout - * @param string|null $event - */ + /** @inheritDoc */ public static function sleep(float|int $timeout = 0, ?string $event = null): void { usleep(max((int) $timeout * 1000 * 1000, 0)); } + + /** @inheritdoc */ + public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void + { + } } diff --git a/src/Handlers/HandlerInterface.php b/src/Handlers/HandlerInterface.php index e0f2d2f..394eb94 100644 --- a/src/Handlers/HandlerInterface.php +++ b/src/Handlers/HandlerInterface.php @@ -8,6 +8,7 @@ namespace Workbunny\WebmanCoroutine\Handlers; use Throwable; +use WeakMap; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; /** @@ -57,4 +58,22 @@ public static function wakeup(string $event): void; * @return void */ public static function sleep(int|float $timeout = 0, ?string $event = null): void; + + /** + * 协程强制终止 + * + * @param object|int|string $suspensionOrSuspensionId + * @param string $message + * @param int $exitCode + * @return void + */ + public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void; + + /** + * 获取所有挂起的对象 + * + * @return WeakMap|null + * @link HandlerMethods::listSuspensionsWeakMap() + */ + public static function listSuspensionsWeakMap(): ?WeakMap; } diff --git a/src/Handlers/HandlerMethods.php b/src/Handlers/HandlerMethods.php index 5afd55c..e7b54dc 100644 --- a/src/Handlers/HandlerMethods.php +++ b/src/Handlers/HandlerMethods.php @@ -4,10 +4,45 @@ namespace Workbunny\WebmanCoroutine\Handlers; +use WeakMap; use Workerman\Worker; trait HandlerMethods { + /** + * @var WeakMap|null <挂起对象, ['id' => int|string, 'event' => string|null, 'startTime' => float|int]> + */ + protected static ?WeakMap $_suspensionsWeakMap = null; + + /** + * 获取挂起对象 + * + * @return WeakMap + */ + public static function listSuspensionsWeakMap(): WeakMap + { + return static::$_suspensionsWeakMap ?: new WeakMap(); + } + + /** + * 添加挂起对象 + * + * @param object $object + * @param string|int $id + * @param string|null $event + * @param float|int $startTime + * @return void + */ + protected static function _setSuspensionsWeakMap(object $object, string|int $id, ?string $event, float|int $startTime): void + { + static::$_suspensionsWeakMap = static::$_suspensionsWeakMap ?: new WeakMap; + static::$_suspensionsWeakMap->offsetSet($object, [ + 'id' => $id, + 'event' => $event, + 'startTime' => $startTime + ]); + } + /** * @codeCoverageIgnore 为了测试可以mock * diff --git a/src/Handlers/RevoltHandler.php b/src/Handlers/RevoltHandler.php index 2409401..b57a290 100644 --- a/src/Handlers/RevoltHandler.php +++ b/src/Handlers/RevoltHandler.php @@ -8,6 +8,7 @@ namespace Workbunny\WebmanCoroutine\Handlers; use Revolt\EventLoop; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use function Workbunny\WebmanCoroutine\package_installed; @@ -77,6 +78,7 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi { try { $suspension = EventLoop::getSuspension(); + static::_setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { @@ -110,4 +112,23 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi } } } + + /** @inheritdoc */ + public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void + { + if ($suspensionOrSuspensionId instanceof EventLoop\Suspension) { + $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); + $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } else { + /** + * @var EventLoop\Suspension $object + * @var array $info + */ + foreach (static::listSuspensionsWeakMap() as $object => $info) { + if ($info['id'] === $suspensionOrSuspensionId) { + $object->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } + } + } + } } diff --git a/src/Handlers/RippleHandler.php b/src/Handlers/RippleHandler.php index a7849ba..d399821 100644 --- a/src/Handlers/RippleHandler.php +++ b/src/Handlers/RippleHandler.php @@ -7,6 +7,7 @@ namespace Workbunny\WebmanCoroutine\Handlers; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use function Workbunny\WebmanCoroutine\package_installed; @@ -19,7 +20,7 @@ class RippleHandler implements HandlerInterface use HandlerMethods; /** - * @var EventLoop\Suspension[] + * @var \Revolt\EventLoop\Suspension[] */ protected static array $_suspensions = []; @@ -76,11 +77,11 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi { try { $suspension = \Co\getSuspension(); + static::_setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { $suspension->suspend(); - return; } } @@ -109,4 +110,23 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi } } } + + /** @inheritdoc */ + public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void + { + if ($suspensionOrSuspensionId instanceof \Revolt\EventLoop\Suspension) { + $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); + $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } else { + /** + * @var \Revolt\EventLoop\Suspension $object + * @var array $info + */ + foreach (static::listSuspensionsWeakMap() as $object => $info) { + if ($info['id'] === $suspensionOrSuspensionId) { + $object->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } + } + } + } } diff --git a/src/Handlers/SwooleHandler.php b/src/Handlers/SwooleHandler.php index 955fb2c..4224ea2 100644 --- a/src/Handlers/SwooleHandler.php +++ b/src/Handlers/SwooleHandler.php @@ -7,10 +7,13 @@ namespace Workbunny\WebmanCoroutine\Handlers; +use stdClass; use Swoole\Coroutine; use Swoole\Event; use Swoole\Runtime; use Swoole\Timer; +use Throwable; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; /** @@ -74,10 +77,20 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi { try { $suspension = Coroutine::getCid(); + /** + * @var object{suspension:int, throw:Throwable} $object + */ + $object = new stdClass(); + $object->suspension = $suspension; + $object->throw = null; + static::_setSuspensionsWeakMap($object, $suspension, $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { Coroutine::suspend(); + if ($object->throw instanceof Throwable) { + throw $object->throw; + } return; } @@ -105,10 +118,35 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi }); } Coroutine::suspend(); + if ($object->throw instanceof Throwable) { + throw $object->throw; + } } finally { if ($event) { unset(static::$_suspensions[$event]); } + unset($object); + } + } + + /** @inheritdoc */ + public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void + { + if ($suspensionOrSuspensionId instanceof stdClass) { + $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); + $suspensionOrSuspensionId->throw = new KilledException($message, $exitCode, $info['event'] ?? null); + Coroutine::resume($info['id']); + } else { + /** + * @var object{suspension:int, throw:Throwable} $object + * @var array $info + */ + foreach (static::listSuspensionsWeakMap() as $object => $info) { + if ($info['id'] === $suspensionOrSuspensionId) { + $object->throw = new KilledException($message, $exitCode, $info['event'] ?? null); + Coroutine::resume($info['id']); + } + } } } } diff --git a/src/Handlers/SwowHandler.php b/src/Handlers/SwowHandler.php index 649bafb..64e0a4e 100644 --- a/src/Handlers/SwowHandler.php +++ b/src/Handlers/SwowHandler.php @@ -8,6 +8,7 @@ namespace Workbunny\WebmanCoroutine\Handlers; use Swow\Coroutine; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; /** @@ -74,6 +75,7 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi { try { $suspension = Coroutine::getCurrent(); + static::_setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { @@ -84,9 +86,9 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi } Coroutine::run(function () use ($timeout, $suspension): void { usleep((int) ($timeout * 1000 * 1000)); - \call_user_func(function ($suspension) { - if ($suspension?->isAvailable()) { - $suspension?->resume(); + \call_user_func(function (Coroutine $suspension) { + if ($suspension->isAvailable()) { + $suspension->resume(); } }, $suspension); }); @@ -97,4 +99,23 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi } } } + + /** @inheritdoc */ + public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void + { + if ($suspensionOrSuspensionId instanceof Coroutine) { + $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); + $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } else { + /** + * @var Coroutine $object + * @var array $info + */ + foreach (static::listSuspensionsWeakMap() as $object => $info) { + if ($info['id'] === $suspensionOrSuspensionId) { + $object->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } + } + } + } } diff --git a/src/Utils/Coroutine/Coroutine.php b/src/Utils/Coroutine/Coroutine.php index 550e36d..f303a0d 100644 --- a/src/Utils/Coroutine/Coroutine.php +++ b/src/Utils/Coroutine/Coroutine.php @@ -8,6 +8,8 @@ namespace Workbunny\WebmanCoroutine\Utils\Coroutine; use Closure; +use WeakMap; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Factory; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\CoroutineInterface; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\DefaultCoroutine; @@ -25,6 +27,11 @@ class Coroutine { use RegisterMethods; + /** + * @var WeakMap|null + */ + protected static ?WeakMap $_coroutinesWeakMap = null; + /** * @var CoroutineInterface|null */ @@ -51,7 +58,14 @@ class Coroutine */ public function __construct(Closure $func) { + // 创建协程 $this->_interface = new (self::$_handlers[Factory::getCurrentEventLoop()] ?? DefaultCoroutine::class)($func); + // 注册协程 + static::$_coroutinesWeakMap = static::$_coroutinesWeakMap ?: new WeakMap(); + static::$_coroutinesWeakMap->offsetSet($this->_interface, [ + 'id' => $this->_interface->id(), + 'startTime' => microtime(true), + ]); } /** @@ -74,6 +88,41 @@ public static function unregisterExecute(string $key): bool return true; } + /** + * 返回所有协程 + * + * @return WeakMap [CoroutineInterface, 开始时间戳] + */ + public static function listCoroutinesWeakMap(): WeakMap + { + return static::$_coroutinesWeakMap ?: new WeakMap(); + } + + /** + * 杀死协程 + * + * @param object|int|string $coroutineOrCoroutineId + * @param string $message + * @param int $exitCode + * @return void + */ + public static function kill(object|int|string $coroutineOrCoroutineId, string $message = 'kill', int $exitCode = 0): void + { + if ($coroutineOrCoroutineId instanceof CoroutineInterface) { + $coroutineOrCoroutineId->kill(new KilledException($message, $exitCode)); + } else { + /** + * @var CoroutineInterface $object + * @var array $info + */ + foreach (static::listCoroutinesWeakMap() as $object => $info) { + if ($info['id'] === $coroutineOrCoroutineId) { + $object->kill(new KilledException($message, $exitCode)); + } + } + } + } + /** * 代理调用WaitGroupInterface方法 * diff --git a/src/Utils/Coroutine/Handlers/CoroutineInterface.php b/src/Utils/Coroutine/Handlers/CoroutineInterface.php index e73b82b..4a735e2 100644 --- a/src/Utils/Coroutine/Handlers/CoroutineInterface.php +++ b/src/Utils/Coroutine/Handlers/CoroutineInterface.php @@ -7,6 +7,8 @@ namespace Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers; +use Throwable; + interface CoroutineInterface { /** @@ -34,4 +36,12 @@ public function origin(): mixed; * @return string|int|null */ public function id(): string|int|null; + + /** + * 杀死协程 + * + * @param Throwable $throwable 抛出的异常 + * @return void + */ + public function kill(Throwable $throwable): void; } diff --git a/src/Utils/Coroutine/Handlers/DefaultCoroutine.php b/src/Utils/Coroutine/Handlers/DefaultCoroutine.php index ee38ba1..0e80e86 100644 --- a/src/Utils/Coroutine/Handlers/DefaultCoroutine.php +++ b/src/Utils/Coroutine/Handlers/DefaultCoroutine.php @@ -7,6 +7,8 @@ namespace Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers; +use Throwable; + class DefaultCoroutine implements CoroutineInterface { /** @@ -37,4 +39,9 @@ public function id(): ?string { return $this->id; } + + /** @inheritdoc */ + public function kill(Throwable $throwable): void + { + } } diff --git a/src/Utils/Coroutine/Handlers/RevoltCoroutine.php b/src/Utils/Coroutine/Handlers/RevoltCoroutine.php index 129fba2..4bda9bf 100644 --- a/src/Utils/Coroutine/Handlers/RevoltCoroutine.php +++ b/src/Utils/Coroutine/Handlers/RevoltCoroutine.php @@ -51,4 +51,10 @@ public function id(): ?string { return $this->_suspension ? spl_object_hash($this->_suspension) : null; } + + /** @inheritdoc */ + public function kill(\Throwable $throwable): void + { + $this->_suspension?->throw($throwable); + } } diff --git a/src/Utils/Coroutine/Handlers/RippleCoroutine.php b/src/Utils/Coroutine/Handlers/RippleCoroutine.php index 571d6b5..09c8e20 100644 --- a/src/Utils/Coroutine/Handlers/RippleCoroutine.php +++ b/src/Utils/Coroutine/Handlers/RippleCoroutine.php @@ -18,9 +18,7 @@ class RippleCoroutine implements CoroutineInterface */ protected ?Suspension $_suspension = null; - /** @inheritdoc - * @param \Closure $func - */ + /** @inheritdoc */ public function __construct(Closure $func) { $this->_suspension = $this->_getSuspension(); @@ -58,6 +56,12 @@ public function id(): ?string return $this->_suspension ? spl_object_hash($this->_suspension) : null; } + /** @inheritdoc */ + public function kill(\Throwable $throwable): void + { + $this->_suspension?->throw($throwable); + } + /** * @codeCoverageIgnore 用于测试mock,忽略覆盖 * diff --git a/src/Utils/Coroutine/Handlers/SwooleCoroutine.php b/src/Utils/Coroutine/Handlers/SwooleCoroutine.php index e0c3999..4cdcb2f 100644 --- a/src/Utils/Coroutine/Handlers/SwooleCoroutine.php +++ b/src/Utils/Coroutine/Handlers/SwooleCoroutine.php @@ -8,6 +8,7 @@ namespace Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers; use Swoole\Coroutine; +use Throwable; use Workbunny\WebmanCoroutine\Handlers\SwooleHandler; class SwooleCoroutine implements CoroutineInterface @@ -17,9 +18,7 @@ class SwooleCoroutine implements CoroutineInterface */ protected ?int $_id = null; - /** @inheritdoc - * @param \Closure $func - */ + /** @inheritdoc */ public function __construct(\Closure $func) { while (1) { @@ -56,4 +55,13 @@ public function id(): ?int { return $this->_id; } + + /** @inheritdoc */ + public function kill(Throwable $throwable): void + { + if ($this->id()) { + // todo swoole目前没有throw方法,暂时只能静默退出 + Coroutine::cancel($this->id()); + } + } } diff --git a/src/Utils/Coroutine/Handlers/SwowCoroutine.php b/src/Utils/Coroutine/Handlers/SwowCoroutine.php index dbeb2c6..195f1b5 100644 --- a/src/Utils/Coroutine/Handlers/SwowCoroutine.php +++ b/src/Utils/Coroutine/Handlers/SwowCoroutine.php @@ -45,4 +45,10 @@ public function id(): ?int { return $this->_coroutine?->getId(); } + + /** @inheritdoc */ + public function kill(\Throwable $throwable): void + { + $this->_coroutine?->throw($throwable); + } } diff --git a/src/helpers.php b/src/helpers.php index 1abe7f7..fe59f2d 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -39,12 +39,7 @@ function event_loop(?string $expectEventLoopClass = null): string */ function wait_for(?\Closure $action, float|int $timeout = -1, null|string $event = null): void { - if (($handler = Factory::getCurrentHandler()) === null) { - Factory::init(null); - /** @var HandlerInterface $handler */ - $handler = Factory::getCurrentHandler(); - } - $handler::waitFor($action, $timeout, $event); + Factory::waitFor($action, $timeout, $event); } /** @@ -57,12 +52,7 @@ function wait_for(?\Closure $action, float|int $timeout = -1, null|string $event */ function sleep(float|int $timeout = 0, null|string $event = null): void { - if (($handler = Factory::getCurrentHandler()) === null) { - Factory::init(null); - /** @var HandlerInterface $handler */ - $handler = Factory::getCurrentHandler(); - } - $handler::sleep($timeout, $event); + Factory::sleep($timeout, $event); } /** @@ -74,12 +64,7 @@ function sleep(float|int $timeout = 0, null|string $event = null): void */ function wakeup(string $event): void { - if (($handler = Factory::getCurrentHandler()) === null) { - Factory::init(null); - /** @var HandlerInterface $handler */ - $handler = Factory::getCurrentHandler(); - } - $handler::wakeup($event); + Factory::wakeup($event); } /** diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 0c98a97..772fa83 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -87,4 +87,10 @@ public function testInit() $handlers = $property->getValue(); $this->assertTrue($handlers[Factory::getCurrentEventLoop()] === Factory::getCurrentHandler()); } + + public function testCallStaticNonExistingMethod() + { + $this->expectException(\BadMethodCallException::class); + Factory::nonExistingMethod('nonExistingMethod', []); + } } diff --git a/tests/mock/TestByFailHandler.php b/tests/mock/TestByFailHandler.php deleted file mode 100644 index fc48dc0..0000000 --- a/tests/mock/TestByFailHandler.php +++ /dev/null @@ -1,35 +0,0 @@ -shouldReceive('suspend')->andReturnNull(); From f53354afa856c91fd8a00e05c1d5e0f941071030 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 8 Nov 2024 18:16:37 +0800 Subject: [PATCH 2/6] feat Factory::kill support --- src/Handlers/HandlerInterface.php | 6 +- src/Handlers/HandlerMethods.php | 9 +- src/Handlers/RevoltHandler.php | 9 +- src/Handlers/RippleHandler.php | 9 +- src/Handlers/SwooleHandler.php | 11 +- src/Handlers/SwowHandler.php | 9 +- src/Utils/Coroutine/Coroutine.php | 6 +- tests/HandlersCase/DefaultHandlerTest.php | 8 ++ tests/HandlersCase/RevoltHandlerTest.php | 30 ++++ tests/HandlersCase/RippleHandlerTest.php | 30 ++++ .../RippleWorkerman5HandlerTest.php | 133 +++++++++++++++++- tests/HandlersCase/SwooleHandlerTest.php | 37 +++++ tests/HandlersCase/SwowHandlerTest.php | 30 ++++ .../SwowWorkerman5HandlerTest.php | 30 ++++ 14 files changed, 323 insertions(+), 34 deletions(-) diff --git a/src/Handlers/HandlerInterface.php b/src/Handlers/HandlerInterface.php index 394eb94..496fb70 100644 --- a/src/Handlers/HandlerInterface.php +++ b/src/Handlers/HandlerInterface.php @@ -72,8 +72,8 @@ public static function kill(object|int|string $suspensionOrSuspensionId, string /** * 获取所有挂起的对象 * - * @return WeakMap|null - * @link HandlerMethods::listSuspensionsWeakMap() + * @return WeakMap + * @link HandlerMethods::getSuspensionsWeakMap() */ - public static function listSuspensionsWeakMap(): ?WeakMap; + public static function getSuspensionsWeakMap(): WeakMap; } diff --git a/src/Handlers/HandlerMethods.php b/src/Handlers/HandlerMethods.php index e7b54dc..21ce1d3 100644 --- a/src/Handlers/HandlerMethods.php +++ b/src/Handlers/HandlerMethods.php @@ -19,9 +19,9 @@ trait HandlerMethods * * @return WeakMap */ - public static function listSuspensionsWeakMap(): WeakMap + public static function getSuspensionsWeakMap(): WeakMap { - return static::$_suspensionsWeakMap ?: new WeakMap(); + return self::$_suspensionsWeakMap = static::$_suspensionsWeakMap ?: new WeakMap(); } /** @@ -33,10 +33,9 @@ public static function listSuspensionsWeakMap(): WeakMap * @param float|int $startTime * @return void */ - protected static function _setSuspensionsWeakMap(object $object, string|int $id, ?string $event, float|int $startTime): void + public static function setSuspensionsWeakMap(object $object, string|int $id, ?string $event, float|int $startTime): void { - static::$_suspensionsWeakMap = static::$_suspensionsWeakMap ?: new WeakMap; - static::$_suspensionsWeakMap->offsetSet($object, [ + static::getSuspensionsWeakMap()->offsetSet($object, [ 'id' => $id, 'event' => $event, 'startTime' => $startTime diff --git a/src/Handlers/RevoltHandler.php b/src/Handlers/RevoltHandler.php index b57a290..6a21e4b 100644 --- a/src/Handlers/RevoltHandler.php +++ b/src/Handlers/RevoltHandler.php @@ -78,7 +78,7 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi { try { $suspension = EventLoop::getSuspension(); - static::_setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); + static::setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { @@ -117,14 +117,15 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void { if ($suspensionOrSuspensionId instanceof EventLoop\Suspension) { - $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); - $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + if ($info = static::getSuspensionsWeakMap()->offsetGet($suspensionOrSuspensionId)) { + $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } } else { /** * @var EventLoop\Suspension $object * @var array $info */ - foreach (static::listSuspensionsWeakMap() as $object => $info) { + foreach (static::getSuspensionsWeakMap() as $object => $info) { if ($info['id'] === $suspensionOrSuspensionId) { $object->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); } diff --git a/src/Handlers/RippleHandler.php b/src/Handlers/RippleHandler.php index d399821..f375f49 100644 --- a/src/Handlers/RippleHandler.php +++ b/src/Handlers/RippleHandler.php @@ -77,7 +77,7 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi { try { $suspension = \Co\getSuspension(); - static::_setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); + static::setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { @@ -115,14 +115,15 @@ public static function sleep(int|float $timeout = 0, ?string $event = null): voi public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void { if ($suspensionOrSuspensionId instanceof \Revolt\EventLoop\Suspension) { - $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); - $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + if ($info = static::getSuspensionsWeakMap()->offsetGet($suspensionOrSuspensionId)) { + $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } } else { /** * @var \Revolt\EventLoop\Suspension $object * @var array $info */ - foreach (static::listSuspensionsWeakMap() as $object => $info) { + foreach (static::getSuspensionsWeakMap() as $object => $info) { if ($info['id'] === $suspensionOrSuspensionId) { $object->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); } diff --git a/src/Handlers/SwooleHandler.php b/src/Handlers/SwooleHandler.php index 4224ea2..03033b0 100644 --- a/src/Handlers/SwooleHandler.php +++ b/src/Handlers/SwooleHandler.php @@ -83,7 +83,7 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi $object = new stdClass(); $object->suspension = $suspension; $object->throw = null; - static::_setSuspensionsWeakMap($object, $suspension, $event, microtime(true)); + static::setSuspensionsWeakMap($object, $suspension, $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { @@ -133,15 +133,16 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void { if ($suspensionOrSuspensionId instanceof stdClass) { - $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); - $suspensionOrSuspensionId->throw = new KilledException($message, $exitCode, $info['event'] ?? null); - Coroutine::resume($info['id']); + if ($info = static::getSuspensionsWeakMap()->offsetGet($suspensionOrSuspensionId)) { + $suspensionOrSuspensionId->throw = new KilledException($message, $exitCode, $info['event'] ?? null); + Coroutine::resume($info['id']); + } } else { /** * @var object{suspension:int, throw:Throwable} $object * @var array $info */ - foreach (static::listSuspensionsWeakMap() as $object => $info) { + foreach (static::getSuspensionsWeakMap() as $object => $info) { if ($info['id'] === $suspensionOrSuspensionId) { $object->throw = new KilledException($message, $exitCode, $info['event'] ?? null); Coroutine::resume($info['id']); diff --git a/src/Handlers/SwowHandler.php b/src/Handlers/SwowHandler.php index 64e0a4e..4c48581 100644 --- a/src/Handlers/SwowHandler.php +++ b/src/Handlers/SwowHandler.php @@ -75,7 +75,7 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi { try { $suspension = Coroutine::getCurrent(); - static::_setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); + static::setSuspensionsWeakMap($suspension, spl_object_hash($suspension), $event, microtime(true)); if ($event) { static::$_suspensions[$event] = $suspension; if ($timeout < 0) { @@ -104,14 +104,15 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi public static function kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0): void { if ($suspensionOrSuspensionId instanceof Coroutine) { - $info = static::$_suspensionsWeakMap->offsetGet($suspensionOrSuspensionId); - $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + if ($info = static::getSuspensionsWeakMap()->offsetGet($suspensionOrSuspensionId)) { + $suspensionOrSuspensionId->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); + } } else { /** * @var Coroutine $object * @var array $info */ - foreach (static::listSuspensionsWeakMap() as $object => $info) { + foreach (static::getSuspensionsWeakMap() as $object => $info) { if ($info['id'] === $suspensionOrSuspensionId) { $object->throw(new KilledException($message, $exitCode, $info['event'] ?? null)); } diff --git a/src/Utils/Coroutine/Coroutine.php b/src/Utils/Coroutine/Coroutine.php index f303a0d..6a7c40e 100644 --- a/src/Utils/Coroutine/Coroutine.php +++ b/src/Utils/Coroutine/Coroutine.php @@ -93,9 +93,9 @@ public static function unregisterExecute(string $key): bool * * @return WeakMap [CoroutineInterface, 开始时间戳] */ - public static function listCoroutinesWeakMap(): WeakMap + public static function getCoroutinesWeakMap(): WeakMap { - return static::$_coroutinesWeakMap ?: new WeakMap(); + return static::$_coroutinesWeakMap = static::$_coroutinesWeakMap ?: new WeakMap(); } /** @@ -115,7 +115,7 @@ public static function kill(object|int|string $coroutineOrCoroutineId, string $m * @var CoroutineInterface $object * @var array $info */ - foreach (static::listCoroutinesWeakMap() as $object => $info) { + foreach (static::getCoroutinesWeakMap() as $object => $info) { if ($info['id'] === $coroutineOrCoroutineId) { $object->kill(new KilledException($message, $exitCode)); } diff --git a/tests/HandlersCase/DefaultHandlerTest.php b/tests/HandlersCase/DefaultHandlerTest.php index 457a3c1..653c5d1 100644 --- a/tests/HandlersCase/DefaultHandlerTest.php +++ b/tests/HandlersCase/DefaultHandlerTest.php @@ -93,4 +93,12 @@ public function testWakeup() DefaultHandler::wakeup(__METHOD__); $this->assertTrue(true); } + + public function testKill() + { + DefaultHandler::kill(new \stdClass()); + $this->assertTrue(true); + DefaultHandler::kill(__METHOD__); + $this->assertTrue(true); + } } diff --git a/tests/HandlersCase/RevoltHandlerTest.php b/tests/HandlersCase/RevoltHandlerTest.php index 87a4566..d886532 100644 --- a/tests/HandlersCase/RevoltHandlerTest.php +++ b/tests/HandlersCase/RevoltHandlerTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use Workbunny\WebmanCoroutine\Handlers\RevoltHandler; @@ -140,4 +141,33 @@ public function testWakeup() RevoltHandler::wakeup(__METHOD__); $this->assertTrue(true); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + // mock + $suspensionMock = Mockery::mock('alias:\Revolt\EventLoop\Suspension'); + $suspensionMock->shouldReceive('throw')->twice()->andReturnUsing(function ($exception) { + $this->assertInstanceOf(KilledException::class, $exception); + }); + $this->assertEquals(0, RevoltHandler::getSuspensionsWeakMap()->count()); + RevoltHandler::setSuspensionsWeakMap($suspensionMock, spl_object_hash($suspensionMock), __METHOD__, microtime(true)); + $this->assertEquals(1, RevoltHandler::getSuspensionsWeakMap()->count()); + + // object + foreach (RevoltHandler::getSuspensionsWeakMap() as $object => $info) { + RevoltHandler::kill($object, __METHOD__, -1); + } + $this->assertTrue(true); + + // string + foreach (RevoltHandler::getSuspensionsWeakMap() as $info) { + RevoltHandler::kill($info['id'], __METHOD__, -1); + } + $this->assertTrue(true); + } } diff --git a/tests/HandlersCase/RippleHandlerTest.php b/tests/HandlersCase/RippleHandlerTest.php index 0b3db85..fbe9914 100644 --- a/tests/HandlersCase/RippleHandlerTest.php +++ b/tests/HandlersCase/RippleHandlerTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use Workbunny\WebmanCoroutine\Handlers\RippleHandler; @@ -116,4 +117,33 @@ public function testWakeup() RippleHandler::wakeup(__METHOD__); $this->assertTrue(true); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + // mock + $suspensionMock = Mockery::mock('alias:\Revolt\EventLoop\Suspension'); + $suspensionMock->shouldReceive('throw')->twice()->andReturnUsing(function ($exception) { + $this->assertInstanceOf(KilledException::class, $exception); + }); + $this->assertEquals(0, RippleHandler::getSuspensionsWeakMap()->count()); + RippleHandler::setSuspensionsWeakMap($suspensionMock, spl_object_hash($suspensionMock), __METHOD__, microtime(true)); + $this->assertEquals(1, RippleHandler::getSuspensionsWeakMap()->count()); + + // object + foreach (RippleHandler::getSuspensionsWeakMap() as $object => $info) { + RippleHandler::kill($object, __METHOD__, -1); + } + $this->assertTrue(true); + + // string + foreach (RippleHandler::getSuspensionsWeakMap() as $info) { + RippleHandler::kill($info['id'], __METHOD__, -1); + } + $this->assertTrue(true); + } } diff --git a/tests/HandlersCase/RippleWorkerman5HandlerTest.php b/tests/HandlersCase/RippleWorkerman5HandlerTest.php index abecfec..024dcd5 100644 --- a/tests/HandlersCase/RippleWorkerman5HandlerTest.php +++ b/tests/HandlersCase/RippleWorkerman5HandlerTest.php @@ -5,21 +5,142 @@ namespace Workbunny\Tests\HandlersCase; use Mockery; -use Workbunny\WebmanCoroutine\Handlers\RippleWorkerman5Handler; +use Workbunny\WebmanCoroutine\Handlers\RippleWorkerman5Handler as RippleHandler; class RippleWorkerman5HandlerTest extends RippleHandlerTest { + protected function setUp(): void + { + parent::setUp(); + require_once __DIR__ . '/../mock/ripple.php'; + } + + protected function tearDown(): void + { + parent::tearDown(); + Mockery::close(); + } + public function testIsAvailable() { - $rippleHandlerMock = Mockery::mock(RippleWorkerman5Handler::class . '[_getWorkerVersion]'); - $rippleHandlerMock->shouldAllowMockingProtectedMethods(); - $rippleHandlerMock->shouldReceive('_getWorkerVersion')->andReturn('4.0.0'); - $this->assertFalse($rippleHandlerMock::isAvailable()); + RippleHandler::isAvailable(); + $this->assertTrue(true); } public function testInitEnv() { - RippleWorkerman5Handler::initEnv(); + RippleHandler::initEnv(); + $this->assertTrue(true); + } + + public function testWaitFor() + { + // success + $return = false; + RippleHandler::waitFor(function () use (&$return) { + return ($return = true); + }); + $this->assertTrue($return); + + // success with sleep + $return = false; + RippleHandler::waitFor(function () use (&$return) { + sleep(1); + + return $return = true; + }); + $this->assertTrue($return); + + // success with event + $return = false; + RippleHandler::waitFor(function () use (&$return) { + sleep(1); + + $return = true; + RippleHandler::wakeup(__METHOD__); + + return $return; + }, event: __METHOD__); + $this->assertTrue($return); + + // timeout in loop + $this->expectException(TimeoutException::class); + RippleHandler::waitFor(function () { + return false; + }, 1); + $this->assertTrue(true); + + // timeout not loop + $this->expectException(TimeoutException::class); + // 模拟超时 + RippleHandler::waitFor(function () { + sleep(2); + + return false; + }, 0.1); + $this->assertTrue(true); + } + + public function testSleep() + { + RippleHandler::sleep(); + $this->assertTrue(true); + + RippleHandler::sleep(0.01); + $this->assertTrue(true); + + RippleHandler::sleep(0.009); + $this->assertTrue(true); + + RippleHandler::sleep(event: __METHOD__); + $this->assertTrue(true); + + RippleHandler::sleep(-1, event: __METHOD__); + $this->assertTrue(true); + } + + public function testWakeup() + { + RippleHandler::wakeup(__METHOD__); + $this->assertTrue(true); + + $suspensionMock = Mockery::mock('alias:\Revolt\EventLoop\Suspension'); + $suspensionMock->shouldReceive('resume')->andReturnNull(); + $reflection = new \ReflectionClass(RippleHandler::class); + $property = $reflection->getProperty('_suspensions'); + $property->setAccessible(true); + $property->setValue(null, [__METHOD__ => $suspensionMock]); + + RippleHandler::wakeup(__METHOD__); + $this->assertTrue(true); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + // mock + $suspensionMock = Mockery::mock('alias:\Revolt\EventLoop\Suspension'); + $suspensionMock->shouldReceive('throw')->twice()->andReturnUsing(function ($exception) { + $this->assertInstanceOf(KilledException::class, $exception); + }); + $this->assertEquals(0, RippleHandler::getSuspensionsWeakMap()->count()); + RippleHandler::setSuspensionsWeakMap($suspensionMock, spl_object_hash($suspensionMock), __METHOD__, microtime(true)); + $this->assertEquals(1, RippleHandler::getSuspensionsWeakMap()->count()); + + // object + foreach (RippleHandler::getSuspensionsWeakMap() as $object => $info) { + RippleHandler::kill($object, __METHOD__, -1); + } + $this->assertTrue(true); + + // string + foreach (RippleHandler::getSuspensionsWeakMap() as $info) { + RippleHandler::kill($info['id'], __METHOD__, -1); + } $this->assertTrue(true); } } diff --git a/tests/HandlersCase/SwooleHandlerTest.php b/tests/HandlersCase/SwooleHandlerTest.php index d9ae526..f5e7e3a 100644 --- a/tests/HandlersCase/SwooleHandlerTest.php +++ b/tests/HandlersCase/SwooleHandlerTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use Workbunny\WebmanCoroutine\Handlers\SwooleHandler; @@ -141,4 +142,40 @@ public function testWakeup() SwooleHandler::wakeup(__METHOD__); $this->assertTrue(true); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + // mock + $object = new \stdClass(); + $object->throw = null; + $object->suspension = 1; + + $suspensionMock = Mockery::mock('alias:\Swoole\Coroutine'); + $suspensionMock->shouldReceive('resume')->twice()->andReturnUsing(function ($id) use ($object) { + $this->assertEquals($object->suspension, $id); + }); + + $this->assertEquals(0, SwooleHandler::getSuspensionsWeakMap()->count()); + SwooleHandler::setSuspensionsWeakMap($object, $object->suspension, __METHOD__, microtime(true)); + $this->assertEquals(1, SwooleHandler::getSuspensionsWeakMap()->count()); + + // object + foreach (SwooleHandler::getSuspensionsWeakMap() as $object => $info) { + SwooleHandler::kill($object, __METHOD__, -1); + } + $this->assertInstanceOf(KilledException::class, $object->throw); + + // string + $object->throw = null; + + foreach (SwooleHandler::getSuspensionsWeakMap() as $info) { + SwooleHandler::kill($info['id'], __METHOD__, -1); + } + $this->assertTrue(true); + } } diff --git a/tests/HandlersCase/SwowHandlerTest.php b/tests/HandlersCase/SwowHandlerTest.php index 7d934da..c36a4fc 100644 --- a/tests/HandlersCase/SwowHandlerTest.php +++ b/tests/HandlersCase/SwowHandlerTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use Workbunny\WebmanCoroutine\Handlers\SwowHandler; @@ -124,4 +125,33 @@ public function testWakeup() SwowHandler::wakeup(__METHOD__); $this->assertTrue(true); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + // mock + $suspensionMock = Mockery::mock('alias:\Swow\Coroutine'); + $suspensionMock->shouldReceive('throw')->twice()->andReturnUsing(function ($exception) { + $this->assertInstanceOf(KilledException::class, $exception); + }); + $this->assertEquals(0, SwowHandler::getSuspensionsWeakMap()->count()); + SwowHandler::setSuspensionsWeakMap($suspensionMock, spl_object_hash($suspensionMock), __METHOD__, microtime(true)); + $this->assertEquals(1, SwowHandler::getSuspensionsWeakMap()->count()); + + // object + foreach (SwowHandler::getSuspensionsWeakMap() as $object => $info) { + SwowHandler::kill($object, __METHOD__, -1); + } + $this->assertTrue(true); + + // string + foreach (SwowHandler::getSuspensionsWeakMap() as $info) { + SwowHandler::kill($info['id'], __METHOD__, -1); + } + $this->assertTrue(true); + } } diff --git a/tests/HandlersCase/SwowWorkerman5HandlerTest.php b/tests/HandlersCase/SwowWorkerman5HandlerTest.php index 05eadf7..8327913 100644 --- a/tests/HandlersCase/SwowWorkerman5HandlerTest.php +++ b/tests/HandlersCase/SwowWorkerman5HandlerTest.php @@ -5,6 +5,7 @@ namespace Workbunny\Tests\HandlersCase; use Mockery; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use Workbunny\WebmanCoroutine\Handlers\SwowWorkerman5Handler as SwowHandler; @@ -123,4 +124,33 @@ public function testWakeup() SwowHandler::wakeup(__METHOD__); $this->assertTrue(true); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + // mock + $suspensionMock = Mockery::mock('alias:\Swow\Coroutine'); + $suspensionMock->shouldReceive('throw')->twice()->andReturnUsing(function ($exception) { + $this->assertInstanceOf(KilledException::class, $exception); + }); + $this->assertEquals(0, SwowHandler::getSuspensionsWeakMap()->count()); + SwowHandler::setSuspensionsWeakMap($suspensionMock, spl_object_hash($suspensionMock), __METHOD__, microtime(true)); + $this->assertEquals(1, SwowHandler::getSuspensionsWeakMap()->count()); + + // object + foreach (SwowHandler::getSuspensionsWeakMap() as $object => $info) { + SwowHandler::kill($object, __METHOD__, -1); + } + $this->assertTrue(true); + + // string + foreach (SwowHandler::getSuspensionsWeakMap() as $info) { + SwowHandler::kill($info['id'], __METHOD__, -1); + } + $this->assertTrue(true); + } } From 314b094fb99f7fb2c9d7a2da8cc6e51ed4e6e678 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Sat, 9 Nov 2024 03:20:56 +0800 Subject: [PATCH 3/6] feat Coroutine::kill support --- src/Utils/Coroutine/Coroutine.php | 13 +++- .../RippleWorkerman5HandlerTest.php | 2 + tests/UtilsCase/Coroutine/CoroutineTest.php | 21 +++++++ .../Coroutine/DefaultCoroutineTest.php | 11 ++++ .../Coroutine/RevoltCoroutineTest.php | 62 ++++++++++++++----- .../Coroutine/RippleCoroutineTest.php | 43 +++++++++++++ .../Coroutine/SwooleCoroutineTest.php | 15 +++++ .../UtilsCase/Coroutine/SwowCoroutineTest.php | 15 +++++ tests/mock/ripple.php | 5 ++ 9 files changed, 169 insertions(+), 18 deletions(-) diff --git a/src/Utils/Coroutine/Coroutine.php b/src/Utils/Coroutine/Coroutine.php index 6a7c40e..2a771dc 100644 --- a/src/Utils/Coroutine/Coroutine.php +++ b/src/Utils/Coroutine/Coroutine.php @@ -22,6 +22,7 @@ /** * @method mixed origin() * @method string|int id() + * @method void kill(\Throwable $throwable) */ class Coroutine { @@ -76,6 +77,14 @@ public function __destruct() $this->_interface = null; } + /** + * @return CoroutineInterface|null + */ + public function getCoroutineInterface(): ?CoroutineInterface + { + return $this->_interface; + } + /** @inheritdoc */ public static function registerVerify(mixed $value): false|string { @@ -101,12 +110,12 @@ public static function getCoroutinesWeakMap(): WeakMap /** * 杀死协程 * - * @param object|int|string $coroutineOrCoroutineId + * @param object|int|string|null $coroutineOrCoroutineId * @param string $message * @param int $exitCode * @return void */ - public static function kill(object|int|string $coroutineOrCoroutineId, string $message = 'kill', int $exitCode = 0): void + public static function kill(object|int|string|null $coroutineOrCoroutineId, string $message = 'kill', int $exitCode = 0): void { if ($coroutineOrCoroutineId instanceof CoroutineInterface) { $coroutineOrCoroutineId->kill(new KilledException($message, $exitCode)); diff --git a/tests/HandlersCase/RippleWorkerman5HandlerTest.php b/tests/HandlersCase/RippleWorkerman5HandlerTest.php index 024dcd5..4774064 100644 --- a/tests/HandlersCase/RippleWorkerman5HandlerTest.php +++ b/tests/HandlersCase/RippleWorkerman5HandlerTest.php @@ -5,6 +5,8 @@ namespace Workbunny\Tests\HandlersCase; use Mockery; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; +use Workbunny\WebmanCoroutine\Exceptions\TimeoutException; use Workbunny\WebmanCoroutine\Handlers\RippleWorkerman5Handler as RippleHandler; class RippleWorkerman5HandlerTest extends RippleHandlerTest diff --git a/tests/UtilsCase/Coroutine/CoroutineTest.php b/tests/UtilsCase/Coroutine/CoroutineTest.php index d8723d7..1b38c86 100644 --- a/tests/UtilsCase/Coroutine/CoroutineTest.php +++ b/tests/UtilsCase/Coroutine/CoroutineTest.php @@ -8,6 +8,7 @@ use Workbunny\Tests\TestCase; use Workbunny\WebmanCoroutine\Utils\Coroutine\Coroutine; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\CoroutineInterface; +use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\DefaultCoroutine; class CoroutineTest extends TestCase { @@ -87,4 +88,24 @@ public function testUnregisterExecute() $result = Coroutine::unregisterExecute('some_key'); $this->assertTrue($result); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + $this->assertEquals(0, Coroutine::getCoroutinesWeakMap()->count()); + $coroutine = new Coroutine($func = function () {}); + $this->assertEquals(1, Coroutine::getCoroutinesWeakMap()->count()); + + Coroutine::kill($interface = $coroutine->getCoroutineInterface()); + + $this->assertInstanceOf(DefaultCoroutine::class, $interface); + + // DefaultCoroutine在初始化时就执行,所以id为null,这里通过null来进行kill测试 + Coroutine::kill(null); + $this->assertTrue(true); + } } diff --git a/tests/UtilsCase/Coroutine/DefaultCoroutineTest.php b/tests/UtilsCase/Coroutine/DefaultCoroutineTest.php index 870aba4..024f414 100644 --- a/tests/UtilsCase/Coroutine/DefaultCoroutineTest.php +++ b/tests/UtilsCase/Coroutine/DefaultCoroutineTest.php @@ -5,6 +5,7 @@ namespace Workbunny\Tests\UtilsCase\Coroutine; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\DefaultCoroutine; class DefaultCoroutineTest extends TestCase @@ -41,4 +42,14 @@ public function testId() $this->assertNull($coroutine->id()); $this->assertNull($coroutine->origin()); } + + public function testKill() + { + $func = function () { + // 模拟闭包函数的执行 + }; + $coroutine = new DefaultCoroutine($func); + $coroutine->kill(new KilledException()); + $this->assertTrue(true); + } } diff --git a/tests/UtilsCase/Coroutine/RevoltCoroutineTest.php b/tests/UtilsCase/Coroutine/RevoltCoroutineTest.php index f888e6c..b1403fa 100644 --- a/tests/UtilsCase/Coroutine/RevoltCoroutineTest.php +++ b/tests/UtilsCase/Coroutine/RevoltCoroutineTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\RevoltCoroutine; class RevoltCoroutineTest extends TestCase @@ -28,12 +29,12 @@ public function testConstruct() // mock $callback = null; $suspensionMock = Mockery::mock('alias:Revolt\EventLoop\Suspension'); - $suspensionMock->shouldReceive('resume')->andReturnNull(); - $suspensionMock->shouldReceive('suspend')->andReturnNull(); + $suspensionMock->shouldReceive('resume')->once()->andReturnNull(); + $suspensionMock->shouldReceive('suspend')->once()->andReturnNull(); $eventLoopMock = Mockery::mock('alias:Revolt\EventLoop'); - $eventLoopMock->shouldReceive('getSuspension')->andReturn($suspensionMock); - $eventLoopMock->shouldReceive('queue')->andReturnUsing(function ($closure) use (&$callback) { + $eventLoopMock->shouldReceive('getSuspension')->once()->andReturn($suspensionMock); + $eventLoopMock->shouldReceive('queue')->once()->andReturnUsing(function ($closure) use (&$callback) { $closure(); $callback = true; }); @@ -57,12 +58,12 @@ public function testDestruct() // mock $callback = null; $suspensionMock = Mockery::mock('alias:Revolt\EventLoop\Suspension'); - $suspensionMock->shouldReceive('resume')->andReturnNull(); - $suspensionMock->shouldReceive('suspend')->andReturnNull(); + $suspensionMock->shouldReceive('resume')->once()->andReturnNull(); + $suspensionMock->shouldReceive('suspend')->once()->andReturnNull(); $eventLoopMock = Mockery::mock('alias:Revolt\EventLoop'); - $eventLoopMock->shouldReceive('getSuspension')->andReturn($suspensionMock); - $eventLoopMock->shouldReceive('queue')->andReturnUsing(function ($closure) use (&$callback) { + $eventLoopMock->shouldReceive('getSuspension')->once()->andReturn($suspensionMock); + $eventLoopMock->shouldReceive('queue')->once()->andReturnUsing(function ($closure) use (&$callback) { $closure(); $callback = true; }); @@ -83,12 +84,12 @@ public function testOrigin() // mock $callback = null; $suspensionMock = Mockery::mock('alias:Revolt\EventLoop\Suspension'); - $suspensionMock->shouldReceive('resume')->andReturnNull(); - $suspensionMock->shouldReceive('suspend')->andReturnNull(); + $suspensionMock->shouldReceive('resume')->once()->andReturnNull(); + $suspensionMock->shouldReceive('suspend')->once()->andReturnNull(); $eventLoopMock = Mockery::mock('alias:Revolt\EventLoop'); - $eventLoopMock->shouldReceive('getSuspension')->andReturn($suspensionMock); - $eventLoopMock->shouldReceive('queue')->andReturnUsing(function ($closure) use (&$callback) { + $eventLoopMock->shouldReceive('getSuspension')->once()->andReturn($suspensionMock); + $eventLoopMock->shouldReceive('queue')->once()->andReturnUsing(function ($closure) use (&$callback) { $closure(); $callback = true; }); @@ -106,12 +107,12 @@ public function testId() // mock $callback = null; $suspensionMock = Mockery::mock('alias:Revolt\EventLoop\Suspension'); - $suspensionMock->shouldReceive('resume')->andReturnNull(); - $suspensionMock->shouldReceive('suspend')->andReturnNull(); + $suspensionMock->shouldReceive('resume')->once()->andReturnNull(); + $suspensionMock->shouldReceive('suspend')->once()->andReturnNull(); $eventLoopMock = Mockery::mock('alias:Revolt\EventLoop'); - $eventLoopMock->shouldReceive('getSuspension')->andReturn($suspensionMock); - $eventLoopMock->shouldReceive('queue')->andReturnUsing(function ($closure) use (&$callback) { + $eventLoopMock->shouldReceive('getSuspension')->once()->andReturn($suspensionMock); + $eventLoopMock->shouldReceive('queue')->once()->andReturnUsing(function ($closure) use (&$callback) { $closure(); $callback = true; }); @@ -120,4 +121,33 @@ public function testId() $this->assertTrue($callback); $this->assertNull($coroutine->id()); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + $suspensionMock = Mockery::mock('alias:Revolt\EventLoop\Suspension'); + $suspensionMock->shouldReceive('suspend')->once()->andReturnNull(); + $suspensionMock->shouldReceive('throw')->once()->andReturnUsing(function ($throwable) { + $this->assertInstanceOf(KilledException::class, $throwable); + }); + $eventLoopMock = Mockery::mock('alias:Revolt\EventLoop'); + $eventLoopMock->shouldReceive('getSuspension')->once()->andReturn($suspensionMock); + $eventLoopMock->shouldReceive('queue')->once()->andReturnNull(); + $func = function () { + // 模拟闭包函数的执行 + }; + $coroutine = new RevoltCoroutine($func); + $reflection = new \ReflectionClass(RevoltCoroutine::class); + $property = $reflection->getProperty('_suspension'); + $property->setAccessible(true); + $property->setValue($coroutine, $suspensionMock); + + $coroutine->kill(new KilledException()); + $this->assertTrue(true); + + } } diff --git a/tests/UtilsCase/Coroutine/RippleCoroutineTest.php b/tests/UtilsCase/Coroutine/RippleCoroutineTest.php index 7eb6628..d5a4c3a 100644 --- a/tests/UtilsCase/Coroutine/RippleCoroutineTest.php +++ b/tests/UtilsCase/Coroutine/RippleCoroutineTest.php @@ -6,6 +6,8 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; +use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\RevoltCoroutine; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\RippleCoroutine; class RippleCoroutineTest extends TestCase @@ -169,4 +171,45 @@ public function testId() $this->assertNull($coroutine->id()); } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @return void + */ + public function testKill() + { + $suspensionMock = Mockery::mock('alias:Revolt\EventLoop\Suspension'); + $suspensionMock->shouldReceive('throw')->once()->andReturnUsing(function ($throwable) { + $this->assertInstanceOf(KilledException::class, $throwable); + }); + $func = function () { + // 模拟闭包函数的执行 + }; + // 模拟构造 + $coroutine = Mockery::mock(RippleCoroutine::class)->makePartial(); + $coroutine->shouldAllowMockingProtectedMethods()->shouldReceive('_getSuspension') + ->andReturn($suspensionMock); + $coroutine->shouldAllowMockingProtectedMethods()->shouldReceive('_async') + ->andReturnUsing(function ($closure) use ($coroutine, &$result, &$reject) { + // 模拟发生协程执行 + call_user_func($closure, function ($res) use (&$result) { + $result = $res; + }, function ($rj) use (&$reject) { + $reject = $rj; + }); + + // 模拟返回 + return Mockery::mock('alias:Ripple\Promise'); + }); + $coroutine->__construct($func); + + $reflection = new \ReflectionClass(RippleCoroutine::class); + $property = $reflection->getProperty('_suspension'); + $property->setAccessible(true); + $property->setValue($coroutine, $suspensionMock); + + $coroutine->kill(new KilledException()); + $this->assertTrue(true); + } } diff --git a/tests/UtilsCase/Coroutine/SwooleCoroutineTest.php b/tests/UtilsCase/Coroutine/SwooleCoroutineTest.php index cab6cfa..02b4e54 100644 --- a/tests/UtilsCase/Coroutine/SwooleCoroutineTest.php +++ b/tests/UtilsCase/Coroutine/SwooleCoroutineTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Handlers\SwooleHandler; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\SwooleCoroutine; @@ -148,4 +149,18 @@ public function testId() $this->assertNull($coroutine->id()); } + + public function testKill() + { + $suspensionMock = Mockery::mock('alias:Swoole\Coroutine'); + $suspensionMock->shouldReceive('create')->once()->andReturn(123); + $suspensionMock->shouldReceive('cancel')->once()->andReturnUsing(function ($id) { + $this->assertEquals(123, $id); + }); + $func = function () { + // 模拟闭包函数的执行 + }; + (new SwooleCoroutine($func))->kill(new KilledException()); + $this->assertTrue(true); + } } diff --git a/tests/UtilsCase/Coroutine/SwowCoroutineTest.php b/tests/UtilsCase/Coroutine/SwowCoroutineTest.php index f7c4ac4..a2f7b7d 100644 --- a/tests/UtilsCase/Coroutine/SwowCoroutineTest.php +++ b/tests/UtilsCase/Coroutine/SwowCoroutineTest.php @@ -6,6 +6,7 @@ use Mockery; use Workbunny\Tests\TestCase; +use Workbunny\WebmanCoroutine\Exceptions\KilledException; use Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\SwowCoroutine; class SwowCoroutineTest extends TestCase @@ -132,4 +133,18 @@ public function testId() $this->assertNull($coroutine->id()); } + + public function testKill() + { + $suspensionMock = Mockery::mock('alias:Swow\Coroutine'); + $suspensionMock->shouldReceive('run')->once()->andReturnSelf(); + $suspensionMock->shouldReceive('throw')->once()->andReturnUsing(function ($throwable) { + $this->assertInstanceOf(KilledException::class, $throwable); + }); + $func = function () { + // 模拟闭包函数的执行 + }; + (new SwowCoroutine($func))->kill(new KilledException()); + $this->assertTrue(true); + } } diff --git a/tests/mock/ripple.php b/tests/mock/ripple.php index dffa836..d806769 100644 --- a/tests/mock/ripple.php +++ b/tests/mock/ripple.php @@ -25,3 +25,8 @@ function defer(\Closure $closure) { call_user_func($closure); } + +function async(\Closure $closure) +{ + return Mockery::mock('alias:\Ripple\Promise'); +} From dab0ff14050b36bad8a8622d072fb7e53c18b107 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Sat, 9 Nov 2024 17:22:57 +0800 Subject: [PATCH 4/6] close #27 --- README.md | 21 ++++--- docs/doc/suspension-manage.md | 80 ++++++++++++++++++++++++ src/Factory.php | 3 +- src/Handlers/SwooleHandler.php | 4 ++ tests/HandlersCase/SwooleHandlerTest.php | 3 - 5 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 docs/doc/suspension-manage.md diff --git a/README.md b/README.md index bcd378e..08b3060 100644 --- a/README.md +++ b/README.md @@ -82,16 +82,17 @@ composer require workbunny/webman-coroutine ## 文档 -| 目录 | 地址 | -|:---:|:----------------------------------------------------------------------------------------------:| -| API | [Fucntion-APIs](https://workbunny.github.io/webman-coroutine/) | -| 教程 | [PHP 协程入门](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/coroutine.md) | -| - | [安装及配置](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/install.md) | -| - | [助手函数](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/helpers.md) | -| - | [`workerman`环境](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/workerman.md) | -| - | [`webman`框架](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/webman.md) | -| - | [`Utils`说明](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/utils.md) | -| - | [自定义拓展](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/custom.md) | +| 目录 | 地址 | +|:---:|:------------------------------------------------------------------------------------------------------:| +| API | [Fucntion-APIs](https://workbunny.github.io/webman-coroutine/) | +| 教程 | [PHP 协程入门](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/coroutine.md) | +| - | [安装及配置](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/install.md) | +| - | [助手函数](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/helpers.md) | +| - | [`workerman`环境](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/workerman.md) | +| - | [`webman`框架](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/webman.md) | +| - | [`Utils`说明](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/utils.md) | +| - | [自定义拓展](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/custom.md) | +| - | [协程的观测和管理](https://github.com/workbunny/webman-coroutine/tree/main/docs/doc/suspension-manage.md) | ## 参与开发 diff --git a/docs/doc/suspension-manage.md b/docs/doc/suspension-manage.md new file mode 100644 index 0000000..5c931f4 --- /dev/null +++ b/docs/doc/suspension-manage.md @@ -0,0 +1,80 @@ +# 协程的观测和管理 + +`webman-coroutine`中提供的关于协程的部分来自`Utils/Coroutine`和`Factory::sleep()` + +## `Utils/Coroutine` 协程工具 + +> `Utils/Coroutine`会在每次构造的时候将注入一个WeakMap,并储存`id`、`startTime` + +- 获取当前进程所有协程 + +```php +$weakMap = \Workbunny\WebmanCoroutine\Utils\Coroutine\Coroutine::getCoroutinesWeakMap(); +``` +> Tips: 方法返回一个储存所有通过`Utils/Coroutine`创建的协程的`WeakMap` + +- 退出当前进程所有协程 + +```php +$weakMap = \Workbunny\WebmanCoroutine\Utils\Coroutine\Coroutine::getCoroutinesWeakMap(); +/** + * @var \Workbunny\WebmanCoroutine\Utils\Coroutine\Handlers\CoroutineInterface $coroutine + * @var array $info ['id' => 协程id, 'startTime' => 开始时间] + */ +foreach ($weakMap as $coroutine => $info) { + $coroutine->kill(new \Workbunny\WebmanCoroutine\Exceptions\KilledException()); +} +``` +> Tips: +> - `kill`方法并不会立即退出协程,而是在该协程下一次唤起的时候触发,抛出一个异常 +> - 各协程驱动各略有不同,如:swoole驱动在协程遇到文件IO事件时并不会立即退出,也不会在下次唤起时抛出异常 + +## `Handler::$suspension`挂起事件 + +> 所有基于`Factory::sleep()`创建的挂起事件既是`Handler::$suspension`,包括`sleep()`、`wait_for()`和`Factory::waitFor()` + +- 获取当前进程所有挂起事件 + +```php +$weakMap = \Workbunny\WebmanCoroutine\Factory::getSuspensionsWeakMap(); +``` +> Tips: 方法返回一个储存所有通过`Factory`创建的协程的`WeakMap` + +- 添加/设置一个挂起事件至`WeakMap` +```php +$weakMap = \Workbunny\WebmanCoroutine\Factory::setSuspensionsWeakMap(); +``` + +- 退出当前进程所有挂起事件 + +```php +$weakMap = \Workbunny\WebmanCoroutine\Factory::getSuspensionsWeakMap(); +/** + * @var mixed $suspension + * @var array $info ['id' => 协程id, 'startTime' => 开始时间, 'event' => 挂起事件|NULL] + */ +foreach ($weakMap as $suspension => $info) { + \Workbunny\WebmanCoroutine\Factory::kill($suspension); +} +``` + +> Tips: +> - `kill`方法并不会立即退出协程,而是在该挂起下一次被唤起的时候触发,抛出一个`KilledException` +> - 各协程驱动各略有不同,如:`swoole`驱动在协程遇到文件IO事件时并不会立即退出,挂起事件会抛出`KilledException` + +## 观测/管理实践 + +### 采样监听方案 + +1. 在服务进程启动时创建定时器 +2. 定时器实现`Coroutine::getCoroutinesWeakMap()`和`Factory::getSuspensionsWeakMap()`的采样,并以进程pid为区分输出至日志 + +> Tips: 可以自定义实现对协程或挂起事件的startTime比对,合理杀死过长挂起的协程/事件 + +### 遥控管理方案 + +1. 在进程中实现命令对应的控制逻辑,例如:`kill`、`dump`、`check`等 +2. 在服务进程启动时通过`redis`/`apcu`对通道进行监听,注册命令对应的控制监听 +3. 遥控cli程序通过对通道的`pub`发送指定命令进行控制 + +> Tips: `webman`/`workerman` 环境基于`apcu`的共享缓存插件推荐:https://www.workerman.net/plugin/133 \ No newline at end of file diff --git a/src/Factory.php b/src/Factory.php index ac467c9..537d22c 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -28,7 +28,8 @@ * @method static void wakeup(string $event) * @method static void sleep(int|float $timeout = 0, ?string $event = null) * @method static void kill(object|int|string $suspensionOrSuspensionId, string $message = 'kill', int $exitCode = 0) - * @method static null|WeakMap listSuspensionsWeakMap() + * @method static null|WeakMap getSuspensionsWeakMap() + * @method static void setSuspensionsWeakMap(object $object, string|int $id, ?string $event, float|int $startTime) */ class Factory { diff --git a/src/Handlers/SwooleHandler.php b/src/Handlers/SwooleHandler.php index 03033b0..c6f0aa7 100644 --- a/src/Handlers/SwooleHandler.php +++ b/src/Handlers/SwooleHandler.php @@ -89,7 +89,9 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi if ($timeout < 0) { Coroutine::suspend(); if ($object->throw instanceof Throwable) { + // @codeCoverageIgnoreStart throw $object->throw; + // @codeCoverageIgnoreEnd } return; @@ -119,7 +121,9 @@ public static function sleep(float|int $timeout = 0, ?string $event = null): voi } Coroutine::suspend(); if ($object->throw instanceof Throwable) { + // @codeCoverageIgnoreStart throw $object->throw; + // @codeCoverageIgnoreEnd } } finally { if ($event) { diff --git a/tests/HandlersCase/SwooleHandlerTest.php b/tests/HandlersCase/SwooleHandlerTest.php index f5e7e3a..9e75e24 100644 --- a/tests/HandlersCase/SwooleHandlerTest.php +++ b/tests/HandlersCase/SwooleHandlerTest.php @@ -91,9 +91,6 @@ public function testWaitFor() }, 0.1); } - /** - * @return void - */ public function testSleep() { $timerMock = Mockery::mock('alias:Swoole\Timer'); From 75daff82cfefceb3e10bc079ca415ec1bcdfb792 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Sat, 9 Nov 2024 17:26:19 +0800 Subject: [PATCH 5/6] close #27 --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 3d75dc4..75197ed 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,7 @@ "friendsofphp/php-cs-fixer": "^3.0", "symfony/var-dumper": "^6.0", "swoole/ide-helper": "^5.1", - "swow/swow": "^1.5", - "revolt/event-loop": "^1.0" + "swow/swow": "^1.5" }, "autoload": { "psr-4": { From 7f6ad1826d5a4f7bef1731a60b2f1c3082796c5c Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Sat, 9 Nov 2024 17:42:21 +0800 Subject: [PATCH 6/6] close #27 --- src/Handlers/RippleWorkerman5Handler.php | 2 -- src/Handlers/SwooleWorkerman5Handler.php | 1 - src/Handlers/SwowWorkerman5Handler.php | 1 - 3 files changed, 4 deletions(-) diff --git a/src/Handlers/RippleWorkerman5Handler.php b/src/Handlers/RippleWorkerman5Handler.php index 9a10a3c..7f9027a 100644 --- a/src/Handlers/RippleWorkerman5Handler.php +++ b/src/Handlers/RippleWorkerman5Handler.php @@ -14,8 +14,6 @@ */ class RippleWorkerman5Handler extends RippleHandler { - use HandlerMethods; - /** @inheritdoc */ public static function isAvailable(): bool { diff --git a/src/Handlers/SwooleWorkerman5Handler.php b/src/Handlers/SwooleWorkerman5Handler.php index 8d9489f..a79f903 100644 --- a/src/Handlers/SwooleWorkerman5Handler.php +++ b/src/Handlers/SwooleWorkerman5Handler.php @@ -12,7 +12,6 @@ */ class SwooleWorkerman5Handler extends SwooleHandler { - use HandlerMethods; /** @inheritdoc */ public static function isAvailable(): bool diff --git a/src/Handlers/SwowWorkerman5Handler.php b/src/Handlers/SwowWorkerman5Handler.php index 70e9795..5edc50d 100644 --- a/src/Handlers/SwowWorkerman5Handler.php +++ b/src/Handlers/SwowWorkerman5Handler.php @@ -12,7 +12,6 @@ */ class SwowWorkerman5Handler extends SwowHandler { - use HandlerMethods; /** @inheritdoc */ public static function isAvailable(): bool