Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 6 additions & 5 deletions src/Exception/LockReleaseException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
*
* Take this exception very serious.
*
* Failing to release a lock might have the potential to introduce deadlocks. Also the
* critical code was executed i.e. side effects may have happened.
* This exception implies that the critical code was executed, i.e. side effects may have happened.
*
* Failing to release a lock might have the potential to introduce deadlocks.
*/
class LockReleaseException extends MutexException
{
Expand All @@ -20,9 +21,9 @@ class LockReleaseException extends MutexException
private ?\Throwable $codeException = null;

/**
* Gets the result that has been returned during the critical code execution.
* The return value of the executed critical code.
*
* @return mixed The return value of the executed code block
* @return mixed
*/
public function getCodeResult()
{
Expand All @@ -42,7 +43,7 @@ public function setCodeResult($codeResult): self
}

/**
* Gets the exception that has happened during the synchronized code execution.
* The exception that has happened during the critical code execution.
*/
public function getCodeException(): ?\Throwable
{
Expand Down
30 changes: 14 additions & 16 deletions src/Mutex/AbstractLockMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,48 +15,46 @@
abstract class AbstractLockMutex extends AbstractMutex
{
/**
* Acquires the lock.
* Acquire a lock.
*
* This method blocks until the lock was acquired.
*
* @throws LockAcquireException The lock could not be acquired
* @throws LockAcquireException
*/
abstract protected function lock(): void;

/**
* Releases the lock.
* Release the lock.
*
* @throws LockReleaseException The lock could not be released
* @throws LockReleaseException
*/
abstract protected function unlock(): void;

#[\Override]
public function synchronized(callable $code)
public function synchronized(callable $fx)
{
$this->lock();

$codeResult = null;
$codeException = null;
$fxResult = null;
$fxException = null;
try {
$codeResult = $code();
} catch (\Throwable $exception) {
$codeException = $exception;

throw $exception;
$fxResult = $fx();
} catch (\Throwable $fxException) {
throw $fxException;
} finally {
try {
$this->unlock();
} catch (LockReleaseException $lockReleaseException) {
$lockReleaseException->setCodeResult($codeResult);
$lockReleaseException->setCodeResult($fxResult);

if ($codeException !== null) {
$lockReleaseException->setCodeException($codeException);
if ($fxException !== null) {
$lockReleaseException->setCodeException($fxException);
}

throw $lockReleaseException;
}
}

return $codeResult;
return $fxResult;
}
}
6 changes: 3 additions & 3 deletions src/Mutex/AbstractRedlockMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ abstract class AbstractRedlockMutex extends AbstractSpinlockMutex implements Log
* called already.
*
* @param array<int, TClient> $clients
* @param float $timeout The timeout in seconds a lock expires
* @param float $acquireTimeout In seconds
*/
public function __construct(array $clients, string $name, float $timeout = 3)
public function __construct(array $clients, string $name, float $acquireTimeout = 3)
{
parent::__construct($name, $timeout);
parent::__construct($name, $acquireTimeout);

$this->clients = $clients;
$this->logger = new NullLogger();
Expand Down
20 changes: 10 additions & 10 deletions src/Mutex/AbstractSpinlockMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ abstract class AbstractSpinlockMutex extends AbstractLockMutex
private string $key;

/** In seconds */
private float $timeout;
private float $acquireTimeout;

/** The timestamp when the lock was acquired */
private ?float $acquiredTs = null;

/**
* @param float $timeout The timeout in seconds a lock expires
* @param float $acquireTimeout In seconds
*/
public function __construct(string $name, float $timeout = 3)
public function __construct(string $name, float $acquireTimeout = 3)
{
$this->key = LockUtil::getInstance()->getKeyPrefix() . ':' . $name;
$this->timeout = $timeout;
$this->acquireTimeout = $acquireTimeout;
}

#[\Override]
Expand All @@ -49,18 +49,18 @@ protected function lock(): void
* acquires successfully the same key which would then be deleted
* by this process.
*/
if ($this->acquire($this->key, $this->timeout + 1)) {
if ($this->acquire($this->key, $this->acquireTimeout + 1)) {
$loop->end();
}
}, $this->timeout);
}, $this->acquireTimeout);
}

#[\Override]
protected function unlock(): void
{
$elapsedTime = microtime(true) - $this->acquiredTs;
if ($elapsedTime > $this->timeout) {
throw ExecutionOutsideLockException::create($elapsedTime, $this->timeout);
if ($elapsedTime > $this->acquireTimeout) {
throw ExecutionOutsideLockException::create($elapsedTime, $this->acquireTimeout);
}

/*
Expand All @@ -75,11 +75,11 @@ protected function unlock(): void
/**
* Try to acquire a lock.
*
* @param float $expire The timeout in seconds when a lock expires
* @param float $expire In seconds
*
* @return bool True if the lock was acquired
*
* @throws LockAcquireException an unexpected error happened
* @throws LockAcquireException An unexpected error happened
*/
abstract protected function acquire(string $key, float $expire): bool;

Expand Down
40 changes: 10 additions & 30 deletions src/Mutex/FlockMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Malkusch\Lock\Exception\LockAcquireException;
use Malkusch\Lock\Exception\LockAcquireTimeoutException;
use Malkusch\Lock\Exception\LockReleaseException;
use Malkusch\Lock\Util\LockUtil;
use Malkusch\Lock\Util\Loop;
use Malkusch\Lock\Util\PcntlTimeout;

Expand All @@ -16,33 +17,30 @@
*/
class FlockMutex extends AbstractLockMutex
{
public const INFINITE_TIMEOUT = -1.0;

private const STRATEGY_BLOCK = 'block';

private const STRATEGY_PCNTL = 'pcntl';

private const STRATEGY_LOOP = 'loop';

/** @var resource */
private $fileHandle;

private float $timeout;
private float $acquireTimeout;

/** @var self::STRATEGY_* */
private $strategy;

/**
* @param resource $fileHandle
* @param float $acquireTimeout In seconds
*/
public function __construct($fileHandle, float $timeout = self::INFINITE_TIMEOUT)
public function __construct($fileHandle, float $acquireTimeout = \INF)
{
if (!is_resource($fileHandle)) {
throw new \InvalidArgumentException('The file handle is not a valid resource');
}

$this->fileHandle = $fileHandle;
$this->timeout = $timeout;
$this->acquireTimeout = $acquireTimeout;
$this->strategy = $this->determineLockingStrategy();
}

Expand All @@ -51,7 +49,7 @@ public function __construct($fileHandle, float $timeout = self::INFINITE_TIMEOUT
*/
private function determineLockingStrategy(): string
{
if ($this->timeout === self::INFINITE_TIMEOUT) {
if ($this->acquireTimeout > 100 * 365.25 * 24 * 60 * 60) { // 100 years
return self::STRATEGY_BLOCK;
}

Expand All @@ -62,25 +60,18 @@ private function determineLockingStrategy(): string
return self::STRATEGY_LOOP;
}

/**
* @throws LockAcquireException
*/
private function lockBlocking(): void
{
if (!flock($this->fileHandle, \LOCK_EX)) {
throw new LockAcquireException('Failed to lock the file');
}
}

/**
* @throws LockAcquireException
* @throws LockAcquireTimeoutException
*/
private function lockPcntl(): void
{
$timeoutInt = (int) ceil($this->timeout);
$acquireTimeoutInt = LockUtil::getInstance()->castFloatToInt(ceil($this->acquireTimeout));

$timebox = new PcntlTimeout($timeoutInt);
$timebox = new PcntlTimeout($acquireTimeoutInt);

try {
$timebox->timeBoxed(
Expand All @@ -89,14 +80,10 @@ function (): void {
}
);
} catch (DeadlineException $e) {
throw LockAcquireTimeoutException::create($timeoutInt);
throw LockAcquireTimeoutException::create($acquireTimeoutInt);
}
}

/**
* @throws LockAcquireTimeoutException
* @throws LockAcquireException
*/
private function lockBusy(): void
{
$loop = new Loop();
Expand All @@ -105,12 +92,9 @@ private function lockBusy(): void
if ($this->acquireNonBlockingLock()) {
$loop->end();
}
}, $this->timeout);
}, $this->acquireTimeout);
}

/**
* @throws LockAcquireException
*/
private function acquireNonBlockingLock(): bool
{
if (!flock($this->fileHandle, \LOCK_EX | \LOCK_NB, $wouldBlock)) {
Expand All @@ -125,10 +109,6 @@ private function acquireNonBlockingLock(): bool
return true;
}

/**
* @throws LockAcquireException
* @throws LockAcquireTimeoutException
*/
#[\Override]
protected function lock(): void
{
Expand Down
11 changes: 6 additions & 5 deletions src/Mutex/MemcachedMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Malkusch\Lock\Mutex;

use Malkusch\Lock\Util\LockUtil;

/**
* Memcached based spinlock implementation.
*/
Expand All @@ -15,12 +17,11 @@ class MemcachedMutex extends AbstractSpinlockMutex
* The Memcached API needs to have at least one server in its pool. I.e.
* it has to be added with Memcached::addServer().
*
* @param string $name The lock name
* @param float $timeout The timeout in seconds a lock expires
* @param float $acquireTimeout In seconds
*/
public function __construct(string $name, \Memcached $memcached, float $timeout = 3)
public function __construct(string $name, \Memcached $memcached, float $acquireTimeout = 3)
{
parent::__construct($name, $timeout);
parent::__construct($name, $acquireTimeout);

$this->memcached = $memcached;
}
Expand All @@ -30,7 +31,7 @@ protected function acquire(string $key, float $expire): bool
{
// memcached supports only integer expire
// https://github.com/memcached/memcached/wiki/Commands#standard-protocol
$expireInt = (int) ceil($expire);
$expireInt = LockUtil::getInstance()->castFloatToInt(ceil($expire));

return $this->memcached->add($key, true, $expireInt);
}
Expand Down
8 changes: 3 additions & 5 deletions src/Mutex/Mutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Malkusch\Lock\Mutex;

use Malkusch\Lock\Exception\ExecutionOutsideLockException;
use Malkusch\Lock\Exception\LockAcquireException;
use Malkusch\Lock\Exception\LockReleaseException;
use Malkusch\Lock\Util\DoubleCheckedLocking;
Expand All @@ -30,10 +29,9 @@ interface Mutex
*
* @return T
*
* @throws \Exception The execution callback threw an exception
* @throws LockAcquireException The mutex could not be acquired, no further side effects
* @throws LockReleaseException The mutex could not be released, the code was already executed
* @throws ExecutionOutsideLockException Some code has been executed outside of the lock
* @throws \Throwable
* @throws LockAcquireException
* @throws LockReleaseException
*/
public function synchronized(callable $code);

Expand Down
15 changes: 8 additions & 7 deletions src/Mutex/MySQLMutex.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ class MySQLMutex extends AbstractLockMutex

private string $name;

private float $timeout;
/** In seconds */
private float $acquireTimeout;

/**
* @param float $timeout In seconds
* @param float $acquireTimeout In seconds
*/
public function __construct(\PDO $PDO, string $name, float $timeout = 0)
public function __construct(\PDO $PDO, string $name, float $acquireTimeout = 0)
{
$this->pdo = $PDO;

Expand All @@ -30,7 +31,7 @@ public function __construct(\PDO $PDO, string $name, float $timeout = 0)
}

$this->name = $namePrefix . $name;
$this->timeout = $timeout;
$this->acquireTimeout = $acquireTimeout;
}

#[\Override]
Expand All @@ -42,11 +43,11 @@ protected function lock(): void
// TODO MariaDB supports microseconds precision since 10.1.2 version,
// but we need to detect the support reliably first
// https://github.com/MariaDB/server/commit/3e792e6cbccb5d7bf5b84b38336f8a40ad232020
$timeoutInt = (int) ceil($this->timeout);
$acquireTimeoutInt = LockUtil::getInstance()->castFloatToInt(ceil($this->acquireTimeout));

$statement->execute([
$this->name,
$timeoutInt,
$acquireTimeoutInt,
]);

$statement->setFetchMode(\PDO::FETCH_NUM);
Expand All @@ -62,7 +63,7 @@ protected function lock(): void
throw new LockAcquireException('An error occurred while acquiring the lock');
}

throw LockAcquireTimeoutException::create($this->timeout);
throw LockAcquireTimeoutException::create($this->acquireTimeout);
}

#[\Override]
Expand Down
Loading