init
This commit is contained in:
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use Workerman\Coroutine\Barrier\BarrierInterface;
|
||||
use Workerman\Events\Swoole;
|
||||
use Workerman\Events\Swow;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Barrier
|
||||
*/
|
||||
class Barrier implements BarrierInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static string $driver;
|
||||
|
||||
/**
|
||||
* Get driver.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getDriver(): string
|
||||
{
|
||||
return static::$driver ??= match (Worker::$eventLoopClass) {
|
||||
Swoole::class => Barrier\Swoole::class,
|
||||
Swow::class => Barrier\Swow::class,
|
||||
default=> Barrier\Fiber::class,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function wait(object &$barrier, int $timeout = -1): void
|
||||
{
|
||||
static::getDriver()::wait($barrier, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(): object
|
||||
{
|
||||
return static::getDriver()::create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Barrier;
|
||||
|
||||
/**
|
||||
* Interface BarrierInterface
|
||||
*/
|
||||
interface BarrierInterface
|
||||
{
|
||||
/**
|
||||
* Wait for the barrier to be released.
|
||||
*
|
||||
* @param object $barrier
|
||||
* @param int $timeout
|
||||
* @return void
|
||||
*/
|
||||
public static function wait(object &$barrier, int $timeout = -1): void;
|
||||
|
||||
/**
|
||||
* Create a new barrier instance.
|
||||
*
|
||||
* @return BarrierInterface
|
||||
*/
|
||||
public static function create(): object;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Barrier;
|
||||
|
||||
use Revolt\EventLoop;
|
||||
use RuntimeException;
|
||||
use Workerman\Coroutine\Utils\DestructionWatcher;
|
||||
use Workerman\Timer;
|
||||
use Fiber as BaseFiber;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Fiber
|
||||
*/
|
||||
class Fiber implements BarrierInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function wait(object &$barrier, int $timeout = -1): void
|
||||
{
|
||||
$coroutine = BaseFiber::getCurrent();
|
||||
$resumed = false;
|
||||
$timerId = null;
|
||||
|
||||
if ($timeout > 0 && $coroutine) {
|
||||
$timerId = Timer::delay($timeout, function() use ($coroutine, &$resumed) {
|
||||
if (!$resumed) {
|
||||
$resumed = true;
|
||||
$coroutine->resume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$coroutine && DestructionWatcher::watch($barrier, function() use ($coroutine, &$resumed, &$timerId) {
|
||||
if (!$resumed) {
|
||||
$resumed = true;
|
||||
if ($timerId !== null) {
|
||||
Timer::del($timerId);
|
||||
}
|
||||
// In PHP 8.4.0 and earlier,
|
||||
// switching fibers during the execution of an object's destructor method is not allowed,
|
||||
// so we implemented a delay.
|
||||
if ($coroutine instanceof BaseFiber) {
|
||||
Timer::delay(0.00001, function() use ($coroutine) {
|
||||
$coroutine->resume();
|
||||
});
|
||||
return;
|
||||
}
|
||||
EventLoop::defer(function () use ($coroutine) {
|
||||
$coroutine->resume();
|
||||
});
|
||||
}
|
||||
});
|
||||
$barrier = null;
|
||||
$coroutine && BaseFiber::suspend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(): object
|
||||
{
|
||||
if (!Worker::isRunning()) {
|
||||
throw new RuntimeException('Fiber barrier only support in workerman runtime');
|
||||
}
|
||||
return new self();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Barrier;
|
||||
|
||||
use Swoole\Coroutine\Barrier as SwooleBarrier;
|
||||
class Swoole implements BarrierInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function wait(object &$barrier, int $timeout = -1): void
|
||||
{
|
||||
SwooleBarrier::wait($barrier, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(): object
|
||||
{
|
||||
return SwooleBarrier::make();
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Barrier;
|
||||
|
||||
use Swow\Sync\WaitReference;
|
||||
|
||||
class Swow implements BarrierInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function wait(object &$barrier, int $timeout = -1): void
|
||||
{
|
||||
WaitReference::wait($barrier, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(): object
|
||||
{
|
||||
return new WaitReference();
|
||||
}
|
||||
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Workerman\Coroutine\Channel\ChannelInterface;
|
||||
use Workerman\Coroutine\Channel\Memory as ChannelMemory;
|
||||
use Workerman\Coroutine\Channel\Swoole as ChannelSwoole;
|
||||
use Workerman\Coroutine\Channel\Swow as ChannelSwow;
|
||||
use Workerman\Coroutine\Channel\Fiber as ChannelFiber;
|
||||
use Workerman\Events\Fiber;
|
||||
use Workerman\Events\Swoole;
|
||||
use Workerman\Events\Swow;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Channel
|
||||
*/
|
||||
class Channel implements ChannelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ChannelInterface
|
||||
*/
|
||||
protected ChannelInterface $driver;
|
||||
|
||||
/**
|
||||
* Channel constructor.
|
||||
*
|
||||
* @param int $capacity
|
||||
*/
|
||||
public function __construct(int $capacity = 1)
|
||||
{
|
||||
if ($capacity < 1) {
|
||||
throw new InvalidArgumentException("The capacity must be greater than 0");
|
||||
}
|
||||
$this->driver = match (Worker::$eventLoopClass) {
|
||||
Swoole::class => new ChannelSwoole($capacity),
|
||||
Swow::class => new ChannelSwow($capacity),
|
||||
Fiber::class => new ChannelFiber($capacity),
|
||||
default => new ChannelMemory($capacity),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function push(mixed $data, float $timeout = -1): bool
|
||||
{
|
||||
return $this->driver->push($data, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pop(float $timeout = -1): mixed
|
||||
{
|
||||
return $this->driver->pop($timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return $this->driver->length();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCapacity(): int
|
||||
{
|
||||
return $this->driver->getCapacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasConsumers(): bool
|
||||
{
|
||||
return $this->driver->hasConsumers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasProducers(): bool
|
||||
{
|
||||
return $this->driver->hasProducers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->driver->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Channel;
|
||||
|
||||
/**
|
||||
* ChannelInterface
|
||||
*/
|
||||
interface ChannelInterface
|
||||
{
|
||||
/**
|
||||
* Push data to channel.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @param float $timeout
|
||||
* @return bool
|
||||
*/
|
||||
public function push(mixed $data, float $timeout = -1): bool;
|
||||
|
||||
/**
|
||||
* Pop data from channel.
|
||||
*
|
||||
* @param float $timeout
|
||||
* @return mixed
|
||||
*/
|
||||
public function pop(float $timeout = -1): mixed;
|
||||
|
||||
/**
|
||||
* Get the length of channel.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function length(): int;
|
||||
|
||||
/**
|
||||
* Get the capacity of channel.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCapacity(): int;
|
||||
|
||||
/**
|
||||
* Check if there are consumers waiting to pop data from the channel.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasConsumers(): bool;
|
||||
|
||||
/**
|
||||
* Check if there are producers waiting to push data to the channel.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasProducers(): bool;
|
||||
|
||||
/**
|
||||
* Close the channel.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close(): void;
|
||||
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Channel;
|
||||
|
||||
use Fiber as BaseFiber;
|
||||
use RuntimeException;
|
||||
use Workerman\Timer;
|
||||
use WeakMap;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Channel
|
||||
*/
|
||||
class Fiber implements ChannelInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $queue = [];
|
||||
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
private WeakMap $waitingPush;
|
||||
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
private WeakMap $waitingPop;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private int $capacity;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private bool $closed = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $capacity
|
||||
*/
|
||||
public function __construct(int $capacity = 1)
|
||||
{
|
||||
$this->capacity = $capacity;
|
||||
$this->waitingPush = new WeakMap();
|
||||
$this->waitingPop = new WeakMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function push(mixed $data, float $timeout = -1): bool
|
||||
{
|
||||
if ($this->closed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($this->queue) >= $this->capacity) {
|
||||
|
||||
if ($timeout == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
throw new RuntimeException("Fiber::getCurrent() returned null. Ensure this method is called within a Fiber context.");
|
||||
}
|
||||
|
||||
$this->waitingPush[$fiber] = true;
|
||||
|
||||
$timedOut = false;
|
||||
$timerId = null;
|
||||
if ($timeout > 0 && Worker::isRunning()) {
|
||||
$timerId = Timer::delay($timeout, function () use ($fiber, &$timedOut) {
|
||||
$timedOut = true;
|
||||
if ($fiber->isSuspended()) {
|
||||
unset($this->waitingPush[$fiber]);
|
||||
$fiber->resume(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BaseFiber::suspend();
|
||||
unset($this->waitingPush[$fiber]);
|
||||
|
||||
if (!$timedOut && $timerId) {
|
||||
Timer::del($timerId);
|
||||
}
|
||||
|
||||
if ($timedOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the channel is closed while waiting, return false.
|
||||
if ($this->closed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($this->waitingPop as $popFiber => $_) {
|
||||
unset($this->waitingPop[$popFiber]);
|
||||
if ($popFiber->isSuspended()) {
|
||||
$popFiber->resume($data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->queue[] = $data;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pop(float $timeout = -1): mixed
|
||||
{
|
||||
if ($this->closed && empty($this->queue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($this->queue)) {
|
||||
if ($timeout == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
throw new RuntimeException("Fiber::getCurrent() returned null. Ensure this method is called within a Fiber context.");
|
||||
}
|
||||
|
||||
$this->waitingPop[$fiber] = true;
|
||||
|
||||
$timedOut = false;
|
||||
$timerId = null;
|
||||
if ($timeout > 0) {
|
||||
Worker::isRunning() && $timerId = Timer::delay($timeout, function () use ($fiber, &$timedOut) {
|
||||
$timedOut = true;
|
||||
if ($fiber->isSuspended()) {
|
||||
unset($this->waitingPop[$fiber]);
|
||||
$fiber->resume(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$data = BaseFiber::suspend();
|
||||
|
||||
unset($this->waitingPop[$fiber]);
|
||||
|
||||
if (!$timedOut && $timerId !== null) {
|
||||
Timer::del($timerId);
|
||||
}
|
||||
|
||||
if ($timedOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($data === false && $this->closed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
$value = array_shift($this->queue);
|
||||
|
||||
foreach ($this->waitingPush as $pushFiber => $_) {
|
||||
unset($this->waitingPush[$pushFiber]);
|
||||
if ($pushFiber->isSuspended()) {
|
||||
$pushFiber->resume();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return count($this->queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCapacity(): int
|
||||
{
|
||||
return $this->capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasConsumers(): bool
|
||||
{
|
||||
return count($this->waitingPop) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasProducers(): bool
|
||||
{
|
||||
return count($this->waitingPush) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->closed = true;
|
||||
|
||||
foreach ($this->waitingPush as $fiber => $_) {
|
||||
unset($this->waitingPush[$fiber]);
|
||||
if ($fiber->isSuspended()) {
|
||||
$fiber->resume(false);
|
||||
}
|
||||
}
|
||||
$this->waitingPush = new WeakMap();
|
||||
|
||||
foreach ($this->waitingPop as $fiber => $_) {
|
||||
unset($this->waitingPop[$fiber]);
|
||||
if ($fiber->isSuspended()) {
|
||||
$fiber->resume(false);
|
||||
}
|
||||
}
|
||||
$this->waitingPop = new WeakMap();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Channel;
|
||||
|
||||
class Memory implements ChannelInterface
|
||||
{
|
||||
private array $data = [];
|
||||
private int $capacity;
|
||||
private bool $closed = false;
|
||||
|
||||
public function __construct(int $capacity = 0)
|
||||
{
|
||||
$this->capacity = $capacity;
|
||||
}
|
||||
|
||||
public function push(mixed $data, float $timeout = -1): bool
|
||||
{
|
||||
if ($this->closed) {
|
||||
return false;
|
||||
}
|
||||
if ($this->capacity > 0 && count($this->data) >= $this->capacity) {
|
||||
// Channel is full
|
||||
return false;
|
||||
}
|
||||
$this->data[] = $data;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function pop(float $timeout = -1): mixed
|
||||
{
|
||||
if (count($this->data) > 0) {
|
||||
return array_shift($this->data);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function length(): int
|
||||
{
|
||||
return count($this->data);
|
||||
}
|
||||
|
||||
public function getCapacity(): int
|
||||
{
|
||||
return $this->capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasConsumers(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasProducers(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->closed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Channel;
|
||||
|
||||
use Swoole\Coroutine\Channel;
|
||||
|
||||
/**
|
||||
* Class Swoole
|
||||
*/
|
||||
class Swoole implements ChannelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Channel
|
||||
*/
|
||||
protected Channel $channel;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $capacity
|
||||
*/
|
||||
public function __construct(protected int $capacity = 1)
|
||||
{
|
||||
$this->channel = new Channel($capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function push(mixed $data, float $timeout = -1): bool
|
||||
{
|
||||
return $this->channel->push($data, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pop(float $timeout = -1): mixed
|
||||
{
|
||||
return $this->channel->pop($timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return $this->channel->length();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCapacity(): int
|
||||
{
|
||||
return $this->channel->capacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasConsumers(): bool
|
||||
{
|
||||
return $this->channel->stats()['consumer_num'] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasProducers(): bool
|
||||
{
|
||||
return $this->channel->stats()['producer_num'] > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->channel->close();
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Channel;
|
||||
|
||||
use Swow\Channel;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class Swow
|
||||
*/
|
||||
class Swow implements ChannelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Channel
|
||||
*/
|
||||
protected Channel $channel;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $capacity
|
||||
*/
|
||||
public function __construct(protected int $capacity = 1)
|
||||
{
|
||||
$this->channel = new Channel($capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function push(mixed $data, float $timeout = -1): bool
|
||||
{
|
||||
try {
|
||||
$this->channel->push($data, $timeout == -1 ? -1 : (int)($timeout * 1000));
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function pop(float $timeout = -1): mixed
|
||||
{
|
||||
try {
|
||||
return $this->channel->pop($timeout == -1 ? -1 : (int)($timeout * 1000));
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return $this->channel->getLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getCapacity(): int
|
||||
{
|
||||
return $this->channel->getCapacity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasConsumers(): bool
|
||||
{
|
||||
return $this->channel->hasConsumers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasProducers(): bool
|
||||
{
|
||||
return $this->channel->hasProducers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->channel->close();
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use ArrayObject;
|
||||
use Workerman\Coroutine\Context\ContextInterface;
|
||||
use Workerman\Events\Swoole;
|
||||
use Workerman\Events\Swow;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Context
|
||||
*/
|
||||
class Context implements ContextInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var class-string<ContextInterface>
|
||||
*/
|
||||
protected static string $driver;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function get(?string $name = null, mixed $default = null): mixed
|
||||
{
|
||||
return static::$driver::get($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function set(string $name, $value): void
|
||||
{
|
||||
static::$driver::set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function has(string $name): bool
|
||||
{
|
||||
return static::$driver::has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function reset(?ArrayObject $data = null): void
|
||||
{
|
||||
static::$driver::reset($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function destroy(): void
|
||||
{
|
||||
static::$driver::destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function initDriver(): void
|
||||
{
|
||||
static::$driver ??= match (Worker::$eventLoopClass) {
|
||||
Swoole::class => Context\Swoole::class,
|
||||
Swow::class => Context\Swow::class,
|
||||
default=> Context\Fiber::class,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Context::initDriver();
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Coroutine\Context;
|
||||
|
||||
use ArrayObject;
|
||||
|
||||
/**
|
||||
* Interface ContextInterface
|
||||
*/
|
||||
interface ContextInterface
|
||||
{
|
||||
/**
|
||||
* Get the value from the context with the specified name.
|
||||
* If the name does not exist, return the default value.
|
||||
*
|
||||
* @param string|null $name The name of the value to get.
|
||||
* @param mixed $default The default value to return if the name does not exist.
|
||||
* @return mixed The value from the context or the default value.
|
||||
*/
|
||||
public static function get(?string $name = null, mixed $default = null): mixed;
|
||||
|
||||
/**
|
||||
* Set the value in the context with the specified name.
|
||||
*
|
||||
* @param string $name The name of the value to set.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public static function set(string $name, mixed $value): void;
|
||||
|
||||
/**
|
||||
* Check if the specified name exists in the context.
|
||||
*
|
||||
* @param string $name The name to check.
|
||||
* @return bool True if the name exists, otherwise false.
|
||||
*/
|
||||
public static function has(string $name): bool;
|
||||
|
||||
/**
|
||||
* Initialize the context with an array of data.
|
||||
*
|
||||
* @param ArrayObject|null $data The array of data to initialize the context.
|
||||
*/
|
||||
public static function reset(?ArrayObject $data = null): void;
|
||||
|
||||
/**
|
||||
* Destroy the context.
|
||||
*/
|
||||
public static function destroy(): void;
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Coroutine\Context;
|
||||
|
||||
use ArrayObject;
|
||||
use WeakMap;
|
||||
use Fiber as BaseFiber;
|
||||
|
||||
/**
|
||||
* Class Fiber
|
||||
*/
|
||||
class Fiber implements ContextInterface
|
||||
{
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
private static WeakMap $contexts;
|
||||
|
||||
/**
|
||||
* @var ArrayObject
|
||||
*/
|
||||
private static ArrayObject $nonFiberContext;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function get(?string $name = null, mixed $default = null): mixed
|
||||
{
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
return $name !== null ? (static::$nonFiberContext[$name] ?? $default) : static::$nonFiberContext;
|
||||
}
|
||||
if ($name === null) {
|
||||
return static::$contexts[$fiber] ??= new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
}
|
||||
return static::$contexts[$fiber][$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function set(string $name, $value): void
|
||||
{
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
static::$nonFiberContext[$name] = $value;
|
||||
return;
|
||||
}
|
||||
static::$contexts[$fiber] ??= new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
static::$contexts[$fiber][$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function has(string $name): bool
|
||||
{
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
return static::$nonFiberContext->offsetExists($name);
|
||||
}
|
||||
return isset(static::$contexts[$fiber]) && static::$contexts[$fiber]->offsetExists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function reset(?ArrayObject $data = null): void
|
||||
{
|
||||
if ($data) {
|
||||
$data->setFlags(ArrayObject::ARRAY_AS_PROPS);
|
||||
} else {
|
||||
$data = new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
}
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
static::$nonFiberContext = $data;
|
||||
return;
|
||||
}
|
||||
static::$contexts[$fiber] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function destroy(): void
|
||||
{
|
||||
$fiber = BaseFiber::getCurrent();
|
||||
if ($fiber === null) {
|
||||
static::$nonFiberContext = new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
return;
|
||||
}
|
||||
unset(static::$contexts[$fiber]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the weakMap.
|
||||
*/
|
||||
public static function initContext(): void
|
||||
{
|
||||
static::$contexts = new WeakMap();
|
||||
static::$nonFiberContext = new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Fiber::initContext();
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Coroutine\Context;
|
||||
|
||||
use ArrayObject;
|
||||
use Swoole\Coroutine;
|
||||
|
||||
class Swoole implements ContextInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function get(?string $name = null, mixed $default = null): mixed
|
||||
{
|
||||
$context = Coroutine::getContext();
|
||||
if (!$context) {
|
||||
return $default;
|
||||
}
|
||||
$context->setFlags(ArrayObject::ARRAY_AS_PROPS);
|
||||
if ($name === null) {
|
||||
return $context;
|
||||
}
|
||||
return $context[$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function set(string $name, $value): void
|
||||
{
|
||||
Coroutine::getContext()[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function has(string $name): bool
|
||||
{
|
||||
$context = Coroutine::getContext();
|
||||
return $context->offsetExists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function reset(?ArrayObject $data = null): void
|
||||
{
|
||||
$context = Coroutine::getContext();
|
||||
$context->setFlags(ArrayObject::ARRAY_AS_PROPS);
|
||||
$context->exchangeArray($data ? $data->getArrayCopy() : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function destroy(): void
|
||||
{
|
||||
$context = Coroutine::getContext();
|
||||
$context->exchangeArray([]);
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Coroutine\Context;
|
||||
|
||||
use ArrayObject;
|
||||
use Swow\Coroutine;
|
||||
use WeakMap;
|
||||
|
||||
class Swow implements ContextInterface
|
||||
{
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
public static WeakMap $contexts;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function get(?string $name = null, mixed $default = null): mixed
|
||||
{
|
||||
$fiber = Coroutine::getCurrent();
|
||||
if ($name === null) {
|
||||
static::$contexts[$fiber] ??= new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
return static::$contexts[$fiber];
|
||||
}
|
||||
return static::$contexts[$fiber][$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function set(string $name, $value): void
|
||||
{
|
||||
$coroutine = Coroutine::getCurrent();
|
||||
static::$contexts[$coroutine] ??= new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
|
||||
static::$contexts[$coroutine][$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function has(string $name): bool
|
||||
{
|
||||
$fiber = Coroutine::getCurrent();
|
||||
return isset(static::$contexts[$fiber]) && static::$contexts[$fiber]->offsetExists($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function reset(?ArrayObject $data = null): void
|
||||
{
|
||||
$coroutine = Coroutine::getCurrent();
|
||||
$data->setFlags(ArrayObject::ARRAY_AS_PROPS);
|
||||
static::$contexts[$coroutine] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function destroy(): void
|
||||
{
|
||||
unset(static::$contexts[Coroutine::getCurrent()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the weakMap.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function initContext(): void
|
||||
{
|
||||
self::$contexts = new WeakMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Swow::initContext();
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman;
|
||||
|
||||
use Workerman\Coroutine\Coroutine\CoroutineInterface;
|
||||
use Workerman\Coroutine\Coroutine\Fiber;
|
||||
use Workerman\Worker;
|
||||
use Workerman\Coroutine\Coroutine\Swoole as SwooleCoroutine;
|
||||
use Workerman\Coroutine\Coroutine\Swow as SwowCoroutine;
|
||||
use Workerman\Events\Swoole as SwooleEvent;
|
||||
use Workerman\Events\Swow as SwowEvent;
|
||||
|
||||
/**
|
||||
* Class Coroutine
|
||||
*/
|
||||
class Coroutine implements CoroutineInterface
|
||||
{
|
||||
/**
|
||||
* @var class-string<CoroutineInterface>
|
||||
*/
|
||||
protected static string $driverClass;
|
||||
|
||||
/**
|
||||
* @var CoroutineInterface
|
||||
*/
|
||||
public CoroutineInterface $driver;
|
||||
|
||||
/**
|
||||
* Coroutine constructor.
|
||||
*
|
||||
* @param callable $callable
|
||||
*/
|
||||
public function __construct(callable $callable)
|
||||
{
|
||||
$this->driver = new static::$driverClass($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(callable $callable, ...$args): CoroutineInterface
|
||||
{
|
||||
return static::$driverClass::create($callable, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function start(mixed ...$args): mixed
|
||||
{
|
||||
return $this->driver->start(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function resume(mixed ...$args): mixed
|
||||
{
|
||||
return $this->driver->resume(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
return $this->driver->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function defer(callable $callable): void
|
||||
{
|
||||
static::$driverClass::defer($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function suspend(mixed $value = null): mixed
|
||||
{
|
||||
return static::$driverClass::suspend($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getCurrent(): CoroutineInterface
|
||||
{
|
||||
return static::$driverClass::getCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function isCoroutine(): bool
|
||||
{
|
||||
return static::$driverClass::isCoroutine();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function init(): void
|
||||
{
|
||||
static::$driverClass = match (Worker::$eventLoopClass ?? null) {
|
||||
SwooleEvent::class => SwooleCoroutine::class,
|
||||
SwowEvent::class => SwowCoroutine::class,
|
||||
default => Fiber::class,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Coroutine::init();
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Coroutine;
|
||||
|
||||
use Fiber;
|
||||
use Swow\Coroutine as SwowCoroutine;
|
||||
|
||||
/**
|
||||
* Interface CoroutineInterface
|
||||
*/
|
||||
interface CoroutineInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a coroutine.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param ...$data
|
||||
* @return CoroutineInterface
|
||||
*/
|
||||
public static function create(callable $callable, ...$data): CoroutineInterface;
|
||||
|
||||
/**
|
||||
* Start a coroutine.
|
||||
*
|
||||
* @param mixed ...$args
|
||||
* @return mixed
|
||||
*/
|
||||
public function start(mixed ...$args): mixed;
|
||||
|
||||
/**
|
||||
* Resume a coroutine.
|
||||
*
|
||||
* @param mixed ...$args
|
||||
* @return mixed
|
||||
*/
|
||||
public function resume(mixed ...$args): mixed;
|
||||
|
||||
/**
|
||||
* Get the id of the coroutine.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function id(): int;
|
||||
|
||||
/**
|
||||
* Register a callable to be executed when the current fiber is destroyed
|
||||
*
|
||||
* @param callable $callable
|
||||
* @return void
|
||||
*/
|
||||
public static function defer(callable $callable): void;
|
||||
|
||||
/**
|
||||
* Yield the coroutine.
|
||||
*
|
||||
* @param mixed|null $value
|
||||
* @return mixed
|
||||
*/
|
||||
public static function suspend(mixed $value = null): mixed;
|
||||
|
||||
/**
|
||||
* Get the current coroutine.
|
||||
*
|
||||
* @return CoroutineInterface|Fiber|SwowCoroutine|static
|
||||
*/
|
||||
public static function getCurrent(): CoroutineInterface|Fiber|SwowCoroutine|static;
|
||||
|
||||
/**
|
||||
* Check if the current coroutine is in a coroutine.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isCoroutine(): bool;
|
||||
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Coroutine;
|
||||
|
||||
use Fiber as BaseFiber;
|
||||
use RuntimeException;
|
||||
use WeakMap;
|
||||
use Workerman\Coroutine\Utils\DestructionWatcher;
|
||||
|
||||
/**
|
||||
* Class Fiber
|
||||
*/
|
||||
class Fiber implements CoroutineInterface
|
||||
{
|
||||
/**
|
||||
* @var BaseFiber|null
|
||||
*/
|
||||
private ?BaseFiber $fiber;
|
||||
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
private static WeakMap $instances;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @param callable|null $callable
|
||||
*/
|
||||
public function __construct(?callable $callable = null)
|
||||
{
|
||||
static $id = 0;
|
||||
$this->id = ++$id;
|
||||
if ($callable) {
|
||||
$callable = function(...$args) use ($callable) {
|
||||
try {
|
||||
$callable(...$args);
|
||||
} finally {
|
||||
$this->fiber = null;
|
||||
}
|
||||
};
|
||||
$this->fiber = new BaseFiber($callable);
|
||||
self::$instances[$this->fiber] = $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(callable $callable, ...$args): CoroutineInterface
|
||||
{
|
||||
$fiber = new Fiber($callable);
|
||||
$fiber->start(...$args);
|
||||
return $fiber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function start(mixed ...$args): mixed
|
||||
{
|
||||
return $this->fiber->start(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function resume(mixed ...$args): mixed
|
||||
{
|
||||
return $this->fiber->resume(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function suspend(mixed $value = null): mixed
|
||||
{
|
||||
return BaseFiber::suspend($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function defer(callable $callable): void
|
||||
{
|
||||
$baseFiber = BaseFiber::getCurrent();
|
||||
if ($baseFiber === null) {
|
||||
throw new RuntimeException('Cannot defer outside of a fiber.');
|
||||
}
|
||||
DestructionWatcher::watch($baseFiber, $callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getCurrent(): CoroutineInterface
|
||||
{
|
||||
if (!$baseFiber = BaseFiber::getCurrent()) {
|
||||
throw new RuntimeException('Not in fiber context');
|
||||
}
|
||||
if (!isset(self::$instances[$baseFiber])) {
|
||||
$fiber = new Fiber();
|
||||
$fiber->fiber = $baseFiber;
|
||||
self::$instances[$baseFiber] = $fiber;
|
||||
}
|
||||
return self::$instances[$baseFiber];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function isCoroutine(): bool
|
||||
{
|
||||
return BaseFiber::getCurrent() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the fiber.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init(): void
|
||||
{
|
||||
self::$instances = new WeakMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Fiber::init();
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Coroutine;
|
||||
|
||||
use RuntimeException;
|
||||
use Swoole\Coroutine;
|
||||
use WeakReference;
|
||||
|
||||
class Swoole implements CoroutineInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static array $instances = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private int $id = 0;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $callable;
|
||||
|
||||
/**
|
||||
* Coroutine constructor.
|
||||
*
|
||||
* @param callable|null $callable
|
||||
*/
|
||||
public function __construct(?callable $callable = null)
|
||||
{
|
||||
$this->callable = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(callable $callable, ...$args): CoroutineInterface
|
||||
{
|
||||
$id = Coroutine::create($callable, ...$args);
|
||||
if (isset(self::$instances[$id]) && $coroutine = self::$instances[$id]->get()) {
|
||||
return $coroutine;
|
||||
}
|
||||
$coroutine = new self($callable);
|
||||
$coroutine->id = $id;
|
||||
self::$instances[$id] = WeakReference::create($coroutine);
|
||||
return $coroutine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function start(mixed ...$args): CoroutineInterface
|
||||
{
|
||||
if ($this->id) {
|
||||
throw new RuntimeException('Coroutine has already started');
|
||||
}
|
||||
$this->id = Coroutine::create($this->callable, ...$args);
|
||||
$this->callable = null;
|
||||
if (isset(self::$instances[$this->id]) && $coroutine = self::$instances[$this->id]->get()) {
|
||||
return $coroutine;
|
||||
}
|
||||
self::$instances[$this->id] = WeakReference::create($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function resume(mixed ...$args): mixed
|
||||
{
|
||||
return Coroutine::resume($this->id, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function defer(callable $callable): void
|
||||
{
|
||||
Coroutine::defer($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function suspend(mixed $value = null): mixed
|
||||
{
|
||||
return Coroutine::suspend($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function getCurrent(): CoroutineInterface
|
||||
{
|
||||
$id = Coroutine::getCid();
|
||||
if ($id === -1) {
|
||||
throw new RuntimeException('Not in coroutine');
|
||||
}
|
||||
if (!isset(self::$instances[$id])) {
|
||||
$coroutine = new self();
|
||||
$coroutine->id = $id;
|
||||
self::$instances[$id] = WeakReference::create($coroutine);
|
||||
}
|
||||
return self::$instances[$id]->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function isCoroutine(): bool
|
||||
{
|
||||
return Coroutine::getCid() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
unset(self::$instances[$this->id]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\Coroutine;
|
||||
|
||||
use Swow\Coroutine;
|
||||
|
||||
/**
|
||||
* Class Swow
|
||||
*/
|
||||
class Swow extends Coroutine implements CoroutineInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $callbacks = [];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function defer(callable $callable): void
|
||||
{
|
||||
$coroutine = static::getCurrent();
|
||||
$coroutine->callbacks[] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function create(callable $callable, ...$args): CoroutineInterface
|
||||
{
|
||||
return static::run($callable, ...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function start(mixed ...$args): mixed
|
||||
{
|
||||
return $this->resume(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
return $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function suspend(mixed $value = null): mixed
|
||||
{
|
||||
return Coroutine::yield($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function isCoroutine(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
foreach (array_reverse($this->callbacks) as $callable) {
|
||||
$callable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Workerman\Coroutine\Exception;
|
||||
|
||||
class PoolException extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class Locker
|
||||
*/
|
||||
class Locker
|
||||
{
|
||||
/**
|
||||
* @var Channel[]
|
||||
*/
|
||||
protected static array $channels = [];
|
||||
|
||||
/**
|
||||
* Lock.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public static function lock(string $key): bool
|
||||
{
|
||||
if (!isset(static::$channels[$key])) {
|
||||
static::$channels[$key] = new Channel(1);
|
||||
}
|
||||
return static::$channels[$key]->push(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public static function unlock(string $key): bool
|
||||
{
|
||||
if ($channel = static::$channels[$key] ?? null) {
|
||||
// Must check hasProducers before pop, because pop in swow will wake up the producer, leading to inaccurate judgment.
|
||||
$hasProducers = $channel->hasProducers();
|
||||
$result = $channel->pop();
|
||||
if (!$hasProducers) {
|
||||
$channel->close();
|
||||
unset(static::$channels[$key]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
throw new RuntimeException("Unlock failed, because the key $key is not locked");
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use Throwable;
|
||||
use Workerman\Coroutine;
|
||||
|
||||
/**
|
||||
* Class Parallel
|
||||
*/
|
||||
class Parallel
|
||||
{
|
||||
/**
|
||||
* @var Channel|null
|
||||
*/
|
||||
protected ?Channel $channel = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $callbacks = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $results = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $exceptions = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $concurrent
|
||||
*/
|
||||
public function __construct(int $concurrent = -1)
|
||||
{
|
||||
if ($concurrent > 0) {
|
||||
$this->channel = new Channel($concurrent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a coroutine.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param string|null $key
|
||||
* @return void
|
||||
*/
|
||||
public function add(callable $callable, ?string $key = null): void
|
||||
{
|
||||
if ($key === null) {
|
||||
$this->callbacks[] = $callable;
|
||||
} else {
|
||||
$this->callbacks[$key] = $callable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait all coroutines complete and return results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function wait(): array
|
||||
{
|
||||
$barrier = Barrier::create();
|
||||
foreach ($this->callbacks as $key => $callback) {
|
||||
$this->channel?->push(true);
|
||||
Coroutine::create(function () use ($callback, $key, $barrier) {
|
||||
try {
|
||||
$this->results[$key] = $callback();
|
||||
} catch (Throwable $throwable) {
|
||||
$this->exceptions[$key] = $throwable;
|
||||
} finally {
|
||||
$this->channel?->pop();
|
||||
}
|
||||
});
|
||||
}
|
||||
Barrier::wait($barrier);
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failed results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getExceptions(): array
|
||||
{
|
||||
return $this->exceptions;
|
||||
}
|
||||
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use Closure;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use stdClass;
|
||||
use Throwable;
|
||||
use WeakMap;
|
||||
use Workerman\Coroutine;
|
||||
use Workerman\Coroutine\Exception\PoolException;
|
||||
use Workerman\Coroutine\Utils\DestructionWatcher;
|
||||
use Workerman\Timer;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class Pool
|
||||
*/
|
||||
class Pool implements PoolInterface
|
||||
{
|
||||
/**
|
||||
* @var Channel
|
||||
*/
|
||||
protected Channel $channel;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected int $minConnections = 1;
|
||||
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
protected WeakMap $connections;
|
||||
|
||||
/**
|
||||
* @var ?object
|
||||
*/
|
||||
protected ?object $nonCoroutineConnection = null;
|
||||
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
protected WeakMap $lastUsedTimes;
|
||||
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
protected WeakMap $lastHeartbeatTimes;
|
||||
|
||||
/**
|
||||
* @var Closure|null
|
||||
*/
|
||||
protected ?Closure $connectionCreateHandler = null;
|
||||
|
||||
/**
|
||||
* @var Closure|null
|
||||
*/
|
||||
protected ?Closure $connectionDestroyHandler = null;
|
||||
|
||||
/**
|
||||
* @var Closure|null
|
||||
*/
|
||||
protected ?Closure $connectionHeartbeatHandler = null;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected float $idleTimeout = 60;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected float $heartbeatInterval = 50;
|
||||
|
||||
/**
|
||||
* @var float
|
||||
*/
|
||||
protected float $waitTimeout = 10;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|Closure|null
|
||||
*/
|
||||
protected LoggerInterface|Closure|null $logger = null;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
private array $configurableProperties = [
|
||||
'minConnections',
|
||||
'idleTimeout',
|
||||
'heartbeatInterval',
|
||||
'waitTimeout',
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $maxConnections
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(protected int $maxConnections = 1, protected array $config = [])
|
||||
{
|
||||
foreach ($config as $key => $value) {
|
||||
$camelCaseKey = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
|
||||
if (in_array($camelCaseKey, $this->configurableProperties, true)) {
|
||||
$this->$camelCaseKey = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->channel = new Channel($maxConnections);
|
||||
$this->lastUsedTimes = new WeakMap();
|
||||
$this->lastHeartbeatTimes = new WeakMap();
|
||||
$this->connections = new WeakMap();
|
||||
|
||||
if (Worker::isRunning()) {
|
||||
Timer::repeat(1, function () {
|
||||
$this->checkConnections();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection creator.
|
||||
*
|
||||
* @param callable $connectionCreateHandler
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnectionCreator(callable $connectionCreateHandler): self
|
||||
{
|
||||
$this->connectionCreateHandler = $connectionCreateHandler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection closer.
|
||||
*
|
||||
* @param callable $connectionDestroyHandler
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnectionCloser(callable $connectionDestroyHandler): self
|
||||
{
|
||||
$this->connectionDestroyHandler = $connectionDestroyHandler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection heartbeat checker.
|
||||
*
|
||||
* @param callable $connectionHeartbeatHandler
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeartbeatChecker(callable $connectionHeartbeatHandler): self
|
||||
{
|
||||
$this->connectionHeartbeatHandler = $connectionHeartbeatHandler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection.
|
||||
*
|
||||
* @return object
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function get(): object
|
||||
{
|
||||
if (!Coroutine::isCoroutine()) {
|
||||
if (!$this->nonCoroutineConnection) {
|
||||
$this->nonCoroutineConnection = $this->createConnection();
|
||||
}
|
||||
return $this->nonCoroutineConnection;
|
||||
}
|
||||
$num = $this->channel->length();
|
||||
if ($num === 0 && $this->getConnectionCount() < $this->maxConnections) {
|
||||
return $this->createConnection();
|
||||
}
|
||||
$connection = $this->channel->pop($this->waitTimeout);
|
||||
if (!$connection) {
|
||||
throw new PoolException("Failed to get a connection from the pool within the wait timeout ($this->waitTimeout seconds). The connection pool is exhausted.");
|
||||
}
|
||||
$this->lastUsedTimes[$connection] = time();
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put connection to pool.
|
||||
*
|
||||
* @param object $connection
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function put(object $connection): void
|
||||
{
|
||||
// This connection does not belong to the connection pool.
|
||||
// It may have been closed by $this->closeConnection($connection).
|
||||
if (!isset($this->connections[$connection])) {
|
||||
throw new PoolException('The connection does not belong to the connection pool.');
|
||||
}
|
||||
if ($connection === $this->nonCoroutineConnection) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->channel->push($connection);
|
||||
} catch (Throwable $throwable) {
|
||||
$this->closeConnection($connection);
|
||||
throw $throwable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the connection is valid.
|
||||
*
|
||||
* @param $connection
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidConnection($connection): bool
|
||||
{
|
||||
return is_object($connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create connection.
|
||||
*
|
||||
* @return object
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function createConnection(): object
|
||||
{
|
||||
if ($this->getConnectionCount() >= $this->maxConnections) {
|
||||
throw new PoolException('CreateConnection failed, maximum connection limit reached.');
|
||||
}
|
||||
// Create a placeholder to ensure the correct value of getConnectionCount().
|
||||
$placeholder = new stdClass;
|
||||
$this->connections[$placeholder] = 0;
|
||||
try {
|
||||
// Coroutines will switch here, so we need $placeholder to ensure the correct value of getConnectionCount().
|
||||
$connection = ($this->connectionCreateHandler)();
|
||||
if (!$this->isValidConnection($connection)) {
|
||||
throw new PoolException('CreateConnection failed, expected a connection object, but got ' . gettype($connection) . '.');
|
||||
}
|
||||
unset($this->connections[$placeholder]);
|
||||
$this->connections[$connection] = $this->lastUsedTimes[$connection] = $this->lastHeartbeatTimes[$connection] = time();
|
||||
} catch (Throwable $throwable) {
|
||||
unset($this->connections[$placeholder]);
|
||||
throw $throwable;
|
||||
}
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection and remove the connection from the connection pool.
|
||||
*
|
||||
* @param object $connection
|
||||
* @return void
|
||||
*/
|
||||
public function closeConnection(object $connection): void
|
||||
{
|
||||
if (!isset($this->connections[$connection])) {
|
||||
return;
|
||||
}
|
||||
// Mark this connection as no longer belonging to the connection pool.
|
||||
unset($this->lastUsedTimes[$connection], $this->lastHeartbeatTimes[$connection], $this->connections[$connection]);
|
||||
if ($this->nonCoroutineConnection === $connection) {
|
||||
$this->nonCoroutineConnection = null;
|
||||
}
|
||||
if (!$this->connectionDestroyHandler) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
($this->connectionDestroyHandler)($connection);
|
||||
} catch (Throwable $throwable) {
|
||||
$this->log($throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup idle connections.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function checkConnections(): void
|
||||
{
|
||||
$num = $this->channel->length();
|
||||
$time = time();
|
||||
for($i = $num; $i > 0; $i--) {
|
||||
$connection = $this->channel->pop(0.001);
|
||||
if (!$connection) {
|
||||
return;
|
||||
}
|
||||
$lastUsedTime = $this->lastUsedTimes[$connection];
|
||||
if ($time - $lastUsedTime > $this->idleTimeout && $this->channel->length() >= $this->minConnections) {
|
||||
$this->closeConnection($connection);
|
||||
continue;
|
||||
}
|
||||
$this->trySendHeartbeat($connection) && $this->channel->push($connection);
|
||||
}
|
||||
if ($this->nonCoroutineConnection) {
|
||||
$this->trySendHeartbeat($this->nonCoroutineConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to send heartbeat.
|
||||
*
|
||||
* @param $connection
|
||||
* @return bool
|
||||
*/
|
||||
private function trySendHeartbeat($connection): bool
|
||||
{
|
||||
$lastHeartbeatTime = $this->lastHeartbeatTimes[$connection] ?? 0;
|
||||
$time = time();
|
||||
if ($this->connectionHeartbeatHandler && $time - $lastHeartbeatTime >= $this->heartbeatInterval) {
|
||||
try {
|
||||
($this->connectionHeartbeatHandler)($connection);
|
||||
$this->lastHeartbeatTimes[$connection] = $time;
|
||||
} catch (Throwable $throwable) {
|
||||
$this->log($throwable);
|
||||
$this->closeConnection($connection);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of connections in the connection pool.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getConnectionCount(): int
|
||||
{
|
||||
return count($this->connections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connections.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function closeConnections(): void
|
||||
{
|
||||
$num = $this->channel->length();
|
||||
for ($i = $num; $i > 0; $i--) {
|
||||
$connection = $this->channel->pop(0.001);
|
||||
if (!$connection) {
|
||||
return;
|
||||
}
|
||||
$this->closeConnection($connection);
|
||||
}
|
||||
$this->nonCoroutineConnection && $this->closeConnection($this->nonCoroutineConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log.
|
||||
*
|
||||
* @param $message
|
||||
* @return void
|
||||
*/
|
||||
protected function log($message): void
|
||||
{
|
||||
if (!$this->logger) {
|
||||
echo $message . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
if ($this->logger instanceof Closure) {
|
||||
($this->logger)($message);
|
||||
return;
|
||||
}
|
||||
$this->logger->info((string)$message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
/**
|
||||
* Interface PoolInterface
|
||||
*/
|
||||
interface PoolInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get a connection from the pool.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(): mixed;
|
||||
|
||||
/**
|
||||
* Put a connection back to the pool.
|
||||
*
|
||||
* @param object $connection
|
||||
* @return void
|
||||
*/
|
||||
public function put(object $connection): void;
|
||||
|
||||
/**
|
||||
* Create a connection.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function createConnection(): object;
|
||||
|
||||
/**
|
||||
* Close the connection and remove the connection from the connection pool.
|
||||
*
|
||||
* @param object $connection
|
||||
* @return void
|
||||
*/
|
||||
public function closeConnection(object $connection): void;
|
||||
|
||||
/**
|
||||
* Get the number of connections in the connection pool.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getConnectionCount(): int;
|
||||
|
||||
/**
|
||||
* Close connections in the connection pool.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function closeConnections(): void;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of workerman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace Workerman\Coroutine\Utils;
|
||||
|
||||
use WeakMap;
|
||||
|
||||
class DestructionWatcher
|
||||
{
|
||||
/**
|
||||
* @var WeakMap
|
||||
*/
|
||||
protected static WeakMap $objects;
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
protected array $callbacks = [];
|
||||
|
||||
/**
|
||||
* DestructionWatcher constructor.
|
||||
*
|
||||
* @param callable|null $callback
|
||||
*/
|
||||
public function __construct(?callable $callback = null)
|
||||
{
|
||||
if ($callback) {
|
||||
$this->callbacks[] = $callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DestructionWatcher destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
foreach (array_reverse($this->callbacks) as $callback) {
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch object destruction.
|
||||
*
|
||||
* @param object $object
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function watch(object $object, callable $callback): void
|
||||
{
|
||||
static::$objects ??= new WeakMap();
|
||||
static::$objects[$object] ??= new static();
|
||||
static::$objects[$object]->callbacks[] = $callback;
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* @author workbunny/Chaz6chez
|
||||
* @email chaz6chez1993@outlook.com
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Workerman\Worker;
|
||||
use Workerman\Coroutine\Coroutine\CoroutineInterface;
|
||||
use Workerman\Coroutine\WaitGroup\Fiber as FiberWaitGroup;
|
||||
use Workerman\Coroutine\WaitGroup\Swoole as SwooleWaitGroup;
|
||||
use Workerman\Coroutine\WaitGroup\Swow as SwowWaitGroup;
|
||||
use Workerman\Coroutine\WaitGroup\WaitGroupInterface;
|
||||
use Workerman\Events\Swoole;
|
||||
use Workerman\Events\Swow;
|
||||
|
||||
/**
|
||||
* @method bool add(int $delta = 1)
|
||||
* @method bool done()
|
||||
* @method int count()
|
||||
* @method bool wait(int|float $timeout = -1)
|
||||
*/
|
||||
class WaitGroup
|
||||
{
|
||||
/**
|
||||
* @var class-string<CoroutineInterface>
|
||||
*/
|
||||
protected static string $driverClass;
|
||||
|
||||
/**
|
||||
* @var WaitGroupInterface
|
||||
*/
|
||||
protected WaitGroupInterface $driver;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->driver = new (self::driverClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get driver class.
|
||||
*
|
||||
* @return class-string<CoroutineInterface>
|
||||
*/
|
||||
protected static function driverClass(): string
|
||||
{
|
||||
return static::$driverClass ??= match (Worker::$eventLoopClass ?? null) {
|
||||
Swoole::class => SwooleWaitGroup::class,
|
||||
Swow::class => SwowWaitGroup::class,
|
||||
default => FiberWaitGroup::class,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 代理调用WaitGroupInterface方法
|
||||
*
|
||||
* @codeCoverageIgnore 系统魔术方法,忽略覆盖
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
if (!method_exists($this->driver, $name)) {
|
||||
throw new BadMethodCallException("Method $name not exists. ");
|
||||
}
|
||||
|
||||
return $this->driver->$name(...$arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* @author workbunny/Chaz6chez
|
||||
* @email chaz6chez1993@outlook.com
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\WaitGroup;
|
||||
|
||||
use Workerman\Coroutine\Channel\Fiber as Channel;
|
||||
|
||||
class Fiber implements WaitGroupInterface
|
||||
{
|
||||
|
||||
/** @var int */
|
||||
protected int $count;
|
||||
|
||||
/**
|
||||
* @var Channel
|
||||
*/
|
||||
protected Channel $channel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->channel = new Channel(1);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function add(int $delta = 1): bool
|
||||
{
|
||||
$this->count += max($delta, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function done(): bool
|
||||
{
|
||||
$this->count--;
|
||||
if ($this->count <= 0) {
|
||||
$this->channel->push(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function count(): int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function wait(int|float $timeout = -1): bool
|
||||
{
|
||||
if ($this->count() > 0) {
|
||||
return $this->channel->pop($timeout);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* @author workbunny/Chaz6chez
|
||||
* @email chaz6chez1993@outlook.com
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\WaitGroup;
|
||||
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class Swoole
|
||||
*/
|
||||
class Swoole implements WaitGroupInterface
|
||||
{
|
||||
|
||||
/** @var WaitGroup */
|
||||
protected WaitGroup $waitGroup;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->waitGroup = new WaitGroup();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function add(int $delta = 1): bool
|
||||
{
|
||||
$this->waitGroup->add(max($delta, 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function done(): bool
|
||||
{
|
||||
if ($this->count() > 0) {
|
||||
$this->waitGroup->done();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function count(): int
|
||||
{
|
||||
return $this->waitGroup->count();
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function wait(int|float $timeout = -1): bool
|
||||
{
|
||||
try {
|
||||
$this->waitGroup->wait(max($timeout, $timeout > 0 ? 0.001 : -1));
|
||||
return true;
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @author workbunny/Chaz6chez
|
||||
* @email chaz6chez1993@outlook.com
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\WaitGroup;
|
||||
|
||||
use Swow\Sync\WaitGroup;
|
||||
use Throwable;
|
||||
|
||||
class Swow implements WaitGroupInterface
|
||||
{
|
||||
|
||||
/** @var WaitGroup */
|
||||
protected WaitGroup $waitGroup;
|
||||
|
||||
/** @var int count */
|
||||
protected int $count;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->waitGroup = new WaitGroup();
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function add(int $delta = 1): bool
|
||||
{
|
||||
$this->waitGroup->add($delta = max($delta, 1));
|
||||
$this->count += $delta;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function done(): bool
|
||||
{
|
||||
if ($this->count() > 0) {
|
||||
$this->count--;
|
||||
$this->waitGroup->done();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function count(): int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function wait(int|float $timeout = -1): bool
|
||||
{
|
||||
try {
|
||||
$this->waitGroup->wait($timeout > 0 ? (int) ($timeout * 1000) : $timeout);
|
||||
return true;
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* @author workbunny/Chaz6chez
|
||||
* @email chaz6chez1993@outlook.com
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Workerman\Coroutine\WaitGroup;
|
||||
|
||||
interface WaitGroupInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Increment count
|
||||
*
|
||||
* @param int $delta
|
||||
* @return bool
|
||||
*/
|
||||
public function add(int $delta = 1): bool;
|
||||
|
||||
/**
|
||||
* Complete count
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function done(): bool;
|
||||
|
||||
/**
|
||||
* Return count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int;
|
||||
|
||||
/**
|
||||
* Wait
|
||||
*
|
||||
* @param int|float $timeout second
|
||||
* @return bool timeout:false success:true
|
||||
*/
|
||||
public function wait(int|float $timeout = -1): bool;
|
||||
}
|
||||
Reference in New Issue
Block a user