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)
This commit is contained in:
Ben Darnell 2015-11-16 19:32:12 -05:00
parent 2fe6893d5d
commit fbeb58d265
2 changed files with 28 additions and 3 deletions

View File

@ -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
}

View File

@ -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 {