Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b28ae9a
Update composer.json
vedavith Jan 23, 2026
8aab350
Update test-unit.yml
vedavith Jan 23, 2026
4a4329a
test_workflow.yml
vedavith Jan 23, 2026
01af359
Update workflow.yml
vedavith Jan 23, 2026
dabbcb1
Create workflow.yml
vedavith Jan 23, 2026
6c169e2
Updated composer.json to support php8.5
vedavith Jan 23, 2026
71df896
Update composer.json to support 8.5
vedavith Jan 23, 2026
55d5b2e
Update composer.json
vedavith Jan 23, 2026
0263dcf
Update workflow.yml
vedavith Jan 23, 2026
23a35ab
Delete .github/workflows/workflow.yml
vedavith Jan 23, 2026
b37e49c
Update test-unit.yml
vedavith Jan 23, 2026
c9a2cbc
Update composer.json
vedavith Jan 23, 2026
23c345d
Update composer.json - php85
vedavith Jan 23, 2026
044a97b
Update composer.json
vedavith Jan 23, 2026
09f2573
Update composer.json - eloquent/liberator
vedavith Jan 23, 2026
fcb17df
Update composer.json
vedavith Jan 23, 2026
c1845a1
Update composer.json - Backward compatibility
vedavith Jan 23, 2026
75a7671
Merge branch 'master' into patch-1
vedavith Jan 24, 2026
49452c6
Prevent INF/NAN from being passed to acquireMutex
vedavith Jan 24, 2026
2f5653f
Updated composer - eloquent
vedavith Jan 24, 2026
355875c
Updated files:
vedavith Jan 24, 2026
6337b31
Refactor formatTimeout to simplify NaN and infinite value handling
vedavith Jan 24, 2026
ff26db6
Refactor tests to remove dependency on Eloquent Liberator and introdu…
vedavith Jan 27, 2026
3a3bed1
Refactor TestAccess class to remove static methods and streamline pro…
vedavith Jan 27, 2026
daebb8a
Add namespace declaration to TestAccess class
vedavith Jan 27, 2026
070f2d5
Refactor tests to use instance of TestAccess for property access
vedavith Jan 27, 2026
6a0e20e
Refactor TestAccess class for improved readability and consistency
vedavith Jan 27, 2026
ef9cd52
Remove version constraint for spatie/async in composer.json
vedavith Jan 27, 2026
4a00e6a
Update phpunit version constraint in composer.json to support newer v…
vedavith Jan 27, 2026
3783124
Update PHP version constraints in composer.json and test-unit.yml to …
vedavith Jan 27, 2026
f9ab21a
Enhance formatTimeout to handle NaN and infinities
vedavith Jan 30, 2026
05ff89c
Refactored TestAccess
vedavith Feb 1, 2026
0ce53d3
Merge branch 'patch-1' of github.com:vedavith/lock into patch-1
vedavith Feb 1, 2026
cc35223
Updated TestAccess class
vedavith Feb 1, 2026
5b51cd6
Updated TestAccess.php
vedavith Feb 1, 2026
9909553
Calling PopsValue from TestAccess
vedavith Feb 1, 2026
dd99643
Refactor MutexConcurrencyTest to simplify TestAccess usage and enhanc…
vedavith Feb 2, 2026
09c825d
Refactor MutexConcurrencyTest: Remove unused imports, adjust type ann…
vedavith Feb 2, 2026
5ba7f99
Refactor FlockMutexTest: Update TestAccess usage for setting private …
vedavith Feb 2, 2026
5cdaa00
Refactor FlockMutexTest: Simplify TestAccess usage and adjust annotat…
vedavith Feb 2, 2026
8572d2f
Refactor tests: Replace `IsType::TYPE_*` constants with strings and r…
vedavith Feb 2, 2026
e4430e1
Refactor tests: Use `NativeType` constants in `IsType` assertions for…
vedavith Feb 2, 2026
7309015
Update test workflow: Add PHP 8.4 to matrix
vedavith Feb 7, 2026
7dca492
Refactor MutexConcurrencyTest: Simplify TestAccess usage, remove redu…
vedavith Feb 7, 2026
e46fc81
Refactor tests: Replace `IsType` usage with `TestAccess::phpunitIsTyp…
vedavith Feb 7, 2026
e232bc3
Refactor tests: Clean up imports, adjust annotations, and simplify `T…
vedavith Feb 7, 2026
f3ad4b0
Add cweagans/composer-patches for patch management and configure spat…
vedavith Feb 7, 2026
de2d80e
Add cweagans/composer-patches for patch management and configure spat…
vedavith Feb 7, 2026
cc642bb
Merge remote-tracking branch 'origin/patch-1' into patch-1
vedavith Feb 7, 2026
0e8dfae
Add cweagans/composer-patches for patch management and configure spat…
vedavith Feb 7, 2026
73ad986
Add cweagans/composer-patches for patch management and configure spat…
vedavith Feb 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
php: ['8.4', '8.5']
type: ['Phpunit', 'Phpunit Lowest']
include:
- php: 'latest'
Expand Down
14 changes: 11 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
],
"homepage": "https://github.com/malkusch/lock",
"require": {
"php": ">=7.4 <8.5",
"php": ">=8.2 <8.6",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/polyfill-php80": "^1.28"
},
"require-dev": {
"cweagans/composer-patches": "^1.7",
"ext-igbinary": "*",
"ext-lzf": "*",
"ext-memcached": "*",
Expand All @@ -47,7 +48,6 @@
"ext-pdo_sqlite": "*",
"ext-redis": "*",
"ext-sysvsem": "*",
"eloquent/liberator": "^2.0 || ^3.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this change. Please send it as an extra PR.

Then this PR for PHP 8.5 would be hugely simplified and much more readable.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please help me with a method popsValue this is not defined anywhere. Calling this method is failing the testcases. @mvorisek

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ergebnis/composer-normalize": "^2.13",
"ergebnis/phpunit-slow-test-detector": "^2.9",
"friendsofphp/php-cs-fixer": "^3.0",
Expand All @@ -57,7 +57,7 @@
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.5.25 || ^10.0 || ^11.0",
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
"predis/predis": "^1.1.8 || ^2.0",
"spatie/async": "^1.5"
},
Expand Down Expand Up @@ -86,9 +86,17 @@
},
"config": {
"allow-plugins": {
"cweagans/composer-patches": true,
"ergebnis/composer-normalize": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"extra": {
"patches": {
"spatie/async": {
"Fix ArrayAccess offsetExists signature": "patches/spatie-async-offsetexists.patch"
}
}
}
}
12 changes: 12 additions & 0 deletions patches/spatie-async-offsetexists.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/src/Pool.php b/src/Pool.php
index 3ed9c41..4d1f7c8 100644
--- a/src/Pool.php
+++ b/src/Pool.php
@@ -248,7 +248,7 @@ class Pool implements ArrayAccess, IteratorAggregate
$this->failed[$process->getPid()] = $process;
}

- public function offsetExists($offset): bool
+ public function offsetExists(mixed $offset): bool
{
// TODO
8 changes: 7 additions & 1 deletion src/Mutex/DistributedMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ protected function acquireWithToken(string $key, float $expireTimeout)
$exception = null;
foreach ($this->getMutexesInRandomOrder() as $index => $mutex) {
try {
if ($this->acquireMutex($mutex, $key, $acquireTimeout - (microtime(true) - $startTs), $expireTimeout)) {
$remainingTimeout = $acquireTimeout - (microtime(true) - $startTs);
// Prevent INF/NAN from being passed to acquireMutex
if (is_infinite($remainingTimeout) || is_nan($remainingTimeout) || $remainingTimeout < 0) {
$remainingTimeout = 0.0;
}

if ($this->acquireMutex($mutex, $key, $remainingTimeout, $expireTimeout)) {
$acquiredIndexes[] = $index;
}
} catch (LockAcquireException $exception) {
Expand Down
13 changes: 11 additions & 2 deletions src/Util/LockUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,20 @@ public function castFloatToInt(float $value): int
*/
public function formatTimeout(float $value): string
{
// Handle NaN explicitly (normalize to a safe numeric string)
if (\is_nan($value)) {
return 'NAN';
}

// Handle infinities explicitly
if (!\is_finite($value)) {
return $value > 0 ? 'INF' : '-INF';
}

$res = (string) round($value, 6);
if (\is_finite($value) && strpos($res, '.') === false) {
if (strpos($res, '.') === false) {
$res .= '.0';
}

return $res;
}
}
23 changes: 11 additions & 12 deletions tests/Mutex/FlockMutexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace Malkusch\Lock\Tests\Mutex;

use Eloquent\Liberator\Liberator;
require_once __DIR__ . '/../TestAccess.php';
use Malkusch\Lock\Exception\DeadlineException;
use Malkusch\Lock\Exception\LockAcquireTimeoutException;
use Malkusch\Lock\Mutex\FlockMutex;
use Malkusch\Lock\Tests\TestAccess;
use Malkusch\Lock\Util\LockUtil;
use Malkusch\Lock\Util\PcntlTimeout;
use PHPUnit\Framework\Attributes\DataProvider;
Expand All @@ -28,7 +29,7 @@ protected function setUp(): void

$this->file = LockUtil::getInstance()->makeRandomTemporaryFilePath('flock');
touch($this->file);
$this->mutex = Liberator::liberate(new FlockMutex(fopen($this->file, 'r'), 1)); // @phpstan-ignore assign.propertyType
$this->mutex = new FlockMutex(fopen($this->file, 'r'), 1);
}

#[\Override]
Expand All @@ -40,14 +41,12 @@ protected function tearDown(): void
}

/**
* @param FlockMutex::STRATEGY_* $strategy
*
* @dataProvider provideTimeoutableStrategiesCases
* @throws \Throwable
*/
#[DataProvider('provideTimeoutableStrategiesCases')]
public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void
{
$this->mutex->strategy = $strategy; // @phpstan-ignore property.private
(new TestAccess($this->mutex))->setProperty('strategy', $strategy);

self::assertTrue($this->mutex->synchronized(static function () { // @phpstan-ignore staticMethod.alreadyNarrowedType
usleep(1100 * 1000);
Expand All @@ -57,17 +56,15 @@ public function testCodeExecutedOutsideLockIsNotThrown(string $strategy): void
}

/**
* @param FlockMutex::STRATEGY_* $strategy
*
* @dataProvider provideTimeoutableStrategiesCases
* @throws \Throwable
*/
#[DataProvider('provideTimeoutableStrategiesCases')]
public function testAcquireTimeoutOccurs(string $strategy): void
{
$anotherResource = fopen($this->file, 'r');
flock($anotherResource, \LOCK_EX);

$this->mutex->strategy = $strategy; // @phpstan-ignore property.private
(new TestAccess($this->mutex))->setProperty('strategy', $strategy);

$this->expectException(LockAcquireTimeoutException::class);
$this->expectExceptionMessage('Lock acquire timeout of 1.0 seconds has been exceeded');
Expand Down Expand Up @@ -101,8 +98,10 @@ public function testNoTimeoutWaitsForever(): void
$anotherResource = fopen($this->file, 'r');
flock($anotherResource, \LOCK_EX);

$this->mutex->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_BLOCK, null, FlockMutex::class)(); // @phpstan-ignore property.private

(new TestAccess($this->mutex))->setProperty(
'strategy',
\Closure::bind(static fn () => FlockMutex::STRATEGY_BLOCK, null, FlockMutex::class)()
);
$timebox = new PcntlTimeout(1);

$this->expectException(DeadlineException::class);
Expand Down
22 changes: 9 additions & 13 deletions tests/Mutex/MutexConcurrencyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Malkusch\Lock\Tests\Mutex;

use Eloquent\Liberator\Liberator;
require_once __DIR__ . '/../TestAccess.php';
use Malkusch\Lock\Mutex\DistributedMutex;
use Malkusch\Lock\Mutex\FlockMutex;
use Malkusch\Lock\Mutex\MemcachedMutex;
Expand All @@ -13,9 +13,9 @@
use Malkusch\Lock\Mutex\PostgreSQLMutex;
use Malkusch\Lock\Mutex\RedisMutex;
use Malkusch\Lock\Mutex\SemaphoreMutex;
use Malkusch\Lock\Tests\TestAccess;
use Malkusch\Lock\Util\LockUtil;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\TestCase;
use Predis\Client as PredisClient;
use Spatie\Async\Pool;
Expand Down Expand Up @@ -66,8 +66,6 @@ private function fork(int $concurrency, \Closure $code): void
* @param \Closure(0|1): int $code The counter code
* @param \Closure(float): Mutex $mutexFactory
* @param \Closure(): void $setUp
*
* @dataProvider provideHighContentionCases
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to keep these annotations for PHP 7.4...

*/
#[DataProvider('provideHighContentionCases')]
public function testHighContention(\Closure $code, \Closure $mutexFactory, ?\Closure $setUp = null): void
Expand Down Expand Up @@ -124,8 +122,6 @@ static function () use ($filename) {
* Tests that five processes run sequentially.
*
* @param \Closure(float): Mutex $mutexFactory
*
* @dataProvider provideExecutionIsSerializedWhenLockedCases
*/
#[DataProvider('provideExecutionIsSerializedWhenLockedCases')]
public function testExecutionIsSerializedWhenLocked(\Closure $mutexFactory): void
Expand Down Expand Up @@ -163,19 +159,19 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable
if (extension_loaded('pcntl')) {
yield 'flockWithTimoutPcntl' => [static function ($timeout) use ($filename) {
$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
$lock = new FlockMutex($file, $timeout);
(new TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)());

return $lock->popsValue();
return (new TestAccess($lock))->popsValue();
}];
}

yield 'flockWithTimoutLoop' => [static function ($timeout) use ($filename) {
$file = fopen($filename, 'w');
$lock = Liberator::liberate(new FlockMutex($file, $timeout));
$lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)(); // @phpstan-ignore property.notFound
$lock = new FlockMutex($file, $timeout);
(new TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)());

return $lock->popsValue();
return (new TestAccess($lock))->popsValue();
}];

if (extension_loaded('sysvsem')) {
Expand All @@ -185,7 +181,7 @@ public static function provideExecutionIsSerializedWhenLockedCases(): iterable
$semaphore,
self::logicalOr(
self::isInstanceOf(\SysvSemaphore::class),
new IsType(IsType::TYPE_RESOURCE)
TestAccess::phpunitIsType('resource')
)
);

Expand Down
15 changes: 8 additions & 7 deletions tests/Mutex/MutexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Malkusch\Lock\Tests\Mutex;

use Eloquent\Liberator\Liberator;
require_once __DIR__ . '/../TestAccess.php';
use Malkusch\Lock\Mutex\AbstractLockMutex;
use Malkusch\Lock\Mutex\AbstractSpinlockMutex;
use Malkusch\Lock\Mutex\DistributedMutex;
Expand All @@ -16,6 +16,7 @@
use Malkusch\Lock\Mutex\PostgreSQLMutex;
use Malkusch\Lock\Mutex\RedisMutex;
use Malkusch\Lock\Mutex\SemaphoreMutex;
use Malkusch\Lock\Tests\TestAccess;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
Expand Down Expand Up @@ -114,19 +115,19 @@ public static function provideMutexFactoriesCases(): iterable
if (extension_loaded('pcntl')) {
yield 'flockWithTimoutPcntl' => [static function () {
$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
$lock = new FlockMutex($file, 3);
(new TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)());

return $lock->popsValue();
return (new TestAccess($lock))->popsValue();
}];
}

yield 'flockWithTimoutLoop' => [static function () {
$file = fopen(vfsStream::url('test/lock'), 'w');
$lock = Liberator::liberate(new FlockMutex($file, 3));
$lock->strategy = \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)(); // @phpstan-ignore property.notFound
$lock = new FlockMutex($file, 3);
(new TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)());

return $lock->popsValue();
return (new TestAccess($lock))->popsValue();
}];

if (extension_loaded('sysvsem')) {
Expand Down
14 changes: 7 additions & 7 deletions tests/Mutex/PostgreSQLMutexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

namespace Malkusch\Lock\Tests\Mutex;

use Eloquent\Liberator\Liberator;
use Malkusch\Lock\Exception\LockAcquireTimeoutException;
use Malkusch\Lock\Mutex\PostgreSQLMutex;
use PHPUnit\Framework\Constraint\IsType;
use Malkusch\Lock\Tests\TestAccess;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

Expand All @@ -26,7 +25,8 @@ protected function setUp(): void

$this->pdo = $this->createMock(\PDO::class);

$this->mutex = Liberator::liberate(new PostgreSQLMutex($this->pdo, 'test-one-negative-key')); // @phpstan-ignore assign.propertyType
$this->mutex = new PostgreSQLMutex($this->pdo, 'test-one-negative-key');
$this->mutex = new TestAccess($this->mutex); // @phpstan-ignore assign.propertyType
}

private function isPhpunit9x(): bool
Expand All @@ -46,7 +46,7 @@ public function testAcquireLock(): void
$statement->expects(self::once())
->method('execute')
->with(self::logicalAnd(
new IsType(IsType::TYPE_ARRAY),
TestAccess::phpunitIsType('array'),
self::countOf(2),
self::callback(function (...$arguments) {
if ($this->isPhpunit9x()) { // https://github.com/sebastianbergmann/phpunit/issues/5891
Expand All @@ -64,7 +64,7 @@ public function testAcquireLock(): void
[533558444, -1716795572]
));

\Closure::bind(static fn ($mutex) => $mutex->lock(), null, PostgreSQLMutex::class)($this->mutex);
\Closure::bind(static fn($mutex) => $mutex->lock(), null, PostgreSQLMutex::class)($this->mutex);
}

public function testReleaseLock(): void
Expand All @@ -79,7 +79,7 @@ public function testReleaseLock(): void
$statement->expects(self::once())
->method('execute')
->with(self::logicalAnd(
new IsType(IsType::TYPE_ARRAY),
TestAccess::phpunitIsType('array'),
self::countOf(2),
self::callback(function (...$arguments) {
if ($this->isPhpunit9x()) { // https://github.com/sebastianbergmann/phpunit/issues/5891
Expand Down Expand Up @@ -112,7 +112,7 @@ public function testAcquireTimeoutOccurs(): void
$statement->expects(self::atLeastOnce())
->method('execute')
->with(self::logicalAnd(
new IsType(IsType::TYPE_ARRAY),
TestAccess::phpunitIsType('array'),
self::countOf(2),
self::callback(function (...$arguments) {
if ($this->isPhpunit9x()) { // https://github.com/sebastianbergmann/phpunit/issues/5891
Expand Down
8 changes: 3 additions & 5 deletions tests/Mutex/RedisMutexTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
use Malkusch\Lock\Exception\MutexException;
use Malkusch\Lock\Mutex\DistributedMutex;
use Malkusch\Lock\Mutex\RedisMutex;
use Malkusch\Lock\Tests\TestAccess;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\TestCase;
use Predis\ClientInterface as PredisClientInterface;

if (\PHP_MAJOR_VERSION >= 8) {

Check failure on line 18 in tests/Mutex/RedisMutexTest.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Comparison operation ">=" between 8 and 8 is always true.
trait RedisCompatibilityTrait
{
/**
Expand Down Expand Up @@ -228,12 +228,12 @@

$client->expects(self::once())
->method('set')
->with('php-malkusch-lock:test', new IsType(IsType::TYPE_STRING), 'PX', 31_557_600_000_000, 'NX')
->with('php-malkusch-lock:test', TestAccess::phpunitIsType('string'), 'PX', 31_557_600_000_000, 'NX')
->willReturnSelf();

$client->expects(self::once())
->method('eval')
->with(self::anything(), 1, 'php-malkusch-lock:test', new IsType(IsType::TYPE_STRING))
->with(self::anything(), 1, 'php-malkusch-lock:test', TestAccess::phpunitIsType('string'))
->willReturn(true);

$this->mutex->synchronized(static function () {});
Expand All @@ -242,8 +242,6 @@
/**
* @param \Redis::SERIALIZER_* $serializer
* @param \Redis::COMPRESSION_* $compressor
*
* @dataProvider provideSerializersAndCompressorsCases
*/
#[DataProvider('provideSerializersAndCompressorsCases')]
public function testSerializersAndCompressors(int $serializer, int $compressor): void
Expand Down
Loading
Loading