Skip to content

Commit e2cd0a7

Browse files
committed
fix flake in test for restarted followers not causing an election
While working on #666, I encountered a test flake caused by a race condition in `TestRaft_FollowerRemovalNoElection`, which is also obscuring the test intent. After we restart the follower, typically the `c.ConnectFully` call will complete quickly enough that the follower never enters a candidate state, so we're not exercising the intended behavior. But if the `c.ConnectFully` call takes too long, the follower can miss heartbeats and start an election. This is expected behavior and its request for an election will be rejected. But in that case, the follower will not have settled on the leader by the time we check it and the test will fail when it shouldn't. Add an extra sleep before we reconnect the cluster to force the restarted follower to become a candidate so that we're exercising the intent of the test, but then wait until the follower shows an event that it's got a leader before continuing to the remaining assertions.
1 parent 0f35be1 commit e2cd0a7

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

raft_test.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3106,14 +3106,37 @@ func TestRaft_FollowerRemovalNoElection(t *testing.T) {
31063106
if err != nil {
31073107
t.Fatalf("error restarting follower: %v", err)
31083108
}
3109+
n.RegisterObserver(NewObserver(c.observationCh, false, nil))
3110+
31093111
c.rafts[i] = n
31103112
c.trans[i] = n.trans.(*InmemTransport)
31113113
c.fsms[i] = n.fsm.(*MockFSM)
3114+
time.Sleep(500 * time.Millisecond)
31123115
c.FullyConnect()
3113-
// There should be no re-election during this sleep
3114-
time.Sleep(250 * time.Millisecond)
31153116

3116-
// Let things settle and make sure we recovered.
3117+
// Wait for the restarted follower to settle.
3118+
ch := c.WaitEventChan(t.Context(), func(o *Observation) bool {
3119+
if o == nil {
3120+
return false
3121+
}
3122+
if o.Raft.localID == follower.localID {
3123+
if obs, ok := o.Data.(LeaderObservation); ok {
3124+
if obs.Leader != "" {
3125+
t.Logf("[INFO] restarted follower has rejoined cluster")
3126+
return true
3127+
}
3128+
}
3129+
3130+
}
3131+
return false
3132+
})
3133+
select {
3134+
case <-time.After(time.Second):
3135+
t.Fatal("restarted follower never got leader")
3136+
case <-ch:
3137+
}
3138+
3139+
// There should have been no re-election
31173140
c.EnsureLeader(t, leader.localAddr)
31183141
c.EnsureSame(t)
31193142
c.EnsureSamePeers(t)

testing.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ func (c *cluster) IndexOf(r *Raft) int {
587587
// EnsureLeader checks that ALL the nodes think the leader is the given expected
588588
// leader.
589589
func (c *cluster) EnsureLeader(t *testing.T, expect ServerAddress) {
590+
t.Helper()
590591
// We assume c.Leader() has been called already; now check all the rafts
591592
// think the leader is correct
592593
fail := false
@@ -612,6 +613,7 @@ func (c *cluster) EnsureLeader(t *testing.T, expect ServerAddress) {
612613

613614
// EnsureSame makes sure all the FSMs have the same contents.
614615
func (c *cluster) EnsureSame(t *testing.T) {
616+
t.Helper()
615617
limit := time.Now().Add(c.longstopTimeout)
616618
first := getMockFSM(c.fsms[0])
617619

@@ -690,6 +692,7 @@ func (c *cluster) getConfiguration(r *Raft) Configuration {
690692

691693
// EnsureSamePeers makes sure all the rafts have the same set of peers.
692694
func (c *cluster) EnsureSamePeers(t *testing.T) {
695+
t.Helper()
693696
limit := time.Now().Add(c.longstopTimeout)
694697
peerSet := c.getConfiguration(c.rafts[0])
695698

0 commit comments

Comments
 (0)