Skip to content

Document lock()'s exclusion-only limitations in the public API documentation #2547

Description

@kriszyp

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

Refs #483, #2540

🤖 Filed by Claude Opus 5 on behalf of Kris.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    Priority

    P2

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions