diff --git a/raft/raft.go b/raft/raft.go index d289de6a1be..f408a326b2d 100644 --- a/raft/raft.go +++ b/raft/raft.go @@ -611,6 +611,7 @@ func (r *raft) becomePreCandidate() { // but doesn't change anything else. In particular it does not increase // r.Term or change r.Vote. r.step = stepCandidate + r.votes = make(map[uint64]bool) r.tick = r.tickElection r.state = StatePreCandidate r.logger.Infof("%x became pre-candidate at term %d", r.id, r.Term) diff --git a/raft/raft_test.go b/raft/raft_test.go index 1fa28544471..357e9bdf684 100644 --- a/raft/raft_test.go +++ b/raft/raft_test.go @@ -3209,6 +3209,83 @@ func TestNodeWithSmallerTermCanCompleteElection(t *testing.T) { } } +// TestPreVoteWithSplitVote verifies that after split vote, cluster can complete +// election in next round. +func TestPreVoteWithSplitVote(t *testing.T) { + n1 := newTestRaft(1, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage()) + n2 := newTestRaft(2, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage()) + n3 := newTestRaft(3, []uint64{1, 2, 3}, 10, 1, NewMemoryStorage()) + + n1.becomeFollower(1, None) + n2.becomeFollower(1, None) + n3.becomeFollower(1, None) + + n1.preVote = true + n2.preVote = true + n3.preVote = true + + nt := newNetwork(n1, n2, n3) + nt.send(pb.Message{From: 1, To: 1, Type: pb.MsgHup}) + + // simulate leader down. followers start split vote. + nt.isolate(1) + nt.send([]pb.Message{ + {From: 2, To: 2, Type: pb.MsgHup}, + {From: 3, To: 3, Type: pb.MsgHup}, + }...) + + // check whether the term values are expected + // n2.Term == 3 + // n3.Term == 3 + sm := nt.peers[2].(*raft) + if sm.Term != 3 { + t.Errorf("peer 2 term: %d, want %d", sm.Term, 3) + } + sm = nt.peers[3].(*raft) + if sm.Term != 3 { + t.Errorf("peer 3 term: %d, want %d", sm.Term, 3) + } + + // check state + // n2 == candidate + // n3 == candidate + sm = nt.peers[2].(*raft) + if sm.state != StateCandidate { + t.Errorf("peer 2 state: %s, want %s", sm.state, StateCandidate) + } + sm = nt.peers[3].(*raft) + if sm.state != StateCandidate { + t.Errorf("peer 3 state: %s, want %s", sm.state, StateCandidate) + } + + // node 2 election timeout first + nt.send(pb.Message{From: 2, To: 2, Type: pb.MsgHup}) + + // check whether the term values are expected + // n2.Term == 4 + // n3.Term == 4 + sm = nt.peers[2].(*raft) + if sm.Term != 4 { + t.Errorf("peer 2 term: %d, want %d", sm.Term, 4) + } + sm = nt.peers[3].(*raft) + if sm.Term != 4 { + t.Errorf("peer 3 term: %d, want %d", sm.Term, 4) + } + + // check state + // n2 == leader + // n3 == follower + sm = nt.peers[2].(*raft) + if sm.state != StateLeader { + t.Errorf("peer 2 state: %s, want %s", sm.state, StateLeader) + } + sm = nt.peers[3].(*raft) + if sm.state != StateFollower { + t.Errorf("peer 3 state: %s, want %s", sm.state, StateFollower) + } +} + func entsWithConfig(configFunc func(*Config), terms ...uint64) *raft { storage := NewMemoryStorage() for i, term := range terms {