You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Document lock()'s exclusion-only limitations in the public API documentation #2547
The Phase 1 lock() guarantee was settled as exclusion-only
(harper#483 comment, docs/record-lock-ownership.md §10). That decision was taken on the condition that the two things lock() does not promise are documented rather than implied — §10 says so in terms:
Nothing above may be softened into "rare" or "best-effort" in the API docs, and neither limitation
may be described as crash-only or as unreachable on a clean handoff.
The design note is the normative source and DESIGN.md routes writers to it, but the §14 work
decomposition assigns issues to every implementation slice and none to the documentation change.
This issue is that owner.
What the public lock() documentation must say
Lifted from §10, which is normative and must not be paraphrased into something weaker:
lock() guarantees exclusive admission of a critical section for a key, and successor
freshness after a clean handoff — the latter only while the key's home still holds that handoff's
dependency set.
A predecessor's write can outrank its successor's.lock() changes nothing about how two
conflicting writes resolve, so whatever the pair would do to the record without a lock is what
they do with one, silently and with no error raised. Reachable on a completely clean handoff
when a caller stamps a future context.timestamp.
Successor freshness is not promised on the recovery path — the predecessor crashed, is
unreachable, or its native commit settled after the recovery barrier was measured. Both the
predecessor's transaction and the successor's derived write end up wrong, which is exactly the
read-then-conditionally-write case lock() exists for.
There is no caller-side mitigation. The obvious candidate (X-Replicate-To / confirm=) is
super-user-gated and entangled with residency, and closes none of limitation 3's routes anyway.
Neither limitation is crash-only, and both are reachable on a clean handoff. Three separate
pre-push review rounds on Cluster record locks: amortized per-record ownership (Phase 1 of #483) #2498 produced text that got this wrong in three different directions,
so it is worth stating plainly rather than in a caveat.
Why this is worth its own issue
The honesty argument is load-bearing for the decision: exclusion-only was chosen over fenced and
quorum-confirmed (#2540) partly because the limitations would be written down. If Phase 1 ships
behind its gate and the public lock() page still describes exclusion without (2) and (3), an app
author reaches for lock() for read-modify-write and hits the failure the design note predicts, with
no warning anywhere they read — and the tradeoff that justified the decision was never actually paid.
Blocked on nothing, but it should land with or before the Phase 1 enablement, not after.
The Phase 1
lock()guarantee was settled as exclusion-only(harper#483 comment,
docs/record-lock-ownership.md§10). That decision was taken on the condition that the two thingslock()does not promise are documented rather than implied — §10 says so in terms:The design note is the normative source and
DESIGN.mdroutes writers to it, but the §14 workdecomposition assigns issues to every implementation slice and none to the documentation change.
This issue is that owner.
What the public
lock()documentation must sayLifted from §10, which is normative and must not be paraphrased into something weaker:
lock()guarantees exclusive admission of a critical section for a key, and successorfreshness after a clean handoff — the latter only while the key's home still holds that handoff's
dependency set.
lock()changes nothing about how twoconflicting writes resolve, so whatever the pair would do to the record without a lock is what
they do with one, silently and with no error raised. Reachable on a completely clean handoff
when a caller stamps a future
context.timestamp.unreachable, or its native commit settled after the recovery barrier was measured. Both the
predecessor's transaction and the successor's derived write end up wrong, which is exactly the
read-then-conditionally-write case
lock()exists for.X-Replicate-To/confirm=) issuper-user-gated and entangled with residency, and closes none of limitation 3's routes anyway.
pre-push review rounds on Cluster record locks: amortized per-record ownership (Phase 1 of #483) #2498 produced text that got this wrong in three different directions,
so it is worth stating plainly rather than in a caveat.
Why this is worth its own issue
The honesty argument is load-bearing for the decision: exclusion-only was chosen over fenced and
quorum-confirmed (#2540) partly because the limitations would be written down. If Phase 1 ships
behind its gate and the public
lock()page still describes exclusion without (2) and (3), an appauthor reaches for
lock()for read-modify-write and hits the failure the design note predicts, withno warning anywhere they read — and the tradeoff that justified the decision was never actually paid.
Blocked on nothing, but it should land with or before the Phase 1 enablement, not after.
Refs #483, #2540
🤖 Filed by Claude Opus 5 on behalf of Kris.