<?php
declare(strict_types=1);
namespace malkusch\lock\util;
use malkusch\lock\mutex\Mutex;
/**
* The double-checked locking pattern.
*
* You should not instantiate this class directly. Use
* {@link \malkusch\lock\mutex\Mutex::check()}.
*
* @author Markus Malkusch <markus@malkusch.de>
* @link bitcoin:1P5FAZ4QhXCuwYPnLZdk3PJsqePbu1UDDA Donations
* @license WTFPL
*/
class DoubleCheckedLocking
{
/**
* @var \malkusch\lock\mutex\Mutex The mutex.
*/
private $mutex;
/**
* @var callable The check.
*/
private $check;
/**
* Constructs a new instance of the DoubleCheckedLocking pattern.
*
* @param \malkusch\lock\mutex\Mutex $mutex Provides methods for exclusive
* code execution.
* @param callable $check Callback that decides if the lock should be
* acquired and if the critical code callback should be executed after
* acquiring the lock.
*/
public function __construct(Mutex $mutex, callable $check)
{
$this->mutex = $mutex;
$this->check = $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.
*
* @param callable $code The critical code callback.
* @throws \Exception The execution callback or the check threw an
* exception.
* @throws \malkusch\lock\exception\LockAcquireException The mutex could not
* be acquired.
* @throws \malkusch\lock\exception\LockReleaseException The mutex could not
* be released.
* @throws \malkusch\lock\exception\ExecutionOutsideLockException Some code
* has been executed outside of the lock.
* @return mixed Boolean false if check did not pass or mixed for what ever
* the critical code callback returns.
*/
public function then(callable $code)
{
if (!\call_user_func($this->check)) {
return false;
}
return $this->mutex->synchronized(function () use ($code) {
if (!\call_user_func($this->check)) {
return false;
}
return $code();
});
}
}