diff --git a/README.md b/README.md index f90252cb..5611e34a 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ implementations or create/extend your own implementation. #### FlockMutex The **FlockMutex** is a lock implementation based on -[`flock()`](http://php.net/manual/en/function.flock.php). +[`flock()`](https://php.net/manual/en/function.flock.php). Example: ```php @@ -194,7 +194,7 @@ extension if possible or busy waiting if not. #### MemcachedMutex The **MemcachedMutex** is a spinlock implementation which uses the -[`Memcached` extension](http://php.net/manual/en/book.memcached.php). +[`Memcached` extension](https://php.net/manual/en/book.memcached.php). Example: ```php @@ -215,7 +215,7 @@ $mutex->synchronized(function () use ($bankAccount, $amount) { #### RedisMutex The **RedisMutex** is the distributed lock implementation of -[RedLock](http://redis.io/topics/distlock) which supports the +[RedLock](https://redis.io/topics/distlock#the-redlock-algorithm) which supports the [`phpredis` extension](https://github.com/phpredis/phpredis) or [`Predis` API](https://github.com/nrk/predis). @@ -244,7 +244,7 @@ $mutex->synchronized(function () use ($bankAccount, $amount) { #### SemaphoreMutex The **SemaphoreMutex** is a lock implementation based on -[Semaphore](http://php.net/manual/en/ref.sem.php). +[Semaphore](https://php.net/manual/en/ref.sem.php). Example: ```php @@ -330,9 +330,9 @@ Commercial support is available. This project is free and is licensed under the MIT. -[1]: http://semver.org/ +[1]: https://semver.org/ [2]: https://github.com/nrk/predis -[3]: http://php.net/manual/en/book.pcntl.php +[3]: https://php.net/manual/en/book.pcntl.php [4]: https://getcomposer.org/ [5]: https://github.com/php-lock/lock/blob/3ca295ccda/src/Mutex/Mutex.php#L15 [6]: https://github.com/php-lock/lock/blob/3ca295ccda/src/Mutex/Mutex.php#L38 diff --git a/src/Exception/ExecutionOutsideLockException.php b/src/Exception/ExecutionOutsideLockException.php index 54916eef..ed848365 100644 --- a/src/Exception/ExecutionOutsideLockException.php +++ b/src/Exception/ExecutionOutsideLockException.php @@ -9,8 +9,9 @@ /** * This exception should be thrown when for example the lock is released or the - * lock times out before the critical code has finished execution. This is a - * serious exception. Side effects might have happened while the critical code + * lock times out before the critical code has finished execution. + * + * This is a serious exception. Side effects might have happened while the critical code * was executed outside of the lock which should not be trusted to be valid. * * Should only be used in contexts where the lock is being released. diff --git a/src/Mutex/AbstractRedlockMutex.php b/src/Mutex/AbstractRedlockMutex.php index e0524ea5..5929e003 100644 --- a/src/Mutex/AbstractRedlockMutex.php +++ b/src/Mutex/AbstractRedlockMutex.php @@ -16,7 +16,9 @@ * * @template TClient of object * - * @see http://redis.io/topics/distlock + * @internal + * + * @see https://redis.io/topics/distlock#the-redlock-algorithm */ abstract class AbstractRedlockMutex extends AbstractSpinlockWithTokenMutex implements LoggerAwareInterface { diff --git a/src/Mutex/Mutex.php b/src/Mutex/Mutex.php index dbd95978..dbeb4a06 100644 --- a/src/Mutex/Mutex.php +++ b/src/Mutex/Mutex.php @@ -38,8 +38,7 @@ public function synchronized(callable $code); /** * Performs a double-checked locking pattern. * - * Call {@link \Malkusch\Lock\Util\DoubleCheckedLocking::then()} on the - * returned object. + * Call {@link DoubleCheckedLocking::then()} on the returned object. * * Example: * diff --git a/src/Mutex/MySQLMutex.php b/src/Mutex/MySQLMutex.php index 81460944..d21cd63a 100644 --- a/src/Mutex/MySQLMutex.php +++ b/src/Mutex/MySQLMutex.php @@ -27,7 +27,7 @@ public function __construct(\PDO $PDO, string $name, float $acquireTimeout = 0) $namePrefix = LockUtil::getInstance()->getKeyPrefix() . ':'; if (\strlen($name) > 64 - \strlen($namePrefix)) { - throw new \InvalidArgumentException('The maximum length of the lock name is ' . (64 - \strlen($namePrefix)) . ' characters'); + throw new \InvalidArgumentException('The maximum length of the database lock name is ' . (64 - \strlen($namePrefix)) . ' characters'); } $this->name = $namePrefix . $name; diff --git a/src/Mutex/NoMutex.php b/src/Mutex/NullMutex.php similarity index 50% rename from src/Mutex/NoMutex.php rename to src/Mutex/NullMutex.php index 881dd1ef..56830ec4 100644 --- a/src/Mutex/NoMutex.php +++ b/src/Mutex/NullMutex.php @@ -5,12 +5,9 @@ namespace Malkusch\Lock\Mutex; /** - * This mutex doesn't lock at all. - * - * Synchronization is not provided! This mutex is just implementing the - * interface without locking. + * This mutex does not lock at all. */ -class NoMutex extends AbstractMutex +class NullMutex extends AbstractMutex { #[\Override] public function synchronized(callable $code) diff --git a/src/Mutex/RedisMutex.php b/src/Mutex/RedisMutex.php index 19caf68d..b154ace2 100644 --- a/src/Mutex/RedisMutex.php +++ b/src/Mutex/RedisMutex.php @@ -17,7 +17,7 @@ * * @extends AbstractRedlockMutex * - * @see http://redis.io/topics/distlock + * @see https://redis.io/topics/distlock#the-redlock-algorithm */ class RedisMutex extends AbstractRedlockMutex { @@ -122,7 +122,7 @@ protected function evalScript(object $client, string $luaScript, array $keys, ar } } else { try { - return $client->eval($luaScript, count($keys), ...[...$keys, ...$arguments]); + return $client->eval($luaScript, count($keys), ...$keys, ...$arguments); } catch (PredisException $e) { throw new LockReleaseException('Failed to release lock', 0, $e); } diff --git a/src/Mutex/SemaphoreMutex.php b/src/Mutex/SemaphoreMutex.php index 80d3f3cf..a1f4bece 100644 --- a/src/Mutex/SemaphoreMutex.php +++ b/src/Mutex/SemaphoreMutex.php @@ -12,11 +12,11 @@ */ class SemaphoreMutex extends AbstractLockMutex { - /** @var \SysvSemaphore|resource The semaphore id */ + /** @var \SysvSemaphore|resource */ private $semaphore; /** - * Use {@link sem_get()} to create the semaphore id. + * Use {@link sem_get()} to create the semaphore. * * Example: * @@ -24,7 +24,7 @@ class SemaphoreMutex extends AbstractLockMutex * $mutex = new SemaphoreMutex($semaphore); * * - * @param \SysvSemaphore|resource $semaphore The semaphore id + * @param \SysvSemaphore|resource $semaphore */ public function __construct($semaphore) { diff --git a/src/Util/DoubleCheckedLocking.php b/src/Util/DoubleCheckedLocking.php index 51deabdd..d45f1794 100644 --- a/src/Util/DoubleCheckedLocking.php +++ b/src/Util/DoubleCheckedLocking.php @@ -12,7 +12,7 @@ * The double-checked locking pattern. * * You should not instantiate this class directly. Use - * {@link \Malkusch\Lock\Mutex\Mutex::check()}. + * {@link Mutex::check()}. * * @internal */ @@ -24,9 +24,8 @@ class DoubleCheckedLocking private $check; /** - * @param Mutex $mutex Provides methods for exclusive code execution - * @param callable(): bool $check Callback that decides if the lock should be acquired and if the critical code - * callback should be executed after acquiring the lock + * @param callable(): bool $check Callback that decides if the lock should be acquired and is rechecked + * after a lock has been acquired */ public function __construct(Mutex $mutex, callable $check) { @@ -35,17 +34,12 @@ public function __construct(Mutex $mutex, callable $check) } /** - * Executes a synchronized callback only after the check callback passes - * before and after acquiring the lock. - * - * If then returns boolean boolean false, the check did not pass before or - * after acquiring the lock. A boolean false can also be returned from the - * critical code callback to indicate that processing did not occure or has - * failed. It is up to the user to decide the last point. + * Executes a block of code only after the check callback passes + * before and after acquiring a lock. * * @template T * - * @param callable(): T $code The critical code callback + * @param callable(): T $code * * @return T|false False if check did not pass * diff --git a/src/Util/Loop.php b/src/Util/Loop.php index ffae548d..5a2cca42 100644 --- a/src/Util/Loop.php +++ b/src/Util/Loop.php @@ -23,7 +23,7 @@ class Loop private bool $looping = false; /** - * Notifies that this was the last iteration. + * Notify that this was the last iteration. */ public function end(): void { @@ -35,7 +35,7 @@ public function end(): void * * The code has to be designed in a way that it can be repeated without any * side effects. When execution was successful it should notify that event - * by calling {@link \Malkusch\Lock\Util\Loop::end()}. I.e. the only side + * by calling {@link Loop::end()}. I.e. the only side * effects of the code may happen after a successful execution. * * If the code throws an exception it will stop repeating the execution. diff --git a/tests/Mutex/FlockMutexTest.php b/tests/Mutex/FlockMutexTest.php index 164b78ee..63c70437 100644 --- a/tests/Mutex/FlockMutexTest.php +++ b/tests/Mutex/FlockMutexTest.php @@ -11,6 +11,7 @@ use Malkusch\Lock\Util\LockUtil; use Malkusch\Lock\Util\PcntlTimeout; use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; class FlockMutexTest extends TestCase @@ -18,8 +19,7 @@ class FlockMutexTest extends TestCase /** @var FlockMutex */ private $mutex; - /** @var string */ - private $file; + private string $file; #[\Override] protected function setUp(): void @@ -88,10 +88,17 @@ static function () { */ public static function provideTimeoutableStrategiesCases(): iterable { - yield [\Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()]; + if (extension_loaded('pcntl')) { + yield [\Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)()]; + } + yield [\Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)()]; } + /** + * @requires extension pcntl + */ + #[RequiresPhpExtension('pcntl')] public function testNoTimeoutWaitsForever(): void { $this->expectException(DeadlineException::class); diff --git a/tests/Mutex/MutexConcurrencyTest.php b/tests/Mutex/MutexConcurrencyTest.php index cf31834c..0477eda4 100644 --- a/tests/Mutex/MutexConcurrencyTest.php +++ b/tests/Mutex/MutexConcurrencyTest.php @@ -159,13 +159,15 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable return new FlockMutex($file, $timeout); }]; - yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename): Mutex { - $file = fopen($filename, 'w'); - $lock = Liberator::liberate(new FlockMutex($file, $timeout)); - $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)(); // @phpstan-ignore property.notFound + if (extension_loaded('pcntl')) { + yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename): Mutex { + $file = fopen($filename, 'w'); + $lock = Liberator::liberate(new FlockMutex($file, $timeout)); + $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)(); // @phpstan-ignore property.notFound - return $lock->popsValue(); - }]; + return $lock->popsValue(); + }]; + } yield 'flockWithTimoutLoop' => [static function ($timeout) use ($filename): Mutex { $file = fopen($filename, 'w'); diff --git a/tests/Mutex/MutexTest.php b/tests/Mutex/MutexTest.php index 42e82421..ca4eb18b 100644 --- a/tests/Mutex/MutexTest.php +++ b/tests/Mutex/MutexTest.php @@ -11,7 +11,7 @@ use Malkusch\Lock\Mutex\MemcachedMutex; use Malkusch\Lock\Mutex\Mutex; use Malkusch\Lock\Mutex\MySQLMutex; -use Malkusch\Lock\Mutex\NoMutex; +use Malkusch\Lock\Mutex\NullMutex; use Malkusch\Lock\Mutex\PostgreSQLMutex; use Malkusch\Lock\Mutex\RedisMutex; use Malkusch\Lock\Mutex\SemaphoreMutex; @@ -46,8 +46,8 @@ public static function setUpBeforeClass(): void */ public static function provideMutexFactoriesCases(): iterable { - yield 'NoMutex' => [static function (): Mutex { - return new NoMutex(); + yield 'NullMutex' => [static function (): Mutex { + return new NullMutex(); }]; yield 'FlockMutex' => [static function (): Mutex { @@ -56,13 +56,15 @@ public static function provideMutexFactoriesCases(): iterable return new FlockMutex($file); }]; - yield 'flockWithTimoutPcntl' => [static function (): Mutex { - $file = fopen(vfsStream::url('test/lock'), 'w'); - $lock = Liberator::liberate(new FlockMutex($file, 3)); - $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)(); // @phpstan-ignore property.notFound + if (extension_loaded('pcntl')) { + yield 'flockWithTimoutPcntl' => [static function (): Mutex { + $file = fopen(vfsStream::url('test/lock'), 'w'); + $lock = Liberator::liberate(new FlockMutex($file, 3)); + $lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)(); // @phpstan-ignore property.notFound - return $lock->popsValue(); - }]; + return $lock->popsValue(); + }]; + } yield 'flockWithTimoutLoop' => [static function (): Mutex { $file = fopen(vfsStream::url('test/lock'), 'w'); diff --git a/tests/Util/LoopTest.php b/tests/Util/LoopTest.php index 2b93523f..9fb020b5 100644 --- a/tests/Util/LoopTest.php +++ b/tests/Util/LoopTest.php @@ -64,12 +64,10 @@ public static function provideInvalidAcquireTimeoutCases(): iterable } /** - * Tests execution within the timeout. - * * @doesNotPerformAssertions */ #[DoesNotPerformAssertions] - public function testExecutionWithinTimeout(): void + public function testExecutionWithinAcquireTimeout(): void { $loop = new Loop(); $loop->execute(static function () use ($loop): void { @@ -78,10 +76,7 @@ public function testExecutionWithinTimeout(): void }, 0.5); } - /** - * Tests execution within the timeout without calling end(). - */ - public function testExecutionWithinAcquireTimeoutWithoutExplicitEnd(): void + public function testExecutionWithinAcquireTimeoutWithoutCallingEnd(): void { $this->expectException(LockAcquireTimeoutException::class); $this->expectExceptionMessage('Lock acquire timeout of 0.5 seconds has been exceeded'); @@ -93,12 +88,10 @@ public function testExecutionWithinAcquireTimeoutWithoutExplicitEnd(): void } /** - * Tests exceeding the execution timeout. - * * @doesNotPerformAssertions */ #[DoesNotPerformAssertions] - public function testExceedTimeoutIsAcceptableIfEndWasCalled(): void + public function testExceedAcquireTimeoutIsAcceptableIfEndWasCalled(): void { $loop = new Loop(); $loop->execute(static function () use ($loop): void { @@ -107,10 +100,7 @@ public function testExceedTimeoutIsAcceptableIfEndWasCalled(): void }, 0.5); } - /** - * Tests exceeding the execution timeout without calling end(). - */ - public function testExceedAcquireTimeoutWithoutExplicitEnd(): void + public function testExceedAcquireTimeoutWithoutCallingEnd(): void { $this->expectException(LockAcquireTimeoutException::class); $this->expectExceptionMessage('Lock acquire timeout of 0.5 seconds has been exceeded'); @@ -121,9 +111,6 @@ public function testExceedAcquireTimeoutWithoutExplicitEnd(): void }, 0.5); } - /** - * Tests that an exception would stop any further iteration. - */ public function testExceptionStopsIteration(): void { $this->expectException(\DomainException::class); @@ -134,10 +121,7 @@ public function testExceptionStopsIteration(): void }, 1); } - /** - * Tests end() will stop the iteration and return the result. - */ - public function testEnd(): void + public function testEndCodeExecutedOnce(): void { $i = 0; $loop = new Loop(); @@ -148,10 +132,7 @@ public function testEnd(): void self::assertSame(1, $i); } - /** - * Tests that the code is executed more times. - */ - public function testIteration(): void + public function testEndCodeExecutedTwice(): void { $i = 0; $loop = new Loop();