Skip to content

Commit dcbe0c8

Browse files
NRG: Fix term handling in candidate state and use higher term from vote request
A candidate could incorrectly revert to an older term without resetting if an old AE arrived with a term that is at least newer than the pterm but not necessarily newer than the term. Also a node that is isolated for a period of time with a high term number should cause the rest of the cluster to move forward with that term number, rather than going backwards to the leader term. Co-authored-by: Reuben Ninan <[email protected]> Signed-off-by: Neil Twigg <[email protected]>
1 parent 6c33673 commit dcbe0c8

File tree

2 files changed

+70
-25
lines changed

2 files changed

+70
-25
lines changed

server/raft.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3201,11 +3201,11 @@ func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) {
32013201
// If the append entry term is newer than the current term, erase our
32023202
// vote.
32033203
if ae.term > n.term {
3204+
n.term = ae.term
32043205
n.vote = noVote
3206+
n.writeTermVote()
32053207
}
32063208
n.debug("Received append entry in candidate state from %q, converting to follower", ae.leader)
3207-
n.term = ae.term
3208-
n.writeTermVote()
32093209
n.stepdown.push(ae.leader)
32103210
}
32113211
}
@@ -3974,8 +3974,8 @@ func (n *raft) processVoteRequest(vr *voteRequest) error {
39743974
n.debug("Stepping down from %s, detected higher term: %d vs %d",
39753975
strings.ToLower(n.State().String()), vr.term, n.term)
39763976
n.stepdown.push(noLeader)
3977-
n.term = vr.term
39783977
}
3978+
n.term = vr.term
39793979
n.vote = noVote
39803980
n.writeTermVote()
39813981
}

server/raft_test.go

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ func TestNRGInvalidTAVDoesntPanic(t *testing.T) {
430430
c.waitOnAllCurrent()
431431
}
432432

433-
func TestNRGCandidateStepsDownAfterAE(t *testing.T) {
433+
func TestNRGAssumeHighTermAfterCandidateIsolation(t *testing.T) {
434434
c := createJetStreamClusterExplicit(t, "R3S", 3)
435435
defer c.shutdown()
436436
c.waitOnLeader()
@@ -441,32 +441,38 @@ func TestNRGCandidateStepsDownAfterAE(t *testing.T) {
441441
rg := c.createRaftGroup("TEST", 3, newStateAdder)
442442
rg.waitOnLeader()
443443

444-
// Pick a random follower node. Bump the term up by a considerable
444+
// Bump the term up on one of the follower nodes by a considerable
445445
// amount and force it into the candidate state. This is what happens
446446
// after a period of time in isolation.
447-
n := rg.nonLeader().node().(*raft)
448-
n.Lock()
449-
n.term += 100
450-
n.switchState(Candidate)
451-
n.Unlock()
447+
follower := rg.nonLeader().node().(*raft)
448+
follower.Lock()
449+
follower.term += 100
450+
follower.switchState(Candidate)
451+
follower.Unlock()
452+
453+
follower.requestVote()
454+
time.Sleep(time.Millisecond * 100)
455+
456+
// The candidate will shortly send a vote request. When that happens,
457+
// the rest of the nodes in the cluster should move up to that term,
458+
// even though they will not grant the vote.
459+
nterm := follower.term
460+
for _, n := range rg {
461+
require_Equal(t, n.node().Term(), nterm)
462+
}
452463

453-
// Have the leader push through something on the current term just
454-
// for good measure, although the heartbeats probably work too.
464+
// Have the leader send out a proposal, which will force the candidate
465+
// back into follower state.
466+
rg.waitOnLeader()
455467
rg.leader().(*stateAdder).proposeDelta(1)
468+
rg.waitOnTotal(t, 1)
456469

457-
// Wait for the leader to receive the next append entry from the
458-
// current leader. What should happen is that the node steps down
459-
// and starts following the leader, as nothing in the log of the
460-
// follower is newer than the term of the leader.
461-
checkFor(t, time.Second, 50*time.Millisecond, func() error {
462-
if n.State() == Candidate {
463-
return fmt.Errorf("shouldn't still be candidate state")
464-
}
465-
if nterm, lterm := n.Term(), rg.leader().node().Term(); nterm != lterm {
466-
return fmt.Errorf("follower term %d should match leader term %d", nterm, lterm)
467-
}
468-
return nil
469-
})
470+
// The candidate should have switched to a follower on a term equal to
471+
// or newer than the candidate had.
472+
for _, n := range rg {
473+
require_NotEqual(t, n.node().State(), Candidate)
474+
require_True(t, n.node().Term() >= nterm)
475+
}
470476
}
471477

472478
// Test to make sure this does not cause us to truncate our wal or enter catchup state.
@@ -588,3 +594,42 @@ func TestNRGLeavesObserverAfterPause(t *testing.T) {
588594
n.ResumeApply()
589595
checkState(false, false)
590596
}
597+
598+
func TestNRGCandidateDoesntRevertTermAfterOldAE(t *testing.T) {
599+
c := createJetStreamClusterExplicit(t, "R3S", 3)
600+
defer c.shutdown()
601+
602+
rg := c.createMemRaftGroup("TEST", 3, newStateAdder)
603+
rg.waitOnLeader()
604+
605+
// Bump the term up a few times.
606+
for i := 0; i < 3; i++ {
607+
rg.leader().node().StepDown()
608+
time.Sleep(time.Millisecond * 50) // Needed because stepdowns not synchronous
609+
rg.waitOnLeader()
610+
}
611+
612+
leader := rg.leader().node().(*raft)
613+
follower := rg.nonLeader().node().(*raft)
614+
615+
// Sanity check that we are where we expect to be.
616+
require_Equal(t, leader.term, 4)
617+
require_Equal(t, follower.term, 4)
618+
619+
// At this point the active term is 4 and pterm is 4, force the
620+
// term up to 9. This won't bump the pterm.
621+
rg.lockAll()
622+
for _, n := range rg {
623+
n.node().(*raft).term += 5
624+
}
625+
rg.unlockAll()
626+
627+
// Build an AE that has a term newer than the pterm but older than
628+
// the term. Give it to the follower in candidate state.
629+
ae := newAppendEntry(leader.id, 6, leader.commit, leader.pterm, leader.pindex, nil)
630+
follower.switchToCandidate()
631+
follower.processAppendEntry(ae, nil)
632+
633+
// The candidate must not have reverted back to term 6.
634+
require_NotEqual(t, follower.term, 6)
635+
}

0 commit comments

Comments
 (0)