Browse Source

raft: avoid unexpected self-bootstrap state machine

Yicheng Qin 11 years ago
parent
commit
8eac28350d
3 changed files with 26 additions and 9 deletions
  1. 4 0
      raft/node.go
  2. 15 9
      raft/node_test.go
  3. 7 0
      raft/raft.go

+ 4 - 0
raft/node.go

@@ -107,6 +107,10 @@ func (n *Node) Next() []Entry {
 // If the current elapsed is greater or equal than the timeout,
 // If the current elapsed is greater or equal than the timeout,
 // node will send corresponding message to the statemachine.
 // node will send corresponding message to the statemachine.
 func (n *Node) Tick() {
 func (n *Node) Tick() {
+	if !n.sm.promotable() {
+		return
+	}
+
 	timeout, msgType := n.election, msgHup
 	timeout, msgType := n.election, msgHup
 	if n.sm.state == stateLeader {
 	if n.sm.state == stateLeader {
 		timeout, msgType = n.heartbeat, msgBeat
 		timeout, msgType = n.heartbeat, msgBeat

+ 15 - 9
raft/node_test.go

@@ -12,6 +12,8 @@ const (
 func TestTickMsgHup(t *testing.T) {
 func TestTickMsgHup(t *testing.T) {
 	n := New(0, defaultHeartbeat, defaultElection)
 	n := New(0, defaultHeartbeat, defaultElection)
 	n.sm = newStateMachine(0, []int{0, 1, 2})
 	n.sm = newStateMachine(0, []int{0, 1, 2})
+	// simulate to patch the join log
+	n.Step(Message{Type: msgApp, Commit: 1, Entries: []Entry{Entry{}}})
 
 
 	for i := 0; i < defaultElection+1; i++ {
 	for i := 0; i < defaultElection+1; i++ {
 		n.Tick()
 		n.Tick()
@@ -31,14 +33,18 @@ func TestTickMsgHup(t *testing.T) {
 
 
 func TestTickMsgBeat(t *testing.T) {
 func TestTickMsgBeat(t *testing.T) {
 	k := 3
 	k := 3
-	n := New(0, defaultHeartbeat, defaultElection)
-	n.sm = newStateMachine(0, []int{0, 1, 2})
-
-	n.Step(Message{Type: msgHup}) // become leader please
-	for _, m := range n.Msgs() {
-		if m.Type == msgVote {
-			n.Step(Message{From: 1, Type: msgVoteResp, Index: 1, Term: 1})
+	n := dictate(New(0, defaultHeartbeat, defaultElection))
+	n.Next()
+	for i := 1; i < k; i++ {
+		n.Add(i, "")
+		for _, m := range n.Msgs() {
+			if m.Type == msgApp {
+				n.Step(Message{From: m.To, Type: msgAppResp, Index: i + 1})
+			}
 		}
 		}
+		// ignore commit index update messages
+		n.Msgs()
+		n.Next()
 	}
 	}
 
 
 	for i := 0; i < defaultHeartbeat+1; i++ {
 	for i := 0; i < defaultHeartbeat+1; i++ {
@@ -52,9 +58,8 @@ func TestTickMsgBeat(t *testing.T) {
 		}
 		}
 	}
 	}
 
 
-	// becomeLeader -> k-1 append
 	// msgBeat -> k-1 append
 	// msgBeat -> k-1 append
-	w := (k - 1) * 2
+	w := k - 1
 	if called != w {
 	if called != w {
 		t.Errorf("called = %v, want %v", called, w)
 		t.Errorf("called = %v, want %v", called, w)
 	}
 	}
@@ -75,6 +80,7 @@ func TestResetElapse(t *testing.T) {
 		n := New(0, defaultHeartbeat, defaultElection)
 		n := New(0, defaultHeartbeat, defaultElection)
 		n.sm = newStateMachine(0, []int{0, 1, 2})
 		n.sm = newStateMachine(0, []int{0, 1, 2})
 		n.sm.term = 2
 		n.sm.term = 2
+		n.sm.log.committed = 1
 
 
 		n.Tick()
 		n.Tick()
 		if n.elapsed != 1 {
 		if n.elapsed != 1 {

+ 7 - 0
raft/raft.go

@@ -198,6 +198,13 @@ func (sm *stateMachine) q() int {
 	return len(sm.ins)/2 + 1
 	return len(sm.ins)/2 + 1
 }
 }
 
 
+// promotable indicates whether state machine could be promoted.
+// New machine has to wait for the first log entry to be committed, or it will
+// always start as a one-node cluster.
+func (sm *stateMachine) promotable() bool {
+	return sm.log.committed != 0
+}
+
 func (sm *stateMachine) becomeFollower(term, lead int) {
 func (sm *stateMachine) becomeFollower(term, lead int) {
 	sm.reset(term)
 	sm.reset(term)
 	sm.lead = lead
 	sm.lead = lead