/
raft_election_logic.go
143 lines (122 loc) · 4.32 KB
/
raft_election_logic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package raft
import (
"math/rand"
"time"
)
/*
startElectionTimer implements an election timer. It should be launched whenever
we want to start a timer towards becoming a candidate in a new election.
This function runs as a go routine
*/
func (this *RaftNode) startElectionTimer() {
timeoutDuration := time.Duration(3000+rand.Intn(3000)) * time.Millisecond
this.mu.Lock()
termStarted := this.currentTerm
this.mu.Unlock()
this.write_log("Election timer started: %v, with term=%d", timeoutDuration, termStarted)
// Keep checking for a resolution
ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
for {
<-ticker.C
this.mu.Lock()
// if node has become a leader
if this.state != "Candidate" && this.state != "Follower" {
this.mu.Unlock()
return
}
// if node received requestVote or appendEntries of a higher term and updated itself
if termStarted != this.currentTerm {
this.mu.Unlock()
return
}
// Start an election if we haven't heard from a leader or haven't voted for someone for the duration of the timeout.
if elapsed := time.Since(this.lastElectionTimerStartedTime); elapsed >= timeoutDuration {
this.startElection()
this.mu.Unlock()
return
}
this.mu.Unlock()
}
}
// startElection starts a new election with this RN as a candidate.
func (this *RaftNode) startElection() {
this.state = "Candidate"
this.currentTerm += 1
termWhenVoteRequested := this.currentTerm
this.lastElectionTimerStartedTime = time.Now()
this.votedFor = this.id
this.write_log("became Candidate with term=%d;", termWhenVoteRequested)
votesReceived := 1
// Send RequestVote RPCs to all other servers concurrently.
for _, peerId := range this.peersIds {
go func(peerId int) {
this.mu.Lock()
var LastLogIndexWhenVoteRequested, LastLogTermWhenVoteRequested int
if len(this.log) > 0 {
lastIndex := len(this.log) - 1
LastLogIndexWhenVoteRequested, LastLogTermWhenVoteRequested = lastIndex, this.log[lastIndex].Term
} else {
LastLogIndexWhenVoteRequested, LastLogTermWhenVoteRequested = -1, -1
}
this.mu.Unlock()
args := RequestVoteArgs{
Term: termWhenVoteRequested,
CandidateId: this.id,
LastLogIndex: LastLogIndexWhenVoteRequested,
LastLogTerm: LastLogTermWhenVoteRequested,
Latency: rand.Intn(500), // Ignore Latency.
}
if LogVoteRequestMessages {
this.write_log("sending RequestVote to %d: %+v", peerId, args)
}
var reply RequestVoteReply
if err := this.server.SendRPCCallTo(peerId, "RaftNode.RequestVote", args, &reply); err == nil {
this.mu.Lock()
defer this.mu.Unlock()
if LogVoteRequestMessages {
this.write_log("received RequestVoteReply from %d: %+v", peerId, reply)
}
if this.state != "Candidate" {
this.write_log("State changed from Candidate to %s", this.state)
return
}
// IMPLEMENT HANDLING THE VOTEREQUEST's REPLY;
// You probably need to have implemented becomeFollower before this.
//-------------------------------------------------------------------------------------------/
if reply.Term > termWhenVoteRequested {
this.write_log("term out of date in RequestVoteReply")
this.becomeFollower(reply.Term)
return
} else if reply.Term == termWhenVoteRequested {
// TODO
if reply.VoteGranted {
votesReceived++
if votesReceived*2 > len(this.peersIds)+1 {
// Won the election!
this.write_log("wins election with %d votes", votesReceived)
this.startLeader()
return
}
}
}
//-------------------------------------------------------------------------------------------/
}
}(peerId)
}
// Run another election timer, in case this election is not successful.
go this.startElectionTimer()
}
// becomeFollower sets a node to be a follower and resets its state.
func (this *RaftNode) becomeFollower(term int) {
this.write_log("became Follower with term=%d; log=%v", term, this.log)
// IMPLEMENT becomeFollower; do you need to start a goroutine here, maybe?
//-------------------------------------------------------------------------------------------/
// TODO
this.state = "Follower"
this.currentTerm = term
this.votedFor = -1
this.lastElectionTimerStartedTime = time.Now()
go this.startElectionTimer()
//-------------------------------------------------------------------------------------------/
}