List of event-loop delay callback IDs. */ private readonly \SplQueue $timers; private int $waitingCount = 0; /** * @param float $lockPeriod Time after which a lock is released from the semaphore after being initially * released by the consumer. */ public function __construct( private readonly Semaphore $semaphore, private readonly float $lockPeriod, ) { if ($lockPeriod <= 0) { throw new \ValueError('The lock period must be greater than 0, got ' . $lockPeriod); } $this->timers = new \SplQueue(); } public function acquire(): Lock { ++$this->waitingCount; if (!$this->timers->isEmpty()) { EventLoop::reference($this->timers->bottom()); } $lock = $this->semaphore->acquire(); if (!--$this->waitingCount && !$this->timers->isEmpty()) { EventLoop::unreference($this->timers->bottom()); } return new Lock(fn () => $this->release($lock)); } private function release(Lock $lock): void { $timer = EventLoop::delay( $this->lockPeriod, function () use ($lock): void { \assert(!$this->timers->isEmpty()); $this->timers->shift(); if ($this->waitingCount && !$this->timers->isEmpty()) { EventLoop::reference($this->timers->bottom()); } $lock->release(); }, ); if (!$this->waitingCount || !$this->timers->isEmpty()) { EventLoop::unreference($timer); } $this->timers->push($timer); } }