Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
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.2', '8.3', '8.4', '8.5']
type: ['Phpunit', 'Phpunit Lowest']
include:
- php: 'latest'
Expand Down
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
],
"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"
},
Expand All @@ -47,7 +47,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.

"ergebnis/composer-normalize": "^2.13",
"ergebnis/phpunit-slow-test-detector": "^2.9",
"friendsofphp/php-cs-fixer": "^3.0",
Expand All @@ -57,7 +56,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
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
14 changes: 12 additions & 2 deletions src/Util/LockUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,21 @@ 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 '0.0';
}

// 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;
}
}
7 changes: 4 additions & 3 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 @@ -47,7 +48,7 @@ protected function tearDown(): void
#[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 Down
10 changes: 5 additions & 5 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 Down Expand Up @@ -163,19 +163,19 @@
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 \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)());

return $lock->popsValue();

Check failure on line 169 in tests/Mutex/MutexConcurrencyTest.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Call to an undefined method Malkusch\Lock\Mutex\FlockMutex::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 \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)());

return $lock->popsValue();

Check failure on line 178 in tests/Mutex/MutexConcurrencyTest.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Call to an undefined method Malkusch\Lock\Mutex\FlockMutex::popsValue().
}];

if (extension_loaded('sysvsem')) {
Expand All @@ -185,7 +185,7 @@
$semaphore,
self::logicalOr(
self::isInstanceOf(\SysvSemaphore::class),
new IsType(IsType::TYPE_RESOURCE)

Check failure on line 188 in tests/Mutex/MutexConcurrencyTest.php

View workflow job for this annotation

GitHub Actions / Smoke (latest, StaticAnalysis)

Access to undefined constant PHPUnit\Framework\Constraint\IsType::TYPE_RESOURCE.
)
);

Expand Down
10 changes: 5 additions & 5 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 Down Expand Up @@ -114,17 +114,17 @@ 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 \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_PCNTL, null, FlockMutex::class)());

return $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 \Malkusch\Lock\Tests\TestAccess($lock))->setProperty('strategy', \Closure::bind(static fn () => FlockMutex::STRATEGY_LOOP, null, FlockMutex::class)());

return $lock->popsValue();
}];
Expand Down
5 changes: 3 additions & 2 deletions tests/Mutex/PostgreSQLMutexTest.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;
use Malkusch\Lock\Tests\TestAccess;
use Malkusch\Lock\Exception\LockAcquireTimeoutException;
use Malkusch\Lock\Mutex\PostgreSQLMutex;
use PHPUnit\Framework\Constraint\IsType;
Expand All @@ -26,7 +26,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 Down
45 changes: 45 additions & 0 deletions tests/TestAccess.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Malkusch\Lock\Tests;

/**
* TestAccess - Helper for accessing private/protected properties and methods in tests.
*/
class TestAccess
{
private $object;

public function __construct($object)
{
$this->object = $object;
}

public function getProperty($property)
{
$ref = new \ReflectionProperty($this->object, $property);
if (\PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}

return $ref->getValue($this->object);
}

public function setProperty($property, $value)
{
$ref = new \ReflectionProperty($this->object, $property);
if (\PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($this->object, $value);
}

public function callMethod($method, ...$args)
{
$ref = new \ReflectionMethod($this->object, $method);
if (\PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}

return $ref->invokeArgs($this->object, $args);
}
}
Loading