Repository navigation
Conversation
…y read or write it LeaderElectionImpl.handlePathNotification() ignored the notifications unless the election had settled (LeaderIsPresent), while an election cycle reads the lock, and may write it, as ElectionInProgress. When the store notified the deletion of the lock before it delivered the result of a read or a write that came before the deletion, the election dropped the deletion and settled on what was gone: - A candidate that read the lock of a leader that then went away followed it. It kept reporting that leader, and nothing made it elect again: Created notifications are ignored, and only a Reconnected or SessionReestablished event revalidates. With no other candidate, nobody led. - An election that wrote the lock, which was then deleted, led without it. ZooKeeper isn't exposed: ZKMetadataStore completes the operations and dispatches the watch events on its single event thread, in order. Oxia is: OxiaMetadataStore completes them on the Oxia client's threads, and the notifications come on a separate stream dispatched on the event thread. The election now counts the deletions of the lock it's notified of. A cycle takes the count before it reads the lock and before it writes it, and reads the lock again instead of settling if the count changed.
codelipenghui
left a comment
There was a problem hiding this comment.
Reviewed head ccad7a776c1eb935377d8811f2d81728f0f6791c.
The deletion counter addresses the reported missed-notification races, but the successful-write retry introduces a shutdown regression: closing during the new reread can leave the election's own live ephemeral lock behind and prevent takeover. Please address the ownership-cleanup issue noted inline.
Validation: the existing LeaderElectionImplTest passed all 12 reported cases at this head, including the new read/write regressions. A deterministic review-only shutdown test failed on this head and passed with the parent production implementation. The original read and write defects were also reproduced against the parent. The shutdown reproduction uses controlled notification/future ordering on the memory store; it is not a natural-timing Oxia reproduction.
| // The lock may have been deleted once written: read it again rather than lead without it | ||
| log.debug().attr("path", path).attr("value", value) | ||
| .log("A deletion of the lock was notified while taking it. Reading it again."); | ||
| FutureUtil.completeAfter(result, elect()); |
There was a problem hiding this comment.
[P1] Preserve ownership cleanup across this reread
A late deletion notification for the previous lock can change the counter even though this successful put's replacement lock still exists. If close() runs while elect() awaits its reread, leaderElectionState is still NoLeader, so asyncClose() skips deletion. The delayed read then rejects Closed without cleaning up the acquired lock.
With the metadata session still alive, other candidates keep following the closed participant and cannot take over until the abandoned key is deleted or the session ends. A deterministic local test holding this reread fails on this head with an own ephemeral lock remaining after close; the identical test passes with the parent implementation.
Please retain provisional ownership across revalidation and release it on close using ownership/version checks, without prematurely publishing Leading. Add a regression for late old deletion → successful creation → pending reread → close while the underlying store remains open.
…ds the lock again A cycle that took the lock while a deletion of the lock was notified reads the lock again before it leads. The deletion may be of an older lock, so the lock may still be the election's, but asyncClose() released the lock only when the election led: closed during that read, the election left its lock behind, and the other participants followed the closed election until its session ended. The election now holds the lock provisionally from that write until a cycle settles the election, and asyncClose() releases it in that state too, with the same checks as when it leads: it deletes the lock if the election's session created it, conditionally on its version.
codelipenghui
left a comment
There was a problem hiding this comment.
Code Review approval for commit 8985e6e.
Summary:
The previous P1 shutdown finding is resolved in 8985e6e3be: provisional ownership now triggers ownership/version-checked cleanup on close. The new regression and retained reproducer pass with the updated source; the new regression fails against the previous implementation. No actionable findings identified within the reviewed scope.
Motivation
LeaderElectionImpl.handlePathNotification()ignores the notifications of the lock unless the election hassettled (
LeaderIsPresent). But an election cycle reads the lock, and may write it, while it'sElectionInProgress. When the store notifies the deletion of the lock before it delivers the result of a read ora write that came before the deletion, the election drops the deletion and settles on what's gone:
getLeaderValue(), and nothing makes it elect again:Creatednotifications are ignored,elect()returns thecurrent state once it isn't
NoLeader, and only aReconnectedorSessionReestablishedevent revalidates.If no other participant takes the lock, nobody leads.
lock and lead at the same time.
These elections pick the load manager leader and the extensible load manager's channel owner
(
LeaderElectionService), the scalable topic controllers, the V5 transaction coordinators, and BookKeeper'sauditor with Pulsar's metadata drivers (
PulsarLedgerAuditorManager).Which stores are exposed:
ZKMetadataStorecompletes the read or the write, and dispatches the watch event, on its singleevent thread, in the order ZooKeeper delivered them. The election settles before it handles the deletion, and
then re-elects.
OxiaMetadataStorecompletes reads and writes on the Oxia client's threads, whileAbstractMetadataStore.receivedNotification()dispatches the notifications, which come on a separate stream, onthe event thread.
OxiaMetadataStoreemits no session events either ([fix][metadata] Deliver the session events on the oxia metadata store #26712 adds them), so nothing recovers anelection that missed a deletion.
For example, on Oxia, broker B starts its election while broker A, the leader, shuts down in a rolling restart:
B's
elect("B")setsElectionInProgressand reads the lock, which still holds"A".A's
close()deletes the lock.B's store dispatches the
Deletednotification, and B's election drops it:The read completes on an Oxia client thread with
"A", and B settles as A's follower inhandleExistingLeaderValue():B now reports A as the leader, although the lock is gone. If B is the only other candidate, nobody leads until A
comes back or B's session is re-established. If a third broker takes the lock, B still reports A.
The new tests force that order by holding the result of the read, or of the write, until the deletion was
dispatched. On master:
Modifications
The election counts the deletions of the lock it's notified of, whatever its state:
A cycle takes the count before it reads the lock and before it writes it. If the count changed by the time the
read or the write completes, the cycle reads the lock again instead of settling:
In the example, B reads the lock again, finds it empty and takes it, or follows whoever took it first. B publishes
nothing before that, so the state listener and
getLeaderValue()never see the gone leader, and the future thatelect()returned completes with the final state. A deletion notified after the election settled still takes theexisting path, which re-elects.
Each cycle compares against its own count, so an overlapping cycle (say, a session revalidation during a
re-election) doesn't hide a deletion from another one. The cost is one extra read when the notified deletion
didn't affect the result: a late notification of an older deletion, or of the election's own delete when it
replaces its lock from a previous session. The latter shows on Oxia and MockZooKeeper in the
LeaderElectionTestcases that replace a lock. It didn't show on ZooKeeper, whose server sends the watch event of a client's own delete
before the reply.
#26712 makes
OxiaMetadataStoreemit session events, so a revalidation would eventually catch a missed deletion.This change fixes the election itself and doesn't depend on it.
Verifying this change
This change added tests and can be verified as follows:
LeaderElectionImplTest.candidateDoesNotFollowALeaderGoneWhileReadingTheLock, on ZooKeeper and Oxia. Aleader holds the lock through one store. A candidate elects through another, and the test holds the result of
the candidate's read of the lock until the leader closed its election and the candidate's store dispatched the
deletion. On master the candidate follows the gone leader. With the fix it takes the leadership.
LeaderElectionImplTest.electionDoesNotLeadWithALockDeletedWhileTakingIt, on the memory store. The testholds the result of the election's write of the lock until the lock was deleted and the deletion dispatched. On
master the election leads without the lock. With the fix it takes the lock again.
a row on each store.
./gradlew quickCheckpassed, and so did all 98 tests inLeaderElectionImplTest,LeaderElectionTest,CoordinationServiceImplTest,LeaderElectionStaleLeaderCloseTest,ZKSessionTest,PulsarLedgerAuditorManagerTestandAuditorBookieTest.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes