*/ private array $accessLocks = []; /** * @var array */ private array $workerLocks = []; public function __construct(private readonly LockFactory $lockFactory) {} /** * Acquire a specific lock for the given scope. * * @throws LockAcquireException * @throws LockCreateException * @return bool True if we did not get the lock immediately and had to wait. This can be useful to * know in the consumer since another process may have created something that we can * re-use immediately. */ public function acquireLock(string $scope, string $key): bool { $this->accessLocks[$scope] = $this->lockFactory->createLocker($scope); $this->workerLocks[$scope] = $this->lockFactory->createLocker( $key, LockingStrategyInterface::LOCK_CAPABILITY_EXCLUSIVE | LockingStrategyInterface::LOCK_CAPABILITY_NOBLOCK ); $hadToWaitForLock = false; do { if (!$this->accessLocks[$scope]->acquire()) { throw new \RuntimeException('Could not acquire access lock for "' . $scope . '".', 1601923209); } try { $locked = $this->workerLocks[$scope]->acquire( LockingStrategyInterface::LOCK_CAPABILITY_EXCLUSIVE | LockingStrategyInterface::LOCK_CAPABILITY_NOBLOCK ); } catch (LockAcquireWouldBlockException $e) { // Somebody else has the lock, we keep waiting. // First release the access lock, it will be acquired in next iteration again. $this->accessLocks[$scope]->release(); // Mark "We had to wait". $hadToWaitForLock = true; // Now lets make a short break (20ms) until we try again, since // the page generation by the lock owner will take a while. usleep(20000); continue; } $this->accessLocks[$scope]->release(); if ($locked) { break; } throw new \RuntimeException('Could not acquire process lock for "' . $scope . '" with key "' . $key . '".', 1601923215); } while (true); return $hadToWaitForLock; } /** * Release a worker specific lock. * * @throws LockAcquireException * @throws LockAcquireWouldBlockException */ public function releaseLock(string $scope): void { if ($this->accessLocks[$scope] ?? null) { if (!$this->accessLocks[$scope]->acquire()) { throw new \RuntimeException('Could not acquire access lock for "' . $scope . '".', 1601923319); } $this->workerLocks[$scope]->release(); $this->workerLocks[$scope]->destroy(); $this->workerLocks[$scope] = null; $this->accessLocks[$scope]->release(); $this->accessLocks[$scope] = null; } } }