= 0) { $semaphore->release(); } return $semaphore; } } while (true); } finally { \restore_error_handler(); } } private static function getNextId(): int { if (self::$nextId === 0) { return self::$nextId = \random_int(1, self::MAX_ID); } return self::$nextId = self::$nextId % self::MAX_ID + 1; } /** * @param int $key Use {@see getKey()} on the creating process and send this key to another process. */ public static function use(int $key): self { if (!\msg_queue_exists($key)) { throw new SyncException('No semaphore with that ID found'); } $queue = \msg_get_queue($key); if (!$queue) { throw new SyncException('Failed to open the semaphore.'); } return new self($queue, $key, 0); } /** * @param int $initializer PID of the process that created the semaphore or 0 if the semaphore was only opened * in this process. * * @throws \Error If the sysvmsg extension is not loaded. */ private function __construct( private readonly \SysvMessageQueue $queue, private readonly int $key, private readonly int $initializer, ) { if (!\extension_loaded("sysvmsg")) { throw new \Error(__CLASS__ . " requires the sysvmsg extension."); } $this->errorHandler = static fn () => true; } public function getKey(): int { return $this->key; } /** * Gets the access permissions of the semaphore. * * @return int A permissions mode. */ public function getPermissions(): int { $stat = \msg_stat_queue($this->queue); return $stat['msg_perm.mode']; } /** * Sets the access permissions of the semaphore. * * The current user must have access to the semaphore in order to change the permissions. * * @param int $mode A permissions mode to set. * * @throws SyncException If the operation failed. */ public function setPermissions(int $mode): void { if (!\msg_set_queue($this->queue, ['msg_perm.mode' => $mode])) { throw new SyncException('Failed to change the semaphore permissions.'); } } public function acquire(): Lock { do { // Attempt to acquire a lock from the semaphore. \set_error_handler($this->errorHandler); try { if (\msg_receive($this->queue, 0, $type, 1, $message, false, \MSG_IPC_NOWAIT, $errno)) { // A free lock was found, so resolve with a lock object that can // be used to release the lock. return new Lock($this->release(...)); } } finally { \restore_error_handler(); } // Check for unusual errors. if ($errno !== \MSG_ENOMSG) { throw new SyncException(\sprintf('Failed to acquire a lock; errno: %d', $errno)); } delay(self::LATENCY_TIMEOUT); } while (true); } /** * Removes the semaphore if it still exists. * * @throws SyncException If the operation failed. */ public function __destruct() { if ($this->initializer === 0 || $this->initializer !== \getmypid()) { return; } if (!\msg_queue_exists($this->key)) { return; } \msg_remove_queue($this->queue); } /** * Releases a lock from the semaphore. * * @throws SyncException If the operation failed. */ private function release(): void { // Send in non-blocking mode. If the call fails because the queue is full, // then the number of locks configured is too large. \set_error_handler($this->errorHandler); try { if (!\msg_send($this->queue, 1, "\0", false, false, $errno)) { if ($errno === \MSG_EAGAIN) { throw new SyncException('The semaphore size is larger than the system allows.'); } throw new SyncException('Failed to release the lock.'); } } finally { \restore_error_handler(); } } }