From 908f2ec6272a6e5097da90e697fa6bb99d99f910 Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Wed, 8 Jul 2026 20:00:21 +0300 Subject: [PATCH 1/4] Document waitTTL behavior (non-blocking default, 1ms server cap) The `waitTTL` parameter of lock()/lockRead() was documented only as "how long to wait to acquire lock until returning false", with no mention of what the default value of 0 does. The README intro further implied the call always blocks until the lock is released, which is misleading. Per the RoadRunner lock plugin (roadrunner-server/lock, rpc.go): a wait of 0 is floored to defaultImmediateTimeout (1ms), so the call is effectively non-blocking and returns false almost immediately when the resource is already locked; a positive wait blocks up to that duration. Clarify the docblocks and README accordingly so the behavior no longer has to be reverse-engineered. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 15 +++++++++++---- src/Lock.php | 19 +++++++++++++++---- src/LockInterface.php | 19 +++++++++++++++---- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 66a3070..e74aed4 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,12 @@ $lock = new Lock(RPC::create('tcp://127.0.0.1:6001')); ### Acquire lock -Locks a resource so that it can be accessed by one process at a time. When a resource is locked, other processes that -attempt to lock the same resource will be blocked until the lock is released. +Locks a resource so that it can be accessed by one process at a time. + +By default the call is **non-blocking**: if the resource is already locked, it returns `false` almost immediately +(the RoadRunner server caps the default `wait` window at `1ms`). Pass a positive `wait` to block until the lock is +released — the call then returns the lock id as soon as the lock becomes free, or `false` when the `wait` timeout +elapses. ```php $id = $lock->lock('pdf:create'); @@ -70,8 +74,11 @@ $id = $lock->lock('pdf:create', id: '14e1b600-9e97-11d8-9f32-f2801f1b9fd1'); ### Acquire read lock Locks a resource for shared access, allowing multiple processes to access the resource simultaneously. When a resource -is locked for shared access, other processes that attempt to lock the resource for exclusive access will be blocked -until all shared locks are released. +is locked for shared access, other processes that attempt to lock the resource for exclusive access will fail to do so +while any shared lock is held. + +As with `lock()`, the `wait` parameter is non-blocking by default (`false` is returned almost immediately, within the +server's `1ms` window); pass a positive `wait` to block for up to that duration for the lock to become available. ```php $id = $lock->lockRead('pdf:create', ttl: 10); diff --git a/src/Lock.php b/src/Lock.php index a4757ad..3d61136 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -25,13 +25,19 @@ public function __construct( /** * Lock a resource for exclusive access * - * Locks a resource so that it can be accessed by one process at a time. When a resource is locked, - * other processes that attempt to lock the same resource will be blocked until the lock is released. + * Locks a resource so that it can be accessed by one process at a time. By default the call is non-blocking: + * if the resource is already locked, it returns false immediately. Pass a positive $waitTTL to wait for the + * lock to be released instead. * * @param non-empty-string $resource The name of the resource to be locked. * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). - * @param int|float|DateInterval $waitTTL How long to wait to acquire lock until returning false. + * @param int|float|DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. * * @throws \InvalidArgumentException If ttl is negative. @@ -63,7 +69,12 @@ public function lock( * @param non-empty-string $resource The name of the resource to be locked. * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). - * @param int|float|DateInterval $waitTTL How long to wait to acquire lock until returning false. + * @param int|float|DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. * * @throws \InvalidArgumentException If ttl is negative. diff --git a/src/LockInterface.php b/src/LockInterface.php index 8cad5a7..7fbf330 100644 --- a/src/LockInterface.php +++ b/src/LockInterface.php @@ -9,13 +9,19 @@ interface LockInterface /** * Lock a resource for exclusive access. * - * Locks a resource so that it can be accessed by one process at a time. When a resource is locked, - * other processes that attempt to lock the same resource will be blocked until the lock is released. + * Locks a resource so that it can be accessed by one process at a time. By default the call is non-blocking: + * if the resource is already locked, it returns false immediately. Pass a positive $waitTTL to wait for the + * lock to be released instead. * * @param non-empty-string $resource The name of the resource to be locked. * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|\DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). - * @param int|float|\DateInterval $waitTTL How long to wait to acquire lock until returning false. + * @param int|float|\DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. */ public function lock( @@ -35,7 +41,12 @@ public function lock( * @param non-empty-string $resource The name of the resource to be locked. * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|\DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). - * @param int|float|\DateInterval $waitTTL How long to wait to acquire lock until returning false. + * @param int|float|\DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. */ public function lockRead( From 7b1539ab500bf7f063fe48f912ddb3700379bf1f Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Wed, 8 Jul 2026 20:09:44 +0300 Subject: [PATCH 2/4] README: add lock() / lockRead() parameters reference Consolidate resource/id/ttl/wait types, units (seconds or DateInterval), defaults and the return value into a single scannable table, so the per-argument behavior no longer has to be pieced together from prose and the source. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index e74aed4..15c0695 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,21 @@ $id = $lock->lockRead('pdf:create', wait: new \DateInterval('PT5S')); $id = $lock->lockRead('pdf:create', id: '14e1b600-9e97-11d8-9f32-f2801f1b9fd1'); ``` +### Lock parameters + +Both `lock()` and `lockRead()` accept the same arguments: + +| Parameter | Type | Default | Description | +|------------|---------------------------------|---------------|-------------| +| `resource` | `non-empty-string` | — | Name of the resource to lock. | +| `id` | `non-empty-string`\|`null` | `null` | Lock owner id. When omitted a random UUID is generated. Keep it — the same `id` must be passed to `release()`. | +| `ttl` | `int`\|`float`\|`DateInterval` | `0` (forever) | Lock lifetime, in seconds. When it elapses the lock is released automatically; `0` means it never expires on its own. | +| `wait` | `int`\|`float`\|`DateInterval` | `0` (~1ms) | How long to wait for the lock to become free, in seconds. `0` is effectively non-blocking — the server caps it at `1ms`, so `false` is returned almost immediately when the resource is already locked. A positive value blocks for up to that duration, then returns `false` on timeout. | + +Both methods return the lock **id** (`non-empty-string`) when the lock is acquired, or `false` when it is not (the resource stayed busy until the `wait` window elapsed). + +> `ttl` and `wait` are expressed in **seconds** (`int` or `float`), or as a `DateInterval`. + ### Release lock Releases an exclusive lock or read lock on a resource that was previously acquired by a call to `lock()` From 8f6fa3c4b0fb2ed6f062f8b645f07a0d19d52213 Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Wed, 8 Jul 2026 20:13:07 +0300 Subject: [PATCH 3/4] README: use waitTTL in examples to match the method signature The named-argument examples used `wait:`, but the parameter is `$waitTTL`, so the snippets would fail with "Unknown named parameter $wait". Rename the parameter references (named args, prose, table) to `waitTTL` so copied snippets match lock()/lockRead(). Addresses CodeRabbit review feedback. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 15c0695..0bb608d 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ $lock = new Lock(RPC::create('tcp://127.0.0.1:6001')); Locks a resource so that it can be accessed by one process at a time. By default the call is **non-blocking**: if the resource is already locked, it returns `false` almost immediately -(the RoadRunner server caps the default `wait` window at `1ms`). Pass a positive `wait` to block until the lock is -released — the call then returns the lock id as soon as the lock becomes free, or `false` when the `wait` timeout -elapses. +(the RoadRunner server caps the default `waitTTL` window at `1ms`). Pass a positive `waitTTL` to block until the lock +is released — the call then returns the lock id as soon as the lock becomes free, or `false` when the `waitTTL` +timeout elapses. ```php $id = $lock->lock('pdf:create'); @@ -63,9 +63,9 @@ $id = $lock->lock('pdf:create', ttl: 10); $id = $lock->lock('pdf:create', ttl: new \DateInterval('PT10S')); // Acquire lock and wait 5 seconds until lock will be released -$id = $lock->lock('pdf:create', wait: 5); +$id = $lock->lock('pdf:create', waitTTL: 5); // or -$id = $lock->lock('pdf:create', wait: new \DateInterval('PT5S')); +$id = $lock->lock('pdf:create', waitTTL: new \DateInterval('PT5S')); // Acquire lock with id - 14e1b600-9e97-11d8-9f32-f2801f1b9fd1 $id = $lock->lock('pdf:create', id: '14e1b600-9e97-11d8-9f32-f2801f1b9fd1'); @@ -77,8 +77,8 @@ Locks a resource for shared access, allowing multiple processes to access the re is locked for shared access, other processes that attempt to lock the resource for exclusive access will fail to do so while any shared lock is held. -As with `lock()`, the `wait` parameter is non-blocking by default (`false` is returned almost immediately, within the -server's `1ms` window); pass a positive `wait` to block for up to that duration for the lock to become available. +As with `lock()`, the `waitTTL` parameter is non-blocking by default (`false` is returned almost immediately, within +the server's `1ms` window); pass a positive `waitTTL` to block for up to that duration for the lock to become available. ```php $id = $lock->lockRead('pdf:create', ttl: 10); @@ -86,9 +86,9 @@ $id = $lock->lockRead('pdf:create', ttl: 10); $id = $lock->lockRead('pdf:create', ttl: new \DateInterval('PT10S')); // Acquire lock and wait 5 seconds until lock will be released -$id = $lock->lockRead('pdf:create', wait: 5); +$id = $lock->lockRead('pdf:create', waitTTL: 5); // or -$id = $lock->lockRead('pdf:create', wait: new \DateInterval('PT5S')); +$id = $lock->lockRead('pdf:create', waitTTL: new \DateInterval('PT5S')); // Acquire lock with id - 14e1b600-9e97-11d8-9f32-f2801f1b9fd1 $id = $lock->lockRead('pdf:create', id: '14e1b600-9e97-11d8-9f32-f2801f1b9fd1'); @@ -103,11 +103,11 @@ Both `lock()` and `lockRead()` accept the same arguments: | `resource` | `non-empty-string` | — | Name of the resource to lock. | | `id` | `non-empty-string`\|`null` | `null` | Lock owner id. When omitted a random UUID is generated. Keep it — the same `id` must be passed to `release()`. | | `ttl` | `int`\|`float`\|`DateInterval` | `0` (forever) | Lock lifetime, in seconds. When it elapses the lock is released automatically; `0` means it never expires on its own. | -| `wait` | `int`\|`float`\|`DateInterval` | `0` (~1ms) | How long to wait for the lock to become free, in seconds. `0` is effectively non-blocking — the server caps it at `1ms`, so `false` is returned almost immediately when the resource is already locked. A positive value blocks for up to that duration, then returns `false` on timeout. | +| `waitTTL` | `int`\|`float`\|`DateInterval` | `0` (~1ms) | How long to wait for the lock to become free, in seconds. `0` is effectively non-blocking — the server caps it at `1ms`, so `false` is returned almost immediately when the resource is already locked. A positive value blocks for up to that duration, then returns `false` on timeout. | -Both methods return the lock **id** (`non-empty-string`) when the lock is acquired, or `false` when it is not (the resource stayed busy until the `wait` window elapsed). +Both methods return the lock **id** (`non-empty-string`) when the lock is acquired, or `false` when it is not (the resource stayed busy until the `waitTTL` window elapsed). -> `ttl` and `wait` are expressed in **seconds** (`int` or `float`), or as a `DateInterval`. +> `ttl` and `waitTTL` are expressed in **seconds** (`int` or `float`), or as a `DateInterval`. ### Release lock From 37a7da9a4ced2aa85bfb9dd5763fc97bd9ab1769 Mon Sep 17 00:00:00 2001 From: Alexander Strizhak Date: Wed, 8 Jul 2026 22:24:26 +0300 Subject: [PATCH 4/4] comments fold --- src/Lock.php | 20 ++++++++++---------- src/LockInterface.php | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Lock.php b/src/Lock.php index 3d61136..2ef70d7 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -33,11 +33,11 @@ public function __construct( * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). * @param int|float|DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. - * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner - * server caps the acquire window at defaultImmediateTimeout (1ms), so false - * is returned almost immediately when the resource is already locked. A - * positive value blocks for up to that duration, returning the lock id as - * soon as the lock is released, or false on timeout. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. * * @throws \InvalidArgumentException If ttl is negative. @@ -70,11 +70,11 @@ public function lock( * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). * @param int|float|DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. - * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner - * server caps the acquire window at defaultImmediateTimeout (1ms), so false - * is returned almost immediately when the resource is already locked. A - * positive value blocks for up to that duration, returning the lock id as - * soon as the lock is released, or false on timeout. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. * * @throws \InvalidArgumentException If ttl is negative. diff --git a/src/LockInterface.php b/src/LockInterface.php index 7fbf330..eefddde 100644 --- a/src/LockInterface.php +++ b/src/LockInterface.php @@ -17,11 +17,11 @@ interface LockInterface * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|\DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). * @param int|float|\DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. - * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner - * server caps the acquire window at defaultImmediateTimeout (1ms), so false - * is returned almost immediately when the resource is already locked. A - * positive value blocks for up to that duration, returning the lock id as - * soon as the lock is released, or false on timeout. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. */ public function lock( @@ -42,11 +42,11 @@ public function lock( * @param non-empty-string|null $id The lock ID. If not specified, a random UUID will be generated. * @param int|float|\DateInterval $ttl The time-to-live of the lock, in seconds. Defaults to 0 (forever). * @param int|float|\DateInterval $waitTTL How long to wait for the lock to become free before giving up, in seconds. - * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner - * server caps the acquire window at defaultImmediateTimeout (1ms), so false - * is returned almost immediately when the resource is already locked. A - * positive value blocks for up to that duration, returning the lock id as - * soon as the lock is released, or false on timeout. + * Defaults to 0. With 0 the call is effectively non-blocking: the RoadRunner + * server caps the acquire window at defaultImmediateTimeout (1ms), so false + * is returned almost immediately when the resource is already locked. A + * positive value blocks for up to that duration, returning the lock id as + * soon as the lock is released, or false on timeout. * @return false|non-empty-string Returns lock ID if the lock was acquired successfully, false otherwise. */ public function lockRead(