Initial Commit
This commit is contained in:
41
dependencies/amphp/sync/composer-require-check.json
vendored
Normal file
41
dependencies/amphp/sync/composer-require-check.json
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"symbol-whitelist": [
|
||||
"null",
|
||||
"true",
|
||||
"false",
|
||||
"static",
|
||||
"self",
|
||||
"parent",
|
||||
"array",
|
||||
"string",
|
||||
"int",
|
||||
"float",
|
||||
"bool",
|
||||
"iterable",
|
||||
"callable",
|
||||
"void",
|
||||
"object",
|
||||
"MSG_EAGAIN",
|
||||
"MSG_ENOMSG",
|
||||
"msg_get_queue",
|
||||
"MSG_IPC_NOWAIT",
|
||||
"msg_queue_exists",
|
||||
"msg_receive",
|
||||
"msg_remove_queue",
|
||||
"msg_send",
|
||||
"msg_set_queue",
|
||||
"msg_stat_queue",
|
||||
"Threaded",
|
||||
"transform",
|
||||
"SysvMessageQueue"
|
||||
],
|
||||
"php-core-extensions": [
|
||||
"Core",
|
||||
"date",
|
||||
"pcre",
|
||||
"Phar",
|
||||
"Reflection",
|
||||
"SPL",
|
||||
"standard"
|
||||
]
|
||||
}
|
71
dependencies/amphp/sync/src/Barrier.php
vendored
Normal file
71
dependencies/amphp/sync/src/Barrier.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A barrier is a synchronization primitive.
|
||||
*
|
||||
* The barrier is initialized with a certain count, which can be increased and decreased until it reaches zero.
|
||||
*
|
||||
* A count of one can be used to block multiple coroutines until a certain condition is met.
|
||||
*
|
||||
* A count of N can be used to await multiple coroutines doing an action to complete.
|
||||
*
|
||||
* **Example**
|
||||
*
|
||||
* ```php
|
||||
* $barrier = new Amp\Sync\Barrier(2);
|
||||
* $barrier->arrive();
|
||||
* $barrier->arrive(); // promise returned from Barrier::await() is now resolved
|
||||
*
|
||||
* yield $barrier->await();
|
||||
* ```
|
||||
*/
|
||||
final class Barrier
|
||||
{
|
||||
/** @var int */
|
||||
private $count;
|
||||
/** @var Deferred */
|
||||
private $deferred;
|
||||
public function __construct(int $count)
|
||||
{
|
||||
if ($count < 1) {
|
||||
throw new \Error('Count must be positive, got ' . $count);
|
||||
}
|
||||
$this->count = $count;
|
||||
$this->deferred = new Deferred();
|
||||
}
|
||||
public function getCount() : int
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
public function arrive(int $count = 1) : void
|
||||
{
|
||||
if ($count < 1) {
|
||||
throw new \Error('Count must be at least 1, got ' . $count);
|
||||
}
|
||||
if ($count > $this->count) {
|
||||
throw new \Error('Count cannot be greater than remaining count: ' . $count . ' > ' . $this->count);
|
||||
}
|
||||
$this->count -= $count;
|
||||
if ($this->count === 0) {
|
||||
$this->deferred->resolve();
|
||||
}
|
||||
}
|
||||
public function register(int $count = 1) : void
|
||||
{
|
||||
if ($count < 1) {
|
||||
throw new \Error('Count must be at least 1, got ' . $count);
|
||||
}
|
||||
if ($this->count === 0) {
|
||||
throw new \Error('Can\'t increase count, because the barrier already broke');
|
||||
}
|
||||
$this->count += $count;
|
||||
}
|
||||
public function await() : Promise
|
||||
{
|
||||
return $this->deferred->promise();
|
||||
}
|
||||
}
|
146
dependencies/amphp/sync/src/ConcurrentIterator/functions.php
vendored
Normal file
146
dependencies/amphp/sync/src/ConcurrentIterator/functions.php
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync\ConcurrentIterator;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CancelledException;
|
||||
use WP_Ultimo\Dependencies\Amp\Iterator;
|
||||
use WP_Ultimo\Dependencies\Amp\Producer;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Barrier;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Lock;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Semaphore;
|
||||
use function WP_Ultimo\Dependencies\Amp\asyncCall;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
use function WP_Ultimo\Dependencies\Amp\coroutine;
|
||||
/**
|
||||
* Concurrently act on iterator values using {@code $processor}.
|
||||
*
|
||||
* @param Iterator $iterator Values to process.
|
||||
* @param Semaphore $semaphore Semaphore limiting the concurrency, e.g. {@code LocalSemaphore}
|
||||
* @param callable $processor Processing callable, which is run as coroutine. It should not throw any errors,
|
||||
* otherwise the entire operation is aborted.
|
||||
*
|
||||
* @return Iterator Result values.
|
||||
*/
|
||||
function transform(Iterator $iterator, Semaphore $semaphore, callable $processor) : Iterator
|
||||
{
|
||||
return new Producer(static function (callable $emit) use($iterator, $semaphore, $processor) {
|
||||
// one dummy item, because we can't start the barrier with a count of zero
|
||||
$barrier = new Barrier(1);
|
||||
/** @var \Throwable|null $error */
|
||||
$error = null;
|
||||
$locks = [];
|
||||
$gc = \false;
|
||||
$processor = coroutine($processor);
|
||||
$processor = static function (Lock $lock, $currentElement) use($processor, $emit, $barrier, &$locks, &$error, &$gc) {
|
||||
$done = \false;
|
||||
try {
|
||||
(yield $processor($currentElement, $emit));
|
||||
$done = \true;
|
||||
} catch (\Throwable $e) {
|
||||
$error = $error ?? $e;
|
||||
$done = \true;
|
||||
} finally {
|
||||
if (!$done) {
|
||||
$gc = \true;
|
||||
}
|
||||
unset($locks[$lock->getId()]);
|
||||
$lock->release();
|
||||
$barrier->arrive();
|
||||
}
|
||||
};
|
||||
while ((yield $iterator->advance())) {
|
||||
if ($error) {
|
||||
break;
|
||||
}
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $semaphore->acquire());
|
||||
if ($gc || isset($locks[$lock->getId()])) {
|
||||
// Throwing here causes a segfault on PHP 7.3
|
||||
return;
|
||||
// throw new CancelledException; // producer and locks have been GCed
|
||||
}
|
||||
$locks[$lock->getId()] = \true;
|
||||
$barrier->register();
|
||||
asyncCall($processor, $lock, $iterator->getCurrent());
|
||||
}
|
||||
$barrier->arrive();
|
||||
// remove dummy item
|
||||
(yield $barrier->await());
|
||||
if ($error) {
|
||||
throw $error;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Concurrently map all iterator values using {@code $processor}.
|
||||
*
|
||||
* The order of the items in the resulting iterator is not guaranteed in any way.
|
||||
*
|
||||
* @param Iterator $iterator Values to map.
|
||||
* @param Semaphore $semaphore Semaphore limiting the concurrency, e.g. {@code LocalSemaphore}
|
||||
* @param callable $processor Processing callable, which is run as coroutine. It should not throw any errors,
|
||||
* otherwise the entire operation is aborted.
|
||||
*
|
||||
* @return Iterator Mapped values.
|
||||
*/
|
||||
function map(Iterator $iterator, Semaphore $semaphore, callable $processor) : Iterator
|
||||
{
|
||||
$processor = coroutine($processor);
|
||||
return transform($iterator, $semaphore, static function ($value, callable $emit) use($processor) {
|
||||
$value = (yield $processor($value));
|
||||
(yield $emit($value));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Concurrently filter all iterator values using {@code $filter}.
|
||||
*
|
||||
* The order of the items in the resulting iterator is not guaranteed in any way.
|
||||
*
|
||||
* @param Iterator $iterator Values to map.
|
||||
* @param Semaphore $semaphore Semaphore limiting the concurrency, e.g. {@code LocalSemaphore}
|
||||
* @param callable $filter Processing callable, which is run as coroutine. It should not throw any errors,
|
||||
* otherwise the entire operation is aborted. Must resolve to a boolean, true to keep values in the resulting
|
||||
* iterator.
|
||||
*
|
||||
* @return Iterator Values, where {@code $filter} resolved to {@code true}.
|
||||
*/
|
||||
function filter(Iterator $iterator, Semaphore $semaphore, callable $filter) : Iterator
|
||||
{
|
||||
$filter = coroutine($filter);
|
||||
return transform($iterator, $semaphore, static function ($value, callable $emit) use($filter) {
|
||||
$keep = (yield $filter($value));
|
||||
if (!\is_bool($keep)) {
|
||||
throw new \TypeError(__NAMESPACE__ . '\\filter\'s callable must resolve to a boolean value, got ' . \gettype($keep));
|
||||
}
|
||||
if ($keep) {
|
||||
(yield $emit($value));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Concurrently invoke a callback on all iterator values using {@code $processor}.
|
||||
*
|
||||
* @param Iterator $iterator Values to act on.
|
||||
* @param Semaphore $semaphore Semaphore limiting the concurrency, e.g. {@code LocalSemaphore}
|
||||
* @param callable $processor Processing callable, which is run as coroutine. It should not throw any errors,
|
||||
* otherwise the entire operation is aborted.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
function each(Iterator $iterator, Semaphore $semaphore, callable $processor) : Promise
|
||||
{
|
||||
$processor = coroutine($processor);
|
||||
$iterator = transform($iterator, $semaphore, static function ($value, callable $emit) use($processor) {
|
||||
(yield $processor($value));
|
||||
(yield $emit(null));
|
||||
});
|
||||
// Use Amp\Iterator\discard in the future
|
||||
return call(static function () use($iterator) {
|
||||
$count = 0;
|
||||
while ((yield $iterator->advance())) {
|
||||
$count++;
|
||||
}
|
||||
return $count;
|
||||
});
|
||||
}
|
73
dependencies/amphp/sync/src/FileMutex.php
vendored
Normal file
73
dependencies/amphp/sync/src/FileMutex.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Coroutine;
|
||||
use WP_Ultimo\Dependencies\Amp\Delayed;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A cross-platform mutex that uses exclusive files as the lock mechanism.
|
||||
*
|
||||
* This mutex implementation is not always atomic and depends on the operating
|
||||
* system's implementation of file creation operations. Use this implementation
|
||||
* only if no other mutex types are available.
|
||||
*
|
||||
* This implementation avoids using [flock()](https://www.php.net/flock)
|
||||
* because flock() is known to have some atomicity issues on some systems. In
|
||||
* addition, flock() does not work as expected when trying to lock a file
|
||||
* multiple times in the same process on Linux. Instead, exclusive file creation
|
||||
* is used to create a lock file, which is atomic on most systems.
|
||||
*
|
||||
* @see https://www.php.net/fopen
|
||||
*/
|
||||
class FileMutex implements Mutex
|
||||
{
|
||||
public const LATENCY_TIMEOUT = 10;
|
||||
/** @var string The full path to the lock file. */
|
||||
private $fileName;
|
||||
/**
|
||||
* @param string $fileName Name of temporary file to use as a mutex.
|
||||
*/
|
||||
public function __construct(string $fileName)
|
||||
{
|
||||
$this->fileName = $fileName;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return new Coroutine($this->doAcquire());
|
||||
}
|
||||
/**
|
||||
* @coroutine
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function doAcquire() : \Generator
|
||||
{
|
||||
// Try to create the lock file. If the file already exists, someone else
|
||||
// has the lock, so set an asynchronous timer and try again.
|
||||
while (($handle = @\fopen($this->fileName, 'x')) === \false) {
|
||||
(yield new Delayed(self::LATENCY_TIMEOUT));
|
||||
}
|
||||
// Return a lock object that can be used to release the lock on the mutex.
|
||||
$lock = new Lock(0, function () : void {
|
||||
$this->release();
|
||||
});
|
||||
\fclose($handle);
|
||||
return $lock;
|
||||
}
|
||||
/**
|
||||
* Releases the lock on the mutex.
|
||||
*
|
||||
* @throws SyncException If the unlock operation failed.
|
||||
*/
|
||||
protected function release()
|
||||
{
|
||||
$success = @\unlink($this->fileName);
|
||||
if (!$success) {
|
||||
throw new SyncException('Failed to unlock the mutex file.');
|
||||
}
|
||||
}
|
||||
}
|
33
dependencies/amphp/sync/src/Internal/MutexStorage.php
vendored
Normal file
33
dependencies/amphp/sync/src/Internal/MutexStorage.php
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Delayed;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Lock;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
/** @internal */
|
||||
final class MutexStorage extends \Threaded
|
||||
{
|
||||
public const LATENCY_TIMEOUT = 10;
|
||||
/** @var bool */
|
||||
private $locked = \false;
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return call(function () : \Generator {
|
||||
$tsl = function () : bool {
|
||||
if ($this->locked) {
|
||||
return \true;
|
||||
}
|
||||
$this->locked = \true;
|
||||
return \false;
|
||||
};
|
||||
while ($this->locked || $this->synchronized($tsl)) {
|
||||
(yield new Delayed(self::LATENCY_TIMEOUT));
|
||||
}
|
||||
return new Lock(0, function () : void {
|
||||
$this->locked = \false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
55
dependencies/amphp/sync/src/Internal/SemaphoreStorage.php
vendored
Normal file
55
dependencies/amphp/sync/src/Internal/SemaphoreStorage.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync\Internal;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Delayed;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Sync\Lock;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
/** @internal */
|
||||
final class SemaphoreStorage extends \Threaded
|
||||
{
|
||||
public const LATENCY_TIMEOUT = 10;
|
||||
/**
|
||||
* Creates a new semaphore with a given number of locks.
|
||||
*
|
||||
* @param int $locks The maximum number of locks that can be acquired from the semaphore.
|
||||
*/
|
||||
public function __construct(int $locks)
|
||||
{
|
||||
foreach (\range(0, $locks - 1) as $lock) {
|
||||
$this[] = $lock;
|
||||
}
|
||||
}
|
||||
public function acquire() : Promise
|
||||
{
|
||||
/**
|
||||
* Uses a double locking mechanism to acquire a lock without blocking. A
|
||||
* synchronous mutex is used to make sure that the semaphore is queried one
|
||||
* at a time to preserve the integrity of the semaphore itself. Then a lock
|
||||
* count is used to check if a lock is available without blocking.
|
||||
*
|
||||
* If a lock is not available, we add the request to a queue and set a timer
|
||||
* to check again in the future.
|
||||
*/
|
||||
return call(function () : \Generator {
|
||||
$tsl = function () : ?int {
|
||||
// If there are no locks available or the wait queue is not empty,
|
||||
// we need to wait our turn to acquire a lock.
|
||||
if (!$this->count()) {
|
||||
return null;
|
||||
}
|
||||
return $this->shift();
|
||||
};
|
||||
while (!$this->count() || ($id = $this->synchronized($tsl)) === null) {
|
||||
(yield new Delayed(self::LATENCY_TIMEOUT));
|
||||
}
|
||||
return new Lock($id, function (Lock $lock) : void {
|
||||
$id = $lock->getId();
|
||||
$this->synchronized(function () use($id) {
|
||||
$this[] = $id;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
23
dependencies/amphp/sync/src/KeyedMutex.php
vendored
Normal file
23
dependencies/amphp/sync/src/KeyedMutex.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A non-blocking synchronization primitive that can be used for mutual exclusion across contexts based on keys.
|
||||
*
|
||||
* Objects that implement this interface should guarantee that all operations are atomic. Implementations do not have to
|
||||
* guarantee that acquiring a lock is first-come, first serve.
|
||||
*/
|
||||
interface KeyedMutex extends KeyedSemaphore
|
||||
{
|
||||
/**
|
||||
* Acquires a lock on the mutex.
|
||||
*
|
||||
* @param string $key Lock key
|
||||
*
|
||||
* @return Promise<Lock> Resolves with a lock object with an ID of 0. May fail with a SyncException
|
||||
* if an error occurs when attempting to obtain the lock (e.g. a shared memory segment closed).
|
||||
*/
|
||||
public function acquire(string $key) : Promise;
|
||||
}
|
24
dependencies/amphp/sync/src/KeyedSemaphore.php
vendored
Normal file
24
dependencies/amphp/sync/src/KeyedSemaphore.php
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A non-blocking counting semaphore based on keys.
|
||||
*
|
||||
* Objects that implement this interface should guarantee that all operations are atomic. Implementations do not have to
|
||||
* guarantee that acquiring a lock is first-come, first serve.
|
||||
*/
|
||||
interface KeyedSemaphore
|
||||
{
|
||||
/**
|
||||
* Acquires a lock on the semaphore.
|
||||
*
|
||||
* @param string $key Lock key
|
||||
*
|
||||
* @return Promise<Lock> Resolves with an integer keyed lock object. Identifiers returned by the
|
||||
* locks should be 0-indexed. Releasing an identifier MUST make that same identifier available. May fail with
|
||||
* a SyncException if an error occurs when attempting to obtain the lock (e.g. a shared memory segment closed).
|
||||
*/
|
||||
public function acquire(string $key) : Promise;
|
||||
}
|
31
dependencies/amphp/sync/src/LocalKeyedMutex.php
vendored
Normal file
31
dependencies/amphp/sync/src/LocalKeyedMutex.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class LocalKeyedMutex implements KeyedMutex
|
||||
{
|
||||
/** @var LocalMutex[] */
|
||||
private $mutex = [];
|
||||
/** @var int[] */
|
||||
private $locks = [];
|
||||
public function acquire(string $key) : Promise
|
||||
{
|
||||
if (!isset($this->mutex[$key])) {
|
||||
$this->mutex[$key] = new LocalMutex();
|
||||
$this->locks[$key] = 0;
|
||||
}
|
||||
return call(function () use($key) {
|
||||
$this->locks[$key]++;
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->mutex[$key]->acquire());
|
||||
return new Lock(0, function () use($lock, $key) {
|
||||
if (--$this->locks[$key] === 0) {
|
||||
unset($this->mutex[$key], $this->locks[$key]);
|
||||
}
|
||||
$lock->release();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
37
dependencies/amphp/sync/src/LocalKeyedSemaphore.php
vendored
Normal file
37
dependencies/amphp/sync/src/LocalKeyedSemaphore.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
final class LocalKeyedSemaphore implements KeyedSemaphore
|
||||
{
|
||||
/** @var LocalSemaphore[] */
|
||||
private $semaphore = [];
|
||||
/** @var int[] */
|
||||
private $locks = [];
|
||||
/** @var int */
|
||||
private $maxLocks;
|
||||
public function __construct(int $maxLocks)
|
||||
{
|
||||
$this->maxLocks = $maxLocks;
|
||||
}
|
||||
public function acquire(string $key) : Promise
|
||||
{
|
||||
if (!isset($this->semaphore[$key])) {
|
||||
$this->semaphore[$key] = new LocalSemaphore($this->maxLocks);
|
||||
$this->locks[$key] = 0;
|
||||
}
|
||||
return call(function () use($key) {
|
||||
$this->locks[$key]++;
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $this->semaphore[$key]->acquire());
|
||||
return new Lock(0, function () use($lock, $key) {
|
||||
if (--$this->locks[$key] === 0) {
|
||||
unset($this->semaphore[$key], $this->locks[$key]);
|
||||
}
|
||||
$lock->release();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
36
dependencies/amphp/sync/src/LocalMutex.php
vendored
Normal file
36
dependencies/amphp/sync/src/LocalMutex.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CallableMaker;
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
class LocalMutex implements Mutex
|
||||
{
|
||||
use CallableMaker;
|
||||
// kept for BC only
|
||||
/** @var bool */
|
||||
private $locked = \false;
|
||||
/** @var Deferred[] */
|
||||
private $queue = [];
|
||||
/** {@inheritdoc} */
|
||||
public function acquire() : Promise
|
||||
{
|
||||
if (!$this->locked) {
|
||||
$this->locked = \true;
|
||||
return new Success(new Lock(0, \Closure::fromCallable([$this, 'release'])));
|
||||
}
|
||||
$this->queue[] = $deferred = new Deferred();
|
||||
return $deferred->promise();
|
||||
}
|
||||
private function release() : void
|
||||
{
|
||||
if (!empty($this->queue)) {
|
||||
$deferred = \array_shift($this->queue);
|
||||
$deferred->resolve(new Lock(0, \Closure::fromCallable([$this, 'release'])));
|
||||
return;
|
||||
}
|
||||
$this->locked = \false;
|
||||
}
|
||||
}
|
43
dependencies/amphp/sync/src/LocalSemaphore.php
vendored
Normal file
43
dependencies/amphp/sync/src/LocalSemaphore.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\CallableMaker;
|
||||
use WP_Ultimo\Dependencies\Amp\Deferred;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use WP_Ultimo\Dependencies\Amp\Success;
|
||||
class LocalSemaphore implements Semaphore
|
||||
{
|
||||
use CallableMaker;
|
||||
// kept for BC only
|
||||
/** @var int[] */
|
||||
private $locks;
|
||||
/** @var Deferred[] */
|
||||
private $queue = [];
|
||||
public function __construct(int $maxLocks)
|
||||
{
|
||||
if ($maxLocks < 1) {
|
||||
throw new \Error('The number of locks must be greater than 0');
|
||||
}
|
||||
$this->locks = \range(0, $maxLocks - 1);
|
||||
}
|
||||
/** {@inheritdoc} */
|
||||
public function acquire() : Promise
|
||||
{
|
||||
if (!empty($this->locks)) {
|
||||
return new Success(new Lock(\array_shift($this->locks), \Closure::fromCallable([$this, 'release'])));
|
||||
}
|
||||
$this->queue[] = $deferred = new Deferred();
|
||||
return $deferred->promise();
|
||||
}
|
||||
private function release(Lock $lock) : void
|
||||
{
|
||||
$id = $lock->getId();
|
||||
if (!empty($this->queue)) {
|
||||
$deferred = \array_shift($this->queue);
|
||||
$deferred->resolve(new Lock($id, \Closure::fromCallable([$this, 'release'])));
|
||||
return;
|
||||
}
|
||||
$this->locks[] = $id;
|
||||
}
|
||||
}
|
68
dependencies/amphp/sync/src/Lock.php
vendored
Normal file
68
dependencies/amphp/sync/src/Lock.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
/**
|
||||
* A handle on an acquired lock from a synchronization object.
|
||||
*
|
||||
* This object is not thread-safe; after acquiring a lock from a mutex or
|
||||
* semaphore, the lock should reside in the same thread or process until it is
|
||||
* released.
|
||||
*/
|
||||
class Lock
|
||||
{
|
||||
/** @var callable|null The function to be called on release or null if the lock has been released. */
|
||||
private $releaser;
|
||||
/** @var int */
|
||||
private $id;
|
||||
/**
|
||||
* Creates a new lock permit object.
|
||||
*
|
||||
* @param int $id The lock identifier.
|
||||
* @param callable(self): void $releaser A function to be called upon release.
|
||||
*/
|
||||
public function __construct(int $id, callable $releaser)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->releaser = $releaser;
|
||||
}
|
||||
/**
|
||||
* Checks if the lock has already been released.
|
||||
*
|
||||
* @return bool True if the lock has already been released, otherwise false.
|
||||
*/
|
||||
public function isReleased() : bool
|
||||
{
|
||||
return !$this->releaser;
|
||||
}
|
||||
/**
|
||||
* @return int Lock identifier.
|
||||
*/
|
||||
public function getId() : int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
/**
|
||||
* Releases the lock. No-op if the lock has already been released.
|
||||
*/
|
||||
public function release()
|
||||
{
|
||||
if (!$this->releaser) {
|
||||
return;
|
||||
}
|
||||
// Invoke the releaser function given to us by the synchronization source
|
||||
// to release the lock.
|
||||
$releaser = $this->releaser;
|
||||
$this->releaser = null;
|
||||
$releaser($this);
|
||||
}
|
||||
/**
|
||||
* Releases the lock when there are no more references to it.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->isReleased()) {
|
||||
$this->release();
|
||||
}
|
||||
}
|
||||
}
|
21
dependencies/amphp/sync/src/Mutex.php
vendored
Normal file
21
dependencies/amphp/sync/src/Mutex.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A non-blocking synchronization primitive that can be used for mutual exclusion across contexts.
|
||||
*
|
||||
* Objects that implement this interface should guarantee that all operations are atomic. Implementations do not have to
|
||||
* guarantee that acquiring a lock is first-come, first serve.
|
||||
*/
|
||||
interface Mutex extends Semaphore
|
||||
{
|
||||
/**
|
||||
* Acquires a lock on the mutex.
|
||||
*
|
||||
* @return Promise<Lock> Resolves with a lock object with an ID of 0. May fail with a SyncException
|
||||
* if an error occurs when attempting to obtain the lock (e.g. a shared memory segment closed).
|
||||
*/
|
||||
public function acquire() : Promise;
|
||||
}
|
211
dependencies/amphp/sync/src/PosixSemaphore.php
vendored
Normal file
211
dependencies/amphp/sync/src/PosixSemaphore.php
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Coroutine;
|
||||
use WP_Ultimo\Dependencies\Amp\Delayed;
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A non-blocking, inter-process POSIX semaphore.
|
||||
*
|
||||
* Uses a POSIX message queue to store a queue of permits in a lock-free data structure. This semaphore implementation
|
||||
* is preferred over other implementations when available, as it provides the best performance.
|
||||
*
|
||||
* Not compatible with Windows.
|
||||
*/
|
||||
class PosixSemaphore implements Semaphore
|
||||
{
|
||||
public const LATENCY_TIMEOUT = 10;
|
||||
/** @var string */
|
||||
private $id;
|
||||
/** @var int The semaphore key. */
|
||||
private $key;
|
||||
/** @var int PID of the process that created the semaphore. */
|
||||
private $initializer = 0;
|
||||
/** @var resource A message queue of available locks. */
|
||||
private $queue;
|
||||
/**
|
||||
* Creates a new semaphore with a given ID and number of locks.
|
||||
*
|
||||
* @param string $id The unique name for the new semaphore.
|
||||
* @param int $maxLocks The maximum number of locks that can be acquired from the semaphore.
|
||||
* @param int $permissions Permissions to access the semaphore. Use file permission format specified as 0xxx.
|
||||
*
|
||||
* @return PosixSemaphore
|
||||
* @throws SyncException If the semaphore could not be created due to an internal error.
|
||||
*/
|
||||
public static function create(string $id, int $maxLocks, int $permissions = 0600) : self
|
||||
{
|
||||
if ($maxLocks < 1) {
|
||||
throw new \Error('Number of locks must be greater than 0');
|
||||
}
|
||||
$semaphore = new self($id);
|
||||
$semaphore->init($maxLocks, $permissions);
|
||||
return $semaphore;
|
||||
}
|
||||
/**
|
||||
* @param string $id The unique name of the semaphore to use.
|
||||
*
|
||||
* @return \Amp\Sync\PosixSemaphore
|
||||
*/
|
||||
public static function use(string $id) : self
|
||||
{
|
||||
$semaphore = new self($id);
|
||||
$semaphore->open();
|
||||
return $semaphore;
|
||||
}
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @throws \Error If the sysvmsg extension is not loaded.
|
||||
*/
|
||||
private function __construct(string $id)
|
||||
{
|
||||
if (!\extension_loaded("sysvmsg")) {
|
||||
throw new \Error(__CLASS__ . " requires the sysvmsg extension.");
|
||||
}
|
||||
$this->id = $id;
|
||||
$this->key = self::makeKey($this->id);
|
||||
}
|
||||
/**
|
||||
* Private method to prevent cloning.
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Prevent serialization.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
throw new \Error('A semaphore cannot be serialized!');
|
||||
}
|
||||
public function getId() : string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
private function open() : void
|
||||
{
|
||||
if (!\msg_queue_exists($this->key)) {
|
||||
throw new SyncException('No semaphore with that ID found');
|
||||
}
|
||||
$this->queue = \msg_get_queue($this->key);
|
||||
if (!$this->queue) {
|
||||
throw new SyncException('Failed to open the semaphore.');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param int $maxLocks The maximum number of locks that can be acquired from the semaphore.
|
||||
* @param int $permissions Permissions to access the semaphore.
|
||||
*
|
||||
* @throws SyncException If the semaphore could not be created due to an internal error.
|
||||
*/
|
||||
private function init(int $maxLocks, int $permissions) : void
|
||||
{
|
||||
if (\msg_queue_exists($this->key)) {
|
||||
throw new SyncException('A semaphore with that ID already exists');
|
||||
}
|
||||
$this->queue = \msg_get_queue($this->key, $permissions);
|
||||
if (!$this->queue) {
|
||||
throw new SyncException('Failed to create the semaphore.');
|
||||
}
|
||||
$this->initializer = \getmypid();
|
||||
// Fill the semaphore with locks.
|
||||
while (--$maxLocks >= 0) {
|
||||
$this->release($maxLocks);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Gets the access permissions of the semaphore.
|
||||
*
|
||||
* @return int A permissions mode.
|
||||
*/
|
||||
public function getPermissions() : int
|
||||
{
|
||||
$stat = \msg_stat_queue($this->queue);
|
||||
return $stat['msg_perm.mode'];
|
||||
}
|
||||
/**
|
||||
* Sets the access permissions of the semaphore.
|
||||
*
|
||||
* The current user must have access to the semaphore in order to change the permissions.
|
||||
*
|
||||
* @param int $mode A permissions mode to set.
|
||||
*
|
||||
* @throws SyncException If the operation failed.
|
||||
*/
|
||||
public function setPermissions(int $mode)
|
||||
{
|
||||
if (!\msg_set_queue($this->queue, ['msg_perm.mode' => $mode])) {
|
||||
throw new SyncException('Failed to change the semaphore permissions.');
|
||||
}
|
||||
}
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return new Coroutine($this->doAcquire());
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private function doAcquire() : \Generator
|
||||
{
|
||||
do {
|
||||
// Attempt to acquire a lock from the semaphore.
|
||||
if (@\msg_receive($this->queue, 0, $type, 1, $id, \false, \MSG_IPC_NOWAIT, $errno)) {
|
||||
// A free lock was found, so resolve with a lock object that can
|
||||
// be used to release the lock.
|
||||
return new Lock(\unpack("C", $id)[1], function (Lock $lock) {
|
||||
$this->release($lock->getId());
|
||||
});
|
||||
}
|
||||
// Check for unusual errors.
|
||||
if ($errno !== \MSG_ENOMSG) {
|
||||
throw new SyncException(\sprintf('Failed to acquire a lock; errno: %d', $errno));
|
||||
}
|
||||
} while ((yield new Delayed(self::LATENCY_TIMEOUT, \true)));
|
||||
}
|
||||
/**
|
||||
* Removes the semaphore if it still exists.
|
||||
*
|
||||
* @throws SyncException If the operation failed.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->initializer === 0 || $this->initializer !== \getmypid()) {
|
||||
return;
|
||||
}
|
||||
if (\PHP_VERSION_ID < 80000 && (!\is_resource($this->queue) || !\msg_queue_exists($this->key))) {
|
||||
return;
|
||||
}
|
||||
if (\PHP_VERSION_ID >= 80000 && (!$this->queue instanceof \SysvMessageQueue || !\msg_queue_exists($this->key))) {
|
||||
return;
|
||||
}
|
||||
\msg_remove_queue($this->queue);
|
||||
}
|
||||
/**
|
||||
* Releases a lock from the semaphore.
|
||||
*
|
||||
* @param int $id Lock identifier.
|
||||
*
|
||||
* @throws SyncException If the operation failed.
|
||||
*/
|
||||
protected function release(int $id)
|
||||
{
|
||||
if (!$this->queue) {
|
||||
return;
|
||||
// Queue already destroyed.
|
||||
}
|
||||
// Call send in non-blocking mode. If the call fails because the queue
|
||||
// is full, then the number of locks configured is too large.
|
||||
if (!@\msg_send($this->queue, 1, \pack("C", $id), \false, \false, $errno)) {
|
||||
if ($errno === \MSG_EAGAIN) {
|
||||
throw new SyncException('The semaphore size is larger than the system allows.');
|
||||
}
|
||||
throw new SyncException('Failed to release the lock.');
|
||||
}
|
||||
}
|
||||
private static function makeKey(string $id) : int
|
||||
{
|
||||
return \abs(\unpack("l", \md5($id, \true))[1]);
|
||||
}
|
||||
}
|
21
dependencies/amphp/sync/src/PrefixedKeyedMutex.php
vendored
Normal file
21
dependencies/amphp/sync/src/PrefixedKeyedMutex.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
final class PrefixedKeyedMutex implements KeyedMutex
|
||||
{
|
||||
/** @var KeyedMutex */
|
||||
private $mutex;
|
||||
/** @var string */
|
||||
private $prefix;
|
||||
public function __construct(KeyedMutex $mutex, string $prefix)
|
||||
{
|
||||
$this->mutex = $mutex;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
public function acquire(string $key) : Promise
|
||||
{
|
||||
return $this->mutex->acquire($this->prefix . $key);
|
||||
}
|
||||
}
|
21
dependencies/amphp/sync/src/PrefixedKeyedSemaphore.php
vendored
Normal file
21
dependencies/amphp/sync/src/PrefixedKeyedSemaphore.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
final class PrefixedKeyedSemaphore implements KeyedSemaphore
|
||||
{
|
||||
/** @var KeyedSemaphore */
|
||||
private $semaphore;
|
||||
/** @var string */
|
||||
private $prefix;
|
||||
public function __construct(KeyedSemaphore $semaphore, string $prefix)
|
||||
{
|
||||
$this->semaphore = $semaphore;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
public function acquire(string $key) : Promise
|
||||
{
|
||||
return $this->semaphore->acquire($this->prefix . $key);
|
||||
}
|
||||
}
|
22
dependencies/amphp/sync/src/Semaphore.php
vendored
Normal file
22
dependencies/amphp/sync/src/Semaphore.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A non-blocking counting semaphore.
|
||||
*
|
||||
* Objects that implement this interface should guarantee that all operations are atomic. Implementations do not have to
|
||||
* guarantee that acquiring a lock is first-come, first serve.
|
||||
*/
|
||||
interface Semaphore
|
||||
{
|
||||
/**
|
||||
* Acquires a lock on the semaphore.
|
||||
*
|
||||
* @return Promise<Lock> Resolves with an integer keyed lock object. Identifiers returned by the
|
||||
* locks should be 0-indexed. Releasing an identifier MUST make that same identifier available. May fail with
|
||||
* a SyncException if an error occurs when attempting to obtain the lock (e.g. a shared memory segment closed).
|
||||
*/
|
||||
public function acquire() : Promise;
|
||||
}
|
31
dependencies/amphp/sync/src/SemaphoreMutex.php
vendored
Normal file
31
dependencies/amphp/sync/src/SemaphoreMutex.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
class SemaphoreMutex implements Mutex
|
||||
{
|
||||
/** @var Semaphore */
|
||||
private $semaphore;
|
||||
/**
|
||||
* @param Semaphore $semaphore A semaphore with a single lock.
|
||||
*/
|
||||
public function __construct(Semaphore $semaphore)
|
||||
{
|
||||
$this->semaphore = $semaphore;
|
||||
}
|
||||
/** {@inheritdoc} */
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return call(function () : \Generator {
|
||||
/** @var \Amp\Sync\Lock $lock */
|
||||
$lock = (yield $this->semaphore->acquire());
|
||||
if ($lock->getId() !== 0) {
|
||||
$lock->release();
|
||||
throw new \Error("Cannot use a semaphore with more than a single lock");
|
||||
}
|
||||
return $lock;
|
||||
});
|
||||
}
|
||||
}
|
21
dependencies/amphp/sync/src/StaticKeyMutex.php
vendored
Normal file
21
dependencies/amphp/sync/src/StaticKeyMutex.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
final class StaticKeyMutex implements Mutex
|
||||
{
|
||||
/** @var KeyedMutex */
|
||||
private $mutex;
|
||||
/** @var string */
|
||||
private $key;
|
||||
public function __construct(KeyedMutex $mutex, string $key)
|
||||
{
|
||||
$this->mutex = $mutex;
|
||||
$this->key = $key;
|
||||
}
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return $this->mutex->acquire($this->key);
|
||||
}
|
||||
}
|
7
dependencies/amphp/sync/src/SyncException.php
vendored
Normal file
7
dependencies/amphp/sync/src/SyncException.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
class SyncException extends \Exception
|
||||
{
|
||||
}
|
31
dependencies/amphp/sync/src/ThreadedMutex.php
vendored
Normal file
31
dependencies/amphp/sync/src/ThreadedMutex.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* A thread-safe, asynchronous mutex using the pthreads locking mechanism.
|
||||
*
|
||||
* Compatible with POSIX systems and Microsoft Windows.
|
||||
*
|
||||
* @deprecated ext-pthreads development has been halted, see https://github.com/krakjoe/pthreads/issues/929
|
||||
*/
|
||||
class ThreadedMutex implements Mutex
|
||||
{
|
||||
/** @var Internal\MutexStorage */
|
||||
private $mutex;
|
||||
/**
|
||||
* Creates a new threaded mutex.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->mutex = new Internal\MutexStorage();
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return $this->mutex->acquire();
|
||||
}
|
||||
}
|
39
dependencies/amphp/sync/src/ThreadedSemaphore.php
vendored
Normal file
39
dependencies/amphp/sync/src/ThreadedSemaphore.php
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
/**
|
||||
* An asynchronous semaphore based on pthreads' synchronization methods.
|
||||
*
|
||||
* This is an implementation of a thread-safe semaphore that has non-blocking
|
||||
* acquire methods. There is a small tradeoff for asynchronous semaphores; you
|
||||
* may not acquire a lock immediately when one is available and there may be a
|
||||
* small delay. However, the small delay will not block the thread.
|
||||
*
|
||||
* @deprecated ext-pthreads development has been halted, see https://github.com/krakjoe/pthreads/issues/929
|
||||
*/
|
||||
class ThreadedSemaphore implements Semaphore
|
||||
{
|
||||
/** @var \Threaded */
|
||||
private $semaphore;
|
||||
/**
|
||||
* Creates a new semaphore with a given number of locks.
|
||||
*
|
||||
* @param int $locks The maximum number of locks that can be acquired from the semaphore.
|
||||
*/
|
||||
public function __construct(int $locks)
|
||||
{
|
||||
if ($locks < 1) {
|
||||
throw new \Error("The number of locks should be a positive integer");
|
||||
}
|
||||
$this->semaphore = new Internal\SemaphoreStorage($locks);
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function acquire() : Promise
|
||||
{
|
||||
return $this->semaphore->acquire();
|
||||
}
|
||||
}
|
29
dependencies/amphp/sync/src/functions.php
vendored
Normal file
29
dependencies/amphp/sync/src/functions.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace WP_Ultimo\Dependencies\Amp\Sync;
|
||||
|
||||
use WP_Ultimo\Dependencies\Amp\Promise;
|
||||
use function WP_Ultimo\Dependencies\Amp\call;
|
||||
/**
|
||||
* Invokes the given callback while maintaining a lock from the provided mutex. The lock is automatically released after
|
||||
* invoking the callback or once the promise returned by the callback is resolved. If the callback returns a Generator,
|
||||
* it will be run as a coroutine. See Amp\call().
|
||||
*
|
||||
* @param Mutex $mutex
|
||||
* @param callable $callback
|
||||
* @param array ...$args
|
||||
*
|
||||
* @return Promise Resolves with the return value of the callback.
|
||||
*/
|
||||
function synchronized(Mutex $mutex, callable $callback, ...$args) : Promise
|
||||
{
|
||||
return call(static function () use($mutex, $callback, $args) : \Generator {
|
||||
/** @var Lock $lock */
|
||||
$lock = (yield $mutex->acquire());
|
||||
try {
|
||||
return (yield call($callback, ...$args));
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user