Siyao Meng created HDDS-17575:
---------------------------------
Summary: SCM that steps down while notifyLeaderReady runs stays
leader ready as a follower and is already ready when it is elected again
Key: HDDS-17575
URL: https://issues.apache.org/jira/browse/HDDS-17575
Project: Apache Ozone
Issue Type: Bug
Reporter: Siyao Meng
Attachments:
MC-1-set-scm-leader-ready-only-in-the-same-leader-term.patch,
TestBugMC1LeaderReadyAfterStepDown.java
h3. Mechanism
Ratis calls {{SCMStateMachine.notifyLeaderReady}} on the state machine apply
thread and {{SCMStateMachine.notifyNotLeader}} from {{changeToFollower}} on
another thread, and nothing orders the two. {{notifyLeaderReady}} flushes the
transaction buffer, reloads the deleted block summary and then calls
{{SCMContext.setLeaderReady}}, which sets the flag without checking that this
SCM is still the leader, or still in the same term. If the step down
({{updateLeaderAndTerm(false, 0)}}, which clears the flag) lands before that
call, the follower ends up with {{isLeader=false}} and {{isLeaderReady=true}}.
{{updateLeaderAndTerm(true, term)}} does not clear the flag, so it stays set
until the SCM next steps down from a leadership, including through the next
election of this SCM.
The {{SCMContext.isLeaderReady}} Javadoc says "True - it is leader and ready".
h3. Trigger
A step down of a newly elected SCM (higher term election, {{ozone admin scm
transfer}}, Ratis admin step down) that lands between Ratis declaring the
leader ready and {{setLeaderReady}}. On master since HDDS-16143 (fix version
2.3.0, not released yet), which moved the transaction buffer flush and the
deleted block summary reload into {{notifyLeaderReady}}, that window contains
both. In released versions it is only the few calls between Ratis' readiness
check and {{setLeaderReady}}, so the practical exposure is master. An earlier
run that stepped each new leader down as soon as it appeared, without timing
help, did not hit it in 5 tries, so in practice it needs a slow flush or
unlucky timing.
h3. Impact
* On the follower, every service gated on {{SCMContext.isLeaderReady}} alone
becomes RUNNING: {{ReplicationManager}}, {{SCMBlockDeletingService}},
{{BackgroundPipelineCreator}}, {{SecretKeyManagerService}}, the container
balancer check. Shown for the first two ({{shouldRun()}} true on the follower).
They cannot send datanode commands, because {{SCMContext.getTermOfLeader}}
throws on a follower, and Ratis writes fail there, so on the follower this is
wasted work and errors.
* {{SCMDatanodeProtocolServer.register}} uses the flag to decide whether a
follower's {{errorNodeNotPermitted}} is final (HDDS-17090). With the stale flag
a lagging follower's rejection is final again and the datanode shuts down (code
reading, not run).
* When the SCM is elected again it is leader ready from
{{updateLeaderAndTerm(true, term)}} on, before it has applied the previous
terms' entries and before that term's {{notifyLeaderReady}} reloads the deleted
block summary, which is the ordering HDDS-10690 established. The reproduction
shows the flag already true inside the new term's {{notifyLeaderReady}}; the
earlier part follows from the code. {{ReplicationManager}} and
{{SCMBlockDeletingService}} are already RUNNING, so they also skip the
{{hdds.scm.wait.time.after.safemode.exit}} wait after the election.
h3. Reproduction
[^TestBugMC1LeaderReadyAfterStepDown.java] ({{hadoop-ozone/integration-test}},
target path and Maven command in the class comment) runs three SCMs, one OM and
three datanodes on unmodified source. A test side log4j appender holds the
apply thread of the new leader at the INFO line logged during
{{DeletedBlockLogImpl.onLeaderReady}}, after the flush and before
{{setLeaderReady}}, while leadership is moved away with the Ratis admin
{{transferLeadership}} API. The hold only changes timing. It passes on
unmodified source when the bug is present (35s):
{noformat}
After step down: ... ratis{role=FOLLOWER term=4 ready=false}
scmContext{isLeader=false isLeaderReady=true} ReplicationManager.shouldRun=true
SCMBlockDeletingService.shouldRun=true
Re-elected, inside notifyLeaderReady: ... ratis{role=LEADER term=5 ready=true}
scmContext{isLeader=true isLeaderReady=true} ...
{noformat}
In the same run the new leader held at the same point in its first election
reports {{isLeaderReady=false}}, as expected. With the patch the test fails:
the follower stays {{isLeaderReady=false}} and both services report
{{shouldRun()}} false.
h3. Patch
[^MC-1-set-scm-leader-ready-only-in-the-same-leader-term.patch], against
424482eb9cfc4b835e1bf73bcdaa9e7d70df59f0. It also applies to master at
257a71ab277.
{{SCMContext.setLeaderReady}} takes the term and sets the flag under its write
lock only if the SCM is still the leader of that term, and returns whether it
did. {{notifyLeaderReady}} reads the Ratis current term when it starts (the
same source {{notifyLeaderChanged}} uses) and notifies the services only if the
flag was set. Since {{notifyNotLeader}} updates {{SCMContext}} under the same
lock, either the step down comes first and the flag is not set, or it comes
after and clears it. A step down plus a new election inside the callback fails
the term check, and the new term's own {{notifyLeaderReady}} sets the flag. The
term is read when the callback starts, because the {{StateMachine}} API does
not pass the term of the entry Ratis checked; a step down and a re-election
that both fit between Ratis' check and that read would still pass, which needs
at least one election timeout with no Ozone code running (argued, not run).
Nothing changes for an SCM that stays leader. The existing test callers are
updated for the new signature.
Covered by {{testLeaderReadyNotSetWhenLeadershipChangesDuringLeaderReady}} in
the existing {{TestSCMStateMachine}} (step down during the flush, step down and
new term during the flush, no change). Without the change it fails because the
follower is leader ready. With it {{TestSCMStateMachine}}, {{TestSCMContext}},
{{TestContainerBalancer}}, {{TestSCMNodeManager}}, {{TestSCMHAManagerImpl}} and
{{TestReplicationManager}} pass (181 tests), checkstyle is clean.
Found by TLA+ model checking and code review of the OM and SCM Ratis state
machine integration (leader readiness, snapshot install and log failure
handling) under HDDS-15926, on commit 424482eb9cfc4b835e1bf73bcdaa9e7d70df59f0.
Checked against HDDS issues and apache/ozone pull requests for duplicates
before filing. The attached patch is a proposal for review. Generated with
Specula (Claude Opus 5.5).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]