Browse Source

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)
Ben Darnell 10 years ago
parent
commit
fbeb58d265
2 changed files with 28 additions and 3 deletions
  1. 7 3
      raft/raft.go
  2. 21 0
      raft/raft_test.go

+ 7 - 3
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 {
 func (r *raft) Step(m pb.Message) error {
 	if m.Type == pb.MsgHup {
 	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
 		return nil
 	}
 	}
 
 

+ 21 - 0
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 {
 func ents(terms ...uint64) *raft {
 	storage := NewMemoryStorage()
 	storage := NewMemoryStorage()
 	for i, term := range terms {
 	for i, term := range terms {