Repository navigation
Conversation
A successor ended its watch once a lease and a half had passed on its own clock, so a listing whose answer stalled, or that it read late after a pause, counted silence storage never showed, and it took the lock from an owner that was still beating. The takeover then deleted the old lease directory, claim included, and the owner, which read only the claim, kept beating and serving beside the successor. A failed claim read also counted as no claim. Silence now counts only up to the send of a listing that found no newer beat. Before each beat the owner reads the claim and lock.json, the record whose if-match decides the owner and which never names a replaced lock id again, and fences when either says the lock is no longer its own; a read that gets no answer sends no beat and is retried until the deadline. A takeover keeps the previous owner's lease directory, so an owner whose clock stopped, or one that reads only the claim, stops at its next beat.
|
Recommendation: revise. Do not merge WhyA restarted server could take the S3 cluster lock from a server that was still serving and still writing heartbeats. Schema apply, branch control, and cleanup each assume one writer process. Two serving owners break that contract. WhatThe lease from #1097 had three defects.
HowSilence now lasts only until the send of a listing that still shows the same newest beat. The successor sends that last listing when the silence would end. A late answer leads to another listing. Before each beat the owner reads the claim and ImpactA server that is still writing heartbeats keeps the lock. A server whose lock was replaced, released, or removed stops at its next beat. An owner that cannot read the claim reaches its deadline and fences. One gap remains, and the RFC states it. An owner whose monotonic clock stopped can still admit work until its next beat. That wait is at most one third of the lease. At the default of 30 seconds the wait is 10 seconds. At the maximum of 600 seconds the wait is 200 seconds. The storage scope does not read the lease deadline. Other tasks can send a request before the lease task runs. Blocking
CI on this commit fails the same assertion in Test Workspace and in Storage Upgrade Compatibility. The CI Gate fails because those two jobs fail. Inline, Optional
Inline,
Inline, Tradeoffs and liabilityThis removes an unbounded period with two serving owners. The change keeps the one if-match election. The new cost is one read of Takeover now leaves the claim and at most two beats under the old lock id. Nothing lists
Five more fixes of this kind should extend #1097 added the lease and made the claim elect nobody. This change keeps that rule and closes the two ways a live owner still lost the lock. Open #1176 re-issues a lost conditional write and reads the object back. Success there is equal bytes, so another successor's record stays a conflict. This takeover can keep treating The lease stays S3-only. Lance's object-store guide requires the store to honor conditional puts, and OmniGraph does not check that for the caller. Local and Azure locks stay unleased, as the RFC requires. This change adds no second engine path for another cloud. ValidationMerge base: GQT cannot express a late listing, a stopped clock, or a failed claim read. The owner is the in-source cluster tests. No Local, at the head:
Result: 16 passed, 0 failed, in 6.01s. The run includes Local, merge-base behavior. Temporary tests were added in an isolated checkout and then removed. Nothing was pushed.
Result: 0 passed, 3 failed.
Local inventory, at the head:
Result: failed in 1.64s. The panic text is The same test, with only merge-base CI on this exact head, workflow run
Limitations:
|
…ock bound Review of the lease fix: `take_over` no longer deletes the superseded owner's lease directory, so the mutable-key inventory's registration of that `delete_prefix` described the defect and failed the count. `state_lock::LOCK_RECORD_MAX_BYTES` is now the one bound on every read of `lock.json`, so the lease's ownership check cannot read with a smaller one and stop a live server at its deadline. The control-plane and storage-failure pages say the owner stops on a claim or a lost record at its next beat, at most a third of a lease later; only the lapse is a timer of its own.
|
Recommendation: ready. WhyThe review of What this commit changesThe mutable-key inventory drops its registration of
The control-plane page and the storage-failure page say the owner stops on a claim, or on a record that no longer holds its lock id, at its next beat. That wait is at most one third of a lease. Only a missed heartbeat uses a timer of its own. That timer is two thirds of a lease after the start of the last acknowledged beat. Status of the earlier findingsThe blocking finding is fixed. On The first optional finding is fixed. The control-plane page said the owner fences at once on a claim or a lost record. It now says the reads before the next beat do that, at most one third of a lease later. The storage-failure page said the owner stops every storage write at once in that case. It now says the owner stops at its next heartbeat, with the same bound. The stopped-clock sentence still says the claim or the replaced record stops the owner at its next beat. The three sentences agree. The second optional finding is fixed for every read that can fence a live S3 server or that decides a conditional update. Those reads are The gap the RFC states is still open. The storage scope does not read the lease deadline. This commit does not say that it does. PR 1221 is the change that closes that gap. This review treats that PR as context only. BlockingNone. Optional
Inline, Tradeoffs and liabilityThis commit removes a registration that described a delete the lease fix already removed. It also removes a second copy of the 64 KiB limit. A smaller limit on the owner read makes A later read that can fence the owner has to pass the same constant. A helper that reads The if-match on Five more fixes of this kind should extend Open PR 1221 stacks on this head and moves the deadline into the storage scope. Open PR 1176 re-issues a lost conditional write and reads the object back. Success there is equal bytes, so another successor's record stays a conflict. Open PR 1214 would send maintenance through the serving owner. That work stays safe only while one process serves. This commit does not change those contracts. The lease stays on S3 only. Lance's object-store guide says a safe concurrent ValidationMerge base with current Reviewed head: Parent: GQT cannot express an inventory count or a lock-record byte limit. No The inventory defect is absent on the merge base. There the delete exists and the registration matches it. The defect starts at Local, isolated worktree, Parent
Result: failed in 1.70s. The panic text is Head Result: passed. 1 passed, 0 failed, in 1.61s. Head, cluster lease tests:
Result: 16 passed, 0 failed, in 6.01s. The run includes This commit does not change those lease tests. The earlier review showed the new cases fail on the merge base. This review does not run them again there. CI on this exact head. Every completed check passed. The skipped checks are the tag-publish jobs.
Limitations:
|
|
Addressed in the new commit on this branch:
The remaining limit, where another task could send before the lease task fenced the scope after a process pause, is closed separately in #1221, which is stacked on this PR. |
Amends §7 of the accepted storage outcomes and restartable ownership RFC. Found while reviewing the S3 lock lease (#1097). Two defects let a restarted server take over the cluster lock from a server that was still serving and heartbeating, leaving two serving owners. The schema-apply, branch-control and cleanup boundaries all assume one writer process, so this broke them. A third defect let an owner that could not read its claim keep beating.
What went wrong
watch_and_claimstopped watching once a lease and a half had passed on the successor's clock. It did not check when the last listing was sent. A listing shows the beats as storage read them, which is some time after the request was sent. So an answer that stalled, or that the successor read late after a pause, counted silence that storage never showed. Example with the default 30 s lease: the successor's second listing is sent at 5 s and its answer arrives at 65 s. The successor then saw 65 s of "silence" while the owner had beaten six times.take_overrandelete_prefixon the old lease directory,superseded.jsonincluded. The owner checked only that claim and never re-readlock.json. Its next claim read came up to a third of a lease later, almost always after the delete. So it kept beating into the recreated directory and kept serving. The same delete also defeated the claim for an owner whose monotonic clock had stopped (a suspended VM), the case the claim exists for.The fix
lock.jsonwith the claim before every beat.lock.jsonis the record whose if-match decides the owner, and it never names a lock id again. The owner fences when a claim exists or the record no longer holds its lock id. That covers a takeover, a release, and acluster force-unlockof a live server's ID. It works whatever happened to the lease directory, including after a takeover by an earlier binary that deleted it.__cluster/lease/.Docs: the lease sections of
docs/dev/control-plane.md,docs/user/operations/storage-failures.md, and an amendment to the storage-outcomes RFC (§7 body plus a Decision-log entry naming the sentences it replaces).Tests
Every new or extended case below fails on
main:lease::tests::a_late_listing_cannot_unseat_a_beating_owner_issue_1067(new): one of the successor's first four listings is answered two leases late. The successor must refuse and write no claim, and the owner must keep its lease. Onmainthe second listing yieldsWatch::Silent, a claim, and the owner fencedSuperseded.lease::tests::a_claim_or_a_lost_record_fences_the_owner_issue_1067(wasa_claim_fences_the_owner_issue_1067): adds the record replaced, released or removed. Each fences at the next beat.lease::tests::an_owner_fences_itself_when_its_beats_lapse_issue_1067: adds a claim the owner cannot read, which must end inLapsed, not endless beats.lease::tests::one_failed_heartbeat_does_not_fence_a_live_owner_issue_1067: adds three unanswered reads, which must not fence. This case passes onmaintoo; it guards the retry.state_lock::tests::a_superseded_owner_whose_clock_stopped_fences_at_its_next_beat_issue_1067(new): the owner and the successor each run on their own paused runtime. The owner's runtime is not driven while the successor watches and takes over, so its clock stops. When it resumes, it must fenceSupersededat its next beat, with the lease directory kept and with it removed.state_lock::tests::a_restarted_server_takes_over_a_silent_owners_lock_issue_1067: now asserts that the claim survives the takeover.crates/omnigraph-cluster/tests/failpoints.rsis not extended. Its fixtures are local roots, which carry no lease, and the cluster's seams are fail-only. The timing defects need control of time, which paused-time tests give exactly.Limits that remain