Skip to content

Commit 74f52a5

Browse files
committed
Document callback result loss when lock release fails in withinSessionLevelLock
- Add docblock warning that LockReleaseException guarantees successful callback but loses return value - Update ADR-003 with 'Known limitation' section explaining PHP finally semantics - Suggest WithinSessionLockReleaseException as potential future improvement - Direct users to low-level API for cases where callback result must be preserved
1 parent 116d753 commit 74f52a5

2 files changed

Lines changed: 12 additions & 0 deletions

File tree

adr/adr-003-within-session-lock-exception-safety.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,17 @@ PHP sets `previous` only in the exception constructor — there is no `addPrevio
5656

5757
The library has no logger dependency and adding one for a single edge case is not justified. Users who need visibility into release failures can wrap `withinSessionLevelLock` in their own try/catch or use `acquireSessionLevelLock` / `releaseSessionLevelLock` directly.
5858

59+
## Known limitation: callback result is lost on release failure
60+
61+
When the callback succeeds but `releaseSessionLevelLock()` throws, `LockReleaseException` is thrown and the callback's return value is discarded. The caller receives no indication of the callback result.
62+
63+
This is a consequence of PHP's `finally` semantics — a `throw` in `finally` prevents the `try` block's `return` from completing. The `LockReleaseException` itself serves as a reliable signal that the callback completed successfully (it is only thrown when `$exception === null`), but the return value is not preserved.
64+
65+
A future improvement could introduce a dedicated exception subclass (e.g. `WithinSessionLockReleaseException`) that carries the callback result. For now, users who need the callback result in this scenario should use `acquireSessionLevelLock()` / `releaseSessionLevelLock()` directly.
66+
5967
## Consequences
6068

6169
- Original exceptions from user callbacks are never masked by release failures.
6270
- `PG_ADVISORY_UNLOCK` is not called when the lock was never acquired, preventing reentrant counter corruption.
6371
- Release failures are silently suppressed when a callback exception is already in flight — acceptable trade-off given no logger.
72+
- Callback return value is lost when release fails — acceptable trade-off; low-level API is available for callers who need it.

src/Postgres/PostgresAdvisoryLocker.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ public function acquireTransactionLevelLock(
7373
*
7474
* @throws LockAcquireException If a database error occurs during lock acquisition. NOT thrown for normal lock contention.
7575
* @throws LockReleaseException If a database error occurs during lock release (only thrown if no other exception occurred during callback execution).
76+
* ⚠️ A LockReleaseException guarantees that the callback has completed successfully,
77+
* but the callback's return value is lost. If you need access to the callback result
78+
* in this scenario, use acquireSessionLevelLock() / releaseSessionLevelLock() directly.
7679
*
7780
* @see acquireTransactionLevelLock() for preferred locking strategy.
7881
*

0 commit comments

Comments
 (0)