Skip to content

Commit a14c364

Browse files
NRG (2.11): Ensure proposal and AE response queues drain after stepdown (#5666)
This ensures that when a Raft node steps down, any items that remain in the proposal or append entry response queues are correctly dropped. Otherwise the group might end up in an inconsistent state if that node becomes leader again. Signed-off-by: Neil Twigg <[email protected]>
2 parents 48cce69 + 98c2891 commit a14c364

File tree

2 files changed

+34
-6
lines changed

2 files changed

+34
-6
lines changed

server/raft.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,9 +1989,11 @@ func (n *raft) runAsFollower() {
19891989
n.debug("Ignoring old vote response, we have stepped down")
19901990
n.votes.popOne()
19911991
case <-n.resp.ch:
1992-
// We're receiving append entry responses from the network, probably because
1993-
// we have only just stepped down and they were already in flight. Ignore them.
1994-
n.resp.popOne()
1992+
// Ignore append entry responses received from before the state change.
1993+
n.resp.drain()
1994+
case <-n.prop.ch:
1995+
// Ignore proposals received from before the state change.
1996+
n.prop.drain()
19951997
case <-n.reqs.ch:
19961998
// We've just received a vote request from the network.
19971999
// Because of drain() it is possible that we get nil from popOne().
@@ -2966,8 +2968,11 @@ func (n *raft) runAsCandidate() {
29662968
case <-n.entry.ch:
29672969
n.processAppendEntries()
29682970
case <-n.resp.ch:
2969-
// Ignore
2970-
n.resp.popOne()
2971+
// Ignore append entry responses received from before the state change.
2972+
n.resp.drain()
2973+
case <-n.prop.ch:
2974+
// Ignore proposals received from before the state change.
2975+
n.prop.drain()
29712976
case <-n.s.quitCh:
29722977
n.shutdown(false)
29732978
return
@@ -4093,8 +4098,9 @@ func (n *raft) switchState(state RaftState) {
40934098

40944099
if pstate == Leader && state != Leader {
40954100
n.updateLeadChange(false)
4096-
// Drain the response queue.
4101+
// Drain the append entry response and proposal queues.
40974102
n.resp.drain()
4103+
n.prop.drain()
40984104
} else if state == Leader && pstate != Leader {
40994105
if len(n.pae) > 0 {
41004106
n.pae = make(map[uint64]*appendEntry)

server/raft_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,28 @@ func TestNRGSimpleElection(t *testing.T) {
342342
}
343343
}
344344

345+
func TestNRGSwitchStateClearsQueues(t *testing.T) {
346+
c := createJetStreamClusterExplicit(t, "R3S", 3)
347+
defer c.shutdown()
348+
349+
rg := c.createMemRaftGroup("TEST", 3, newStateAdder)
350+
rg.waitOnLeader()
351+
352+
sa := rg.leader().(*stateAdder)
353+
n := sa.node().(*raft)
354+
355+
for i := 0; i < 10_000; i++ {
356+
sa.proposeDelta(1)
357+
}
358+
359+
n.Lock()
360+
defer n.Unlock()
361+
362+
n.switchState(Follower)
363+
require_Equal(t, n.prop.len(), 0)
364+
require_Equal(t, n.resp.len(), 0)
365+
}
366+
345367
func TestNRGStepDownOnSameTermDoesntClearVote(t *testing.T) {
346368
c := createJetStreamClusterExplicit(t, "R3S", 3)
347369
defer c.shutdown()

0 commit comments

Comments
 (0)