From fbeb58d265fd219b85a2741774e78864d8dc33c3 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Mon, 16 Nov 2015 19:32:12 -0500 Subject: [PATCH] raft: no-op instead of panic for Campaigning while leader We need to be able to force an election (on one node) after creating a new group (cockroachdb/cockroach#1384), but it is difficult to ensure that our call to Campaign does not race with an election that may be started by raft itself. A redundant call to Campaign should be a no-op instead of a panic. (But the panic in becomeCandidate remains, because we don't want to update the term or change the committed index in this case) --- raft/raft.go | 10 +++++++--- raft/raft_test.go | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/raft/raft.go b/raft/raft.go index 4baa114b1..9a4632715 100644 --- a/raft/raft.go +++ b/raft/raft.go @@ -485,9 +485,13 @@ func (r *raft) poll(id uint64, v bool) (granted int) { func (r *raft) Step(m pb.Message) error { if m.Type == pb.MsgHup { - r.logger.Infof("%x is starting a new election at term %d", r.id, r.Term) - r.campaign() - r.Commit = r.raftLog.committed + if r.state != StateLeader { + r.logger.Infof("%x is starting a new election at term %d", r.id, r.Term) + r.campaign() + r.Commit = r.raftLog.committed + } else { + r.logger.Debugf("%x ignoring MsgHup because already leader", r.id) + } return nil } diff --git a/raft/raft_test.go b/raft/raft_test.go index 9534632db..50e2fd42f 100644 --- a/raft/raft_test.go +++ b/raft/raft_test.go @@ -1751,6 +1751,27 @@ func TestRaftNodes(t *testing.T) { } } +func TestCampaignWhileLeader(t *testing.T) { + r := newTestRaft(1, []uint64{1}, 5, 1, NewMemoryStorage()) + if r.state != StateFollower { + t.Errorf("expected new node to be follower but got %s", r.state) + } + // We don't call campaign() directly because it comes after the check + // for our current state. + r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup}) + if r.state != StateLeader { + t.Errorf("expected single-node election to become leader but got %s", r.state) + } + term := r.Term + r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgHup}) + if r.state != StateLeader { + t.Errorf("expected to remain leader but got %s", r.state) + } + if r.Term != term { + t.Errorf("expected to remain in term %v but got %v", term, r.Term) + } +} + func ents(terms ...uint64) *raft { storage := NewMemoryStorage() for i, term := range terms {