Skip to content

feat(lease): fence descriptor-lease release by SWIM verdict and holder incarnation - #347

Closed
EnRaiha wants to merge 4 commits into
mainfrom
fix/p2-lease-skew
Closed

EnRaiha wants to merge 4 commits into
mainfrom
fix/p2-lease-skew

Conversation

@EnRaiha

@EnRaiha EnRaiha commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Why

Two defects in the descriptor-lease release path, both from the P2 consensus-safety review:

  1. The drain wait compared a holder's stamped expires_at against this node's wall clock. The holder stamps from its own clock and its HLC is never merged in, so a fast local clock could retire a live holder's lease early — the window the drain exists to close.
  2. Release waited for topology removal or expiry. A crashed holder stayed a member until the removal path ran, and its leases blocked every DDL drain on those descriptors for the full lease duration.

Part of #165 (P2 — cluster consensus safety).

What

  • Skew bound. lease_expired gives the stamped deadline the same allowance the HLC ingress enforces (MAX_CLOCK_SKEW_NS); a saturated deadline is never retired — the filter drops holds only where the lapse is certain.
  • SWIM-fed liveness. LeaseHolderLiveness (+ LeaseHolderLivenessHook): Dead/Left mark a holder, Alive revives (verdicts are refutable), Suspect is a no-op by decision. The GC collects a holder absent from topology or marked gone — collect_non_member_lease_releases(topology, cache, liveness). The set is created with the node's cluster handle, registered as a SWIM subscriber at subsystem start (the same topology-backed resolver the routing hook uses), and handed to the RaftLoop via with_lease_liveness.
  • Incarnation fencing. DescriptorLeaseGrantFenced { lease, holder_incarnation } is appended last (zerompk numbers variants by position) and proposed only under can_activate_feature(LEASE_FENCING_VERSION); mixed-version clusters keep the unfenced grant. MetadataCache.lease_incarnations records the stamp; a verdict at N releases leases stamped <= N, so a restarted node that re-acquired is unaffected; an unstamped lease releases as before.

Validation

  • cargo test -p nodedb-cluster --lib lease — 17 passed (collector incl. dead-member, fenced-newer-lease, revive; liveness hooks; clamp boundary).
  • cargo test -p nodedb-cluster --lib metadata_group::cache — 2 passed (fenced grant records; release clears).
  • cargo check -p nodedb --all-targets — clean; repository preflight passes.

Notes

  • A SWIM false positive now releases a live node's leases earlier than expiry would. That is the Dead verdict's authority — the same one topology removal already had — and an Alive refutation clears the mark.
  • state/fields.rs and state/init.rs stay at their base line counts: two adjacent doc comments were compacted to make room for the new field.
  • Follow-up (not in this PR): a propose-side gate test for the fenced variant.

The drain wait counted a holder's lease as expired by comparing the
holder's stamped expires_at against this node's wall clock. A peer stamps
from its own clock and its HLC is never merged in, so a fast local clock
could retire a live holder's lease early — the exact window the drain
exists to close.

lease_expired gives the stamped deadline the same allowance the HLC
ingress enforces (MAX_CLOCK_SKEW_NS): a lease is retired only once even a
clock that far ahead would agree it lapsed. A deadline whose addition
saturates is never retired — the filter drops holds only where the lapse
is certain.
The lease GC releases a holder's descriptor leases on topology absence,
which lags a crash: the node stays a member until the removal path runs,
and its leases block DDL drains for the whole lease duration. This adds
the faster signal as a lease-specific input — routing, placement, and
rebalancing never read it.

Dead and Left mark a holder; Alive revives it, because a verdict is
refutable by a higher incarnation and a refuted node is serving again.
Suspect stays a no-op: it is transient and the holder may still hold its
lease. The subscriber runs on the detector task and only touches a set.
The periodic lease GC released only holders absent from ClusterTopology,
so a crashed holder blocked every DDL drain on its descriptors until the
lease expired. The GC now also releases a holder SWIM marked Dead/Left:
the liveness set is created with the node's cluster handle, registered as
a SWIM subscriber at subsystem start (with the same topology-backed
resolver the routing hook uses), and handed to the RaftLoop through
with_lease_liveness. Suspect is not a release signal, and a refuted
verdict clears the mark again.
…ation

A SWIM Dead verdict releases a holder's leases, but a node that restarted
(re-acquired at a higher incarnation) could still be fenced by a stale
verdict. The fenced grant variant carries the holder's incarnation:
DescriptorLeaseGrantFenced is appended last — zerompk numbers variants by
position — and is proposed only once the cluster reports
LEASE_FENCING_VERSION, so mixed-version clusters keep the unfenced grant
and run without the fence. The cache records the stamp, the liveness hook
stores the incarnation the verdict landed at, and the GC releases a lease
only when its stamp is <= that incarnation. An unstamped lease (pre-fencing
or mixed-version) releases as before.
Copilot AI lite review requested due to automatic review settings September 19, 2026 07:46
@EnRaiha EnRaiha added the area:cluster-raft Raft, replication, consensus safety label Sep 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@farhan-syah

Copy link
Copy Markdown
Member

Closing. A SWIM Dead verdict does not stop a partitioned-but-alive holder from using its descriptor lease, so releasing on that verdict lets DDL proceed under a live lease. The PR notes the false-positive case, and no holder-side fence covers it. Release must stay expiry-bound (with the skew allowance), unless the holder fences itself first. The maintainers will handle this in-tree.

@farhan-syah
farhan-syah deleted the fix/p2-lease-skew branch September 23, 2026 01:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cluster-raft Raft, replication, consensus safety

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants