Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
2975ced
cas: classify transient GC round failures and stream emulated blob pu…
filimonov Sep 1, 2026
bf77615
Classify a relink-confirm refusal as NO_REPLICA_HAS_PART, not NETWORK…
filimonov Aug 26, 2026
b9140d4
cas: wire-keys phase 1 — move every codec onto WireKey carriers (no k…
filimonov Sep 1, 2026
540890f
cas: wire-keys phase 2 — cut the wire-format JSON keys to semantic names
filimonov Sep 1, 2026
23e2d8b
cas: wire-keys phase 3 — proof, review polish, and the ref-protocol b…
filimonov Sep 1, 2026
b55e445
cas: reuse the JSON object/row reader across a stream's rows; decode …
filimonov Sep 1, 2026
acede29
docs: recommend single-replica merges for `CAS`
filimonov Aug 31, 2026
f7c64c8
Allow EXPORT PARTITION from a source on a CAS disk
filimonov Aug 31, 2026
572c10a
add settings
k-morozov Sep 2, 2026
e424083
add validation
k-morozov Sep 2, 2026
f6a7881
cas: don't refuse a GCS mount just because the versioning probe can't…
filimonov Sep 4, 2026
5740d2a
cas: scope the relink-confirm refusal to the asked-about ref, not the…
filimonov Sep 4, 2026
0349d57
cas: key the manifest decode cache by id alone, no HEAD on a hit
filimonov Sep 4, 2026
adc0fa7
cas: introduce the CasRequests/CasOperation request engine (core)
filimonov Sep 4, 2026
37c9bd4
cas: migrate every CAS subsystem onto CasRequests/CasOperation
filimonov Sep 4, 2026
c3f7b20
cas: rename Incarnation to Etag/Dialect, migrate the gtest suite onto…
filimonov Sep 4, 2026
d528693
cas: review follow-ups and test hardening after the request-engine mi…
filimonov Sep 4, 2026
8f6cd74
cas: overlap the GC fold's small-object reads on a bounded pool (cas_…
filimonov Sep 4, 2026
01043cb
cas: a same-pool fetch always relinks onto the pool's disk
filimonov Sep 4, 2026
a4b3cbc
cas: a disk's teardown no longer waits out a GC round
filimonov Sep 4, 2026
9912221
cas: GC round cost on write-once keys (mount-floor memo, late reads, …
filimonov Sep 4, 2026
4ec7554
cas: hot-key write lane, phase A — serialize and combine a process's …
filimonov Sep 4, 2026
38661e7
cas: fix ASan use-after-scope in test hooks that outlive their captur…
filimonov Sep 4, 2026
438e063
cas: log the single-attempt S3 client's failed attempt below Error
filimonov Sep 4, 2026
427e8ef
Merge branch 'antalya-26.6' into feature/antalya-26.6/CAS-improvements
filimonov Sep 4, 2026
67d337f
cas: fix a genuine race and a fatal-abort hazard in the chunked-flush…
filimonov Sep 4, 2026
249c994
cas: guard the fault state of a gtest backend that GC reads from a po…
filimonov Sep 5, 2026
45325aa
cas: a resurrect compare-swaps the marker it already read instead of …
filimonov Sep 5, 2026
9abc940
cas: bring two test_cas_gcs tests onto the request engine's contract
filimonov Sep 5, 2026
7c7c6bd
cas: prove an existing pool by reading `_pool_meta`, not by enumerati…
filimonov Sep 5, 2026
f725984
cas: the startup residual check reads one small page and stops at the…
filimonov Sep 5, 2026
b39b1b5
cas: test_cas_mount_renewal_retry asserts the renewal's terminal even…
filimonov Sep 5, 2026
68b62d8
Merge PR #2307 (cas/configurable-mount-lease-timing) into feature/ant…
filimonov Sep 7, 2026
96e9485
cas: reissue a control-plane request at once when the failure says th…
filimonov Sep 7, 2026
9a6bcb6
cas: budget an attempt by its whole envelope and fuse a first attempt…
filimonov Sep 7, 2026
d651b23
cas: an explicit unsafe no-delay remount after a lapsed lease
filimonov Sep 7, 2026
5882d62
cas: recommend a longer S3 keep-alive on every CAS disk, in docs and …
filimonov Sep 7, 2026
ecc96eb
cas: pin the backend/budget mismatch as a death test under sanitizer …
filimonov Sep 7, 2026
a98ea13
ci: point the next ASan memory-ceiling investigation at the thread count
filimonov Sep 7, 2026
089c9e8
tests: forget inline CAS disks at the end of every stateless test tha…
filimonov Sep 7, 2026
9429f1e
tests: relink_liveness accepts the fake's extended delay echo
filimonov Sep 7, 2026
801664c
tests: widen the fuse-timeout integration test's margins for sanitize…
filimonov Sep 7, 2026
f4fc01d
tests: give the lost-response renewal test more polling room under sa…
filimonov Sep 7, 2026
89dedb1
tests: keep the test_cas_s3 connect timeout inside the default lease …
filimonov Sep 7, 2026
61c6caa
cas: a sibling that already won namespace birth is Superseded, not a …
filimonov Sep 7, 2026
8be816a
s3: the read identity check fires only when bytes of two incarnations…
filimonov Sep 8, 2026
140ad05
s3: the CAS batch delete honours support_batch_delete; the GC admits …
filimonov Sep 8, 2026
bbdc54a
s3: shutdown and startup toggle request processing on the single-atte…
filimonov Sep 8, 2026
6dd3656
cas: move the CAS error codes out of the range upstream uses
filimonov Sep 7, 2026
90ac695
io: ObjectStorageRetryProfile and ObjectStorageControlRequest live in…
filimonov Sep 7, 2026
81ab92c
object-storage: the new iterate overload stays visible in AzureObject…
filimonov Sep 7, 2026
93089cd
cas: inspect holds the pool for the whole operation
filimonov Sep 7, 2026
6f16a82
docs(cas): document cas_gc_bulk_delete_chunk_keys and the Stopped out…
filimonov Sep 7, 2026
15f4fd8
cas: say what happens to the keys of a bulk-delete chunk that exhaust…
filimonov Sep 7, 2026
e5605ee
s3: the readObject refresh-callback comment names what the copied cal…
filimonov Sep 7, 2026
9271b16
cas: gtest pool hooks own their clock and sleep state instead of refe…
filimonov Sep 8, 2026
112a201
ci: move the local-run tooling fixes off this branch
filimonov Sep 8, 2026
1ca3d62
fetch: CAS routing comments keep the reason and drop the provenance
filimonov Sep 8, 2026
850acfa
fetch: the forced content-addressed disk choice lives next to the oth…
filimonov Sep 8, 2026
24a0afe
object-storage: the default removeObjectsIfExistUnderProfile forwards…
filimonov Sep 8, 2026
0c8da46
errors: the fork's error-code range is written down
filimonov Sep 8, 2026
cacaff1
s3: the object storage keeps its MultiVersion client member; the refr…
filimonov Sep 8, 2026
fd1717b
s3: shutdown and clone comments say only what is true and needed
filimonov Sep 8, 2026
cd284f2
s3: move fork-added client/attempt-seed tests out of gtest_aws_s3_cli…
filimonov Sep 8, 2026
9b09988
s3: move fork-added identity-check tests out of gtest_readbuffer_s3.cpp
filimonov Sep 8, 2026
e4bf7a8
s3: move fork-added write tests out of gtest_writebuffer_s3.cpp
filimonov Sep 8, 2026
173a439
s3: the delivery-time identity bookkeeping comment says the one thing…
filimonov Sep 8, 2026
090bf01
io: ObjectStorageControlRequest and the attempt-seed settings describ…
filimonov Sep 8, 2026
92e993b
io: collapse the two identical DEBUG log branches in makeSinglepartUp…
filimonov Sep 8, 2026
2c3c71f
s3: deleteFileFromS3 takes an optional attempt seed; the single-objec…
filimonov Sep 8, 2026
b03efbf
tests: give every inline content-addressed disk a per-run unique iden…
filimonov Sep 8, 2026
16fd0d6
cas: unify the decommission drain clock with the mount lease's boot c…
filimonov Sep 8, 2026
6d34b1d
cas: assert the S3 single-attempt connect cap by difference under san…
filimonov Sep 8, 2026
d876cda
cas: guard ChasingWriterBackend's hook state with a mutex under TSan
filimonov Sep 8, 2026
9dfa69c
cas: every gtest hook handed to a Pool owns its state instead of refe…
filimonov Sep 9, 2026
96fe21d
cas: the connect-cap gtests prove the cap without a clock
filimonov Sep 9, 2026
e5690c3
cas: test_cas_mount_renewal_retry stops depending on real time
filimonov Sep 9, 2026
b21a9cc
cas: the streaming-publish gtests in gtest_cas_backend.cpp cannot hang
filimonov Sep 9, 2026
8be3f30
tests: pin index_granularity in 04299_cas_projection_inline_disk
filimonov Sep 9, 2026
711efad
poco: make HTTPServerConnection's stop flag atomic
filimonov Sep 9, 2026
4e57fd7
cas: test S3 clients against ephemeral mock servers use one connectio…
filimonov Sep 9, 2026
b13776d
Merge branch 'antalya-26.6' into feature/antalya-26.6/CAS-improvements
CarlosFelipeOR Sep 9, 2026
a8d9dd1
cas: stop the test mock servers without Poco's abort path
filimonov Sep 9, 2026
4724615
Revert "poco: make HTTPServerConnection's stop flag atomic"
filimonov Sep 9, 2026
f377ba3
Merge branch 'feature/antalya-26.6/CAS-improvements' into feature/ant…
filimonov Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 8 additions & 6 deletions docs/en/antalya/cas/architecture/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,17 @@ storage, and GC would silently stop reclaiming.
`runCapabilityProbe` (`Backend/CasProbe.cpp`) runs a throwaway-key battery against every writable
mount, described in full on the [bucket requirements](/antalya/cas/bucket-requirements) page. It is
fail-closed: any check that does not pass throws `NOT_IMPLEMENTED` naming the specific failure, and
the mount refuses to become writable. Two further gates run as the battery's opening steps, and one
the mount refuses to become writable. The one tolerated exception is a versioning probe that cannot
answer at all, described in the first bullet below. Two further gates run as the battery's opening steps, and one
sits genuinely alongside it. The distinction matters: because the versioning check runs *inside* the
battery, skipping the battery used to skip it too, which is exactly why the third gate exists.

- `checkPoolPreconditions` — inside the battery. On the `GCS`-dialect combination only, requires bucket versioning to be
*verifiably* off. A confirmed `Enabled` and an inconclusive probe both throw: `CAS` cannot assume
the safe answer here, because what it would do on a versioned bucket is delete objects it believes
it reclaimed. A probe is inconclusive when the credential may not read the bucket's versioning
configuration, or when the backend cannot answer at all.
- `checkPoolPreconditions` — inside the battery. On the `GCS`-dialect combination only, checks that
bucket versioning is off. A confirmed `Enabled` throws: what `CAS` would do on a versioned bucket is
delete objects it believes it reclaimed. An inconclusive probe — the credential may not read the
bucket's versioning configuration, or the backend cannot answer at all — logs a warning and lets
the mount proceed, since it is not evidence of a versioned bucket; verifying it then falls to the
operator, as it already does for soft delete.
- `checkSkipAccessCheckSupport` — alongside the battery, in the skip branch of `Pool::open`, since it
is the gate that decides whether the battery may be skipped at all. It asks whether the backend may serve a writable mount that skips the
battery at all. The `GCS`-dialect combination refuses, so `skip_access_check = true` cannot reach a
Expand Down
1 change: 1 addition & 0 deletions docs/en/antalya/cas/architecture/garbage-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ the user-facing configuration surface.
| Setting | Default | Bounds |
|---|---|---|
| `cas_gc_meta_pool_size` | 16 | bounded pool for condemn-marker writes |
| `cas_gc_read_concurrency` | 16 | bounded pool for the fold's read-ahead; `1` disables |

## Observability {#observability}

Expand Down
2 changes: 1 addition & 1 deletion docs/en/antalya/cas/architecture/manifests-and-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ swept for that root.
flowchart TD
A["LIST one page of cas/manifests/<br/>freeze candidates with exact GET"] --> B{"build-prefix eligible?<br/>durable watermark fact only"}
B -->|"epoch less than lease epoch"| ELIG["eligible, old-epoch debris"]
B -->|"same epoch, min_active clears build_seq"| ELIG
B -->|"same epoch, min_active_build_sequence clears build_seq"| ELIG
B -->|"no lease, or epoch ahead, or build may be live"| SKIP["skip"]
ELIG --> C["protection view: committed manifests<br/>plus live precommits<br/>plus manifests with an unfolded minus-one"]
C -->|"key protected"| SKIP2["skip"]
Expand Down
78 changes: 50 additions & 28 deletions docs/en/antalya/cas/architecture/mounts-and-leases.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ doc_type: 'reference'

Page 4 of 4 in the CAS architecture set. Covers server identity, the mount lease that fences
writers, and the server-scoped control-plane objects. No external coordinator is involved: there
is no ZooKeeper/Keeper client anywhere in this protocol — `MountLeaseKeeper` is a local lease
is no ZooKeeper/Keeper client anywhere in this protocol — `MountLeaseRenewer` is a local lease
*renewer*, not a Keeper client.

## `cas_server_root_id` — the identity {#server-root-id}
Expand Down Expand Up @@ -61,51 +61,55 @@ Two failure modes this closes:
over, regardless of lease expiry.
- A **same-uuid live twin** (two processes sharing one uuid file and `server_root_id`) is caught separately, by
the mount claim's token-stability observation, and aborts with an operator-facing message rather
than corrupting the pool.
than corrupting the pool — this is the default behavior, with `cas_unsafe_remount_no_delay` off.
With it on, a same-uuid claim over such a slot reclaims at once instead of observing (see
`cas_unsafe_remount_no_delay` in the configuration reference).

## The mount lease {#mount-lease}

One object, `gc/server-roots/<server_root_id>/mount`, carries **both** the liveness lease and the build
watermark — there is no separate watermark object. `MountLease` fields: `server_uuid`,
`writer_epoch`, `write_attempt_id`, `hostname`, `pid`, `started_at_ms`, renewal `seq`,
`expires_at_ms`, `min_active` (the build-watermark floor), and `gc_fenced`.
`expires_at_ms`, `min_active_build_sequence` (the build-watermark floor), and `gc_fenced`.

- **Logical renewal identity.** Each holder-originated body has a fresh nonzero
`write_attempt_id`. One logical renewal fixes one immutable `(key, bytes, expected token,
write_attempt_id)` tuple before I/O. Every physical retry repeats it byte-for-byte; a later GC
fence preserves the observed ID, while reclaim and successor bodies mint new IDs.
- **Resolve before retry.** A transient or ambiguous conditional `PUT` is followed by one exact
`GET`. The keeper adopts the result only when the complete body, including `write_attempt_id`,
`GET`, except that an attempt whose transport error names a failed connection is reissued first
after a flat pause and settled by the reissue's own answer (a 2xx) or by the exact `GET` that
follows its `412`. The renewer adopts the result only when the complete body, including `write_attempt_id`,
equals its immutable request. If the predecessor token is still current, another identical `PUT`
may follow bounded backoff. A same-pair twin, GC-fenced body, successor, foreign holder, or absent
body is never treated as this renewal.
- **Absolute deadline.** Renewal uses `CLOCK_BOOTTIME`, not `CLOCK_MONOTONIC`, so a VM resumed from
suspend correctly observes itself expired. Its absolute deadline is the minimum of the existing
request-operation budget and the last confirmed lease deadline minus the safety margin. The
controller checks that one configured attempt still fits before each backend `PUT` or resolving
controller checks that one attempt envelope still fits before each backend `PUT` or resolving
`GET`, after each interruptible backoff, and before accepting success. A retry, `GET`, response
timestamp, or wall-clock step never extends authority.
- **Cadence.** The runtime normally starts a logical renewal every `mount_renew_period` (default
10 s), with TTL `mount_lease_ttl_ms` (default 30 s, TTL/3 renewal ratio). The next beat is anchored
- **Cadence.** The runtime normally starts a logical renewal every `cas_mount_renew_period_ms` (default
10 s), with TTL `cas_mount_lease_ttl_ms` (default 30 s, TTL/3 renewal ratio). The next beat is anchored
at the committed body's pre-I/O BOOTTIME start. A slow recovery therefore causes an immediate
catch-up beat when the nominal cadence has elapsed; it does not wait a fresh full period after the
response.
- **Per-write recheck.** Every durable write or delete captures the fence generation at admission
and rechecks it immediately before the object-store call and on every conditional retry. Reads
are not gated.
- **Request-budget admission.** `refAppendFenceOk` refuses to *start* a ref-log attempt unless
`attempt_timeout + safety_margin` fits inside the remaining lease, rejecting with
`BAD_ARGUMENTS` at request-admission time rather than mid-flight.
`2 × envelope + safety_margin` fits inside the remaining lease (a write and its settlement read),
rejecting with `BAD_ARGUMENTS` at request-admission time rather than mid-flight.

**Losing the lease is neither read-only mode nor a process abort.** `MountLeaseKeeper` is a
**Losing the lease is neither read-only mode nor a process abort.** `MountLeaseRenewer` is a
synchronous durable-slot state machine. A committed result advances its token, sequence, confirmed
BOOTTIME deadline, and cadence anchor. Any admitted deterministic failure, confirmed conflict, or
ambiguity left at the deadline/attempt limit moves it to `RenewalTerminal`; it cannot mint another
body or publish a clean farewell. Owner cancellation before any request is the only
`NotAttempted` result and leaves clean release possible. Cancellation after a request was sent is
terminal because that request may still land.

After the keeper call returns, `CasMountRuntime` consumes the result. A terminal result trips the
After the renewer call returns, `CasMountRuntime` consumes the result. A terminal result trips the
local fence (latches `lost`, bumps the fence generation, moves the in-process runtime to
`TransientNotLive`) and latches one self-remount generation. A confirmed foreign/successor or
same-pair conflict remains a typed fail-closed error; it is never adopted. A real fence still costs
Expand All @@ -115,9 +119,25 @@ inside authority already proved by the last confirmed lease.

GC's own view of a dead server is symmetric and clock-skew-immune: a slot becomes fence-eligible
only after the leader observes the *same* renewal token hold stable, on its own monotonic clock,
for `TTL + TTL/20 + cadence` — the identical formula a re-mounting server uses to wait out a
predecessor. The stamped `expires_at_ms` never participates in that decision; wall-clock `now` is
audit-only.
for `TTL + floor(TTL/20) + period` — close to, but not identical to, the threshold a re-mounting
server uses to wait out a predecessor, which observes `TTL + floor(TTL/20) + max(1,
floor(period/2))`. Both thresholds are evaluated purely on the observer's own clock and its own
configured `TTL`/`period`; nothing about the writer's timing travels on the wire. The stamped
`expires_at_ms` never participates in either decision — it is a writer-stamped diagnostic used by
`system.cas_mounts` and by the non-authoritative decommission epoch-recovery precheck, never an
authorization; local fencing is derived instead from the confirmed request's pre-I/O `BOOTTIME`
anchor plus the TTL, and wall-clock `now` stays audit-only.

Every server sharing a pool must therefore run the identical `cas_mount_lease_ttl_ms` and
`cas_mount_renew_period_ms`: a member or GC leader configured with a shorter threshold than its
peers can fence out a healthy peer whose token-update gap merely exceeds that shorter threshold —
a peer renewing frequently stays live, one that missed a renewal does not. Change these values only
with every member of the pool stopped; a graceful restart removes only that member's own startup
observation and does not make mixed thresholds safe. With the defaults (TTL 30 s, period 10 s,
margin 2 s), `TTL − margin − period − 2 × envelope = 4 s` is the scheduling-lateness budget before
the first renewal attempt of a period can begin, where `envelope = attempt_timeout + 2 × cap` and
`cap` is `attempt_timeout` when the disk's `connect_timeout_ms` is `0`, else
`min(connect_timeout_ms, attempt_timeout)` (7 s with defaults).

## The two monotone counters {#counters}

Expand All @@ -133,9 +153,9 @@ into "not found".

Global build ordering is the **pair** `(writer_epoch, build_seq)` compared lexicographically — the
exact comparison GC uses for eligibility. The durable authority for both is the mount object
itself: no mount means no deletion authority means nothing is swept. `min_active`, the oldest
itself: no mount means no deletion authority means nothing is swept. `min_active_build_sequence`, the oldest
in-flight `build_seq`, rides in the same mount object as the watermark floor; `UINT64_MAX` in
`min_active` is the farewell/retired sentinel, not a real build.
`min_active_build_sequence` is the farewell/retired sentinel, not a real build.

## Mount claim outcomes {#claim-outcomes}

Expand All @@ -153,9 +173,10 @@ a `MountClaimResult::Kind` together with a `MountPriorState` describing which ce
| `MountPriorState` | Certificate that justified the reclaim |
|---|---|
| `None` | no reclaim needed (fresh claim or same-epoch refresh) |
| `Clean` | the predecessor's own graceful farewell (`min_active == UINT64_MAX`) |
| `Clean` | the predecessor's own graceful farewell (`min_active_build_sequence == UINT64_MAX`) |
| `Fenced` | GC's own threshold-gated fence-out (`gc_fenced`) |
| `UncleanObserved` | this claimant's own token-stability observation held for the full `TTL + drift` window |
| `UncleanUnsafe` | the operator's explicit `cas_unsafe_remount_no_delay` authorization carried the slot's exact token — not a certificate of death |

## Behavioral mount-slot model {#mount-state-machines}

Expand All @@ -166,12 +187,13 @@ the claim outcomes above and is shown here as behavior, not as a type in the cod
stateDiagram-v2
[*] --> Absent
Absent --> Live: claimMount putIfAbsent, seq=1
Live --> Live: keeper beat, putOverwrite seq+1
Live --> Live: renewer beat, putOverwrite seq+1
Live --> Fenced: GC observes a stable token past threshold, gc_fenced=1, body preserved
Live --> Terminated: certified drain, terminal farewell (expires_at=now, min_active=MAX)
Live --> Terminated: certified drain, terminal farewell (expires_at=now, min_active_build_sequence=MAX)
Fenced --> Live: same-uuid claim with a fresh writer_epoch, instant reclaim
Terminated --> Live: same-uuid claim with a fresh writer_epoch, instant reclaim
Live --> Live: same-uuid claim, proven-dead token via UncleanObserved
Live --> Live: same-uuid claim under cas_unsafe_remount_no_delay, no observation
Fenced --> Fenced: same uuid and epoch claim, FencedSelf, no write
Live --> Absent: decommission tail, mount then epoch then owner tombstone
Terminated --> [*]
Expand Down Expand Up @@ -202,24 +224,24 @@ under a live mount is an operator-level event.

**Writable open** runs in a strict order: bootstrap-residual proof, capability probe under a
random per-mount prefix, pool-meta create-or-validate, `validateServerRootId`, owner claim,
`allocateWriterEpoch`, mount claim and synchronous keeper start, materialization grace if the
predecessor was unclean (default 30 s), arm the fence, then create and release the runtime-owned
renewal and remount workers before the writable pool becomes externally visible. If the grace period
consumed the TTL, one fresh synchronous renewal re-anchors the deadline before the fence is armed.
`allocateWriterEpoch`, mount claim and synchronous renewer start, arm the fence, then create and
release the runtime-owned renewal and remount workers before the writable pool becomes externally
visible. If the claim consumed the TTL, one fresh synchronous renewal re-anchors the deadline
before the fence is armed.
Failure to construct either worker joins the partial pair, closes the fence, and fails the writable
open. No incident path constructs a thread.

The renewal and remount workers are separate and long-lived under one stable `CasMountRuntime`.
`scheduleRemount` increments a requested-generation latch and wakes the persistent remount worker,
including while an older generation is active. Before keeper replacement, remount requests
`ParkRequested` and waits for the renewal driver to report `Parked`, which proves that no keeper call
including while an older generation is active. Before renewer replacement, remount requests
`ParkRequested` and waits for the renewal driver to report `Parked`, which proves that no renewer call
is in flight. A successful remount handles only its snapshotted generation; a newer request is
processed before renewal resumes.

**Clean unmount:** request stop and join both persistent workers, drain the ref lanes, and only if
the drain *certified* quiescence call `MountLeaseKeeper::release` on an `Active` keeper to write the
terminal farewell (`expires_at_ms` already expired, `min_active = UINT64_MAX`). That sentinel is what
lets a successor reclaim instantly. A `RenewalTerminal` keeper, an unresolved ref write, or a sent
the drain *certified* quiescence call `MountLeaseRenewer::release` on an `Active` renewer to write the
terminal farewell (`expires_at_ms` already expired, `min_active_build_sequence = UINT64_MAX`). That sentinel is what
lets a successor reclaim instantly. A `RenewalTerminal` renewer, an unresolved ref write, or a sent
renewal ambiguity writes no farewell — an unearned farewell would let a successor start mutating
while a stale conditional request from the predecessor is still in flight.

Expand Down
Loading
Loading