Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

71
dependencies/amphp/sync/src/Barrier.php vendored Normal file
View 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();
}
}

View 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;
});
}

View 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.');
}
}
}

View 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;
});
});
}
}

View 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;
});
});
});
}
}

View 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;
}

View 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;
}

View 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();
});
});
}
}

View 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();
});
});
}
}

View 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;
}
}

View 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
View 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
View 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;
}

View 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]);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}

View 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;
});
}
}

View 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);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace WP_Ultimo\Dependencies\Amp\Sync;
class SyncException extends \Exception
{
}

View 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();
}
}

View 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();
}
}

View 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();
}
});
}