Skip to content

Commit bcbe4f7

Browse files
committed
Tweak Raft state transitions
1 parent 64e3ef8 commit bcbe4f7

File tree

4 files changed

+55
-35
lines changed

4 files changed

+55
-35
lines changed

src/raft/node/candidate.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -46,35 +46,39 @@ impl RawNode<Candidate> {
4646
Ok(())
4747
}
4848

49-
/// Transforms the node into a follower. We either lost the election
50-
/// and follow the winner, or we discovered a new term in which case
51-
/// we step into it as a leaderless follower.
52-
fn become_follower(mut self, term: Term, leader: Option<NodeID>) -> Result<RawNode<Follower>> {
49+
/// Transitions the candidate to a follower. We either lost the election and
50+
/// follow the winner, or we discovered a new term in which case we step
51+
/// into it as a leaderless follower.
52+
pub(super) fn into_follower(
53+
mut self,
54+
term: Term,
55+
leader: Option<NodeID>,
56+
) -> Result<RawNode<Follower>> {
5357
assert!(term >= self.term, "Term regression {} -> {}", self.term, term);
5458

5559
if let Some(leader) = leader {
5660
// We lost the election, follow the winner.
5761
assert_eq!(term, self.term, "Can't follow leader in different term");
5862
info!("Lost election, following leader {} in term {}", leader, term);
5963
let voted_for = Some(self.id); // by definition
60-
Ok(self.become_role(Follower::new(Some(leader), voted_for)))
64+
Ok(self.into_role(Follower::new(Some(leader), voted_for)))
6165
} else {
6266
// We found a new term, but we don't necessarily know who the leader
6367
// is yet. We'll find out when we step a message from it.
6468
assert_ne!(term, self.term, "Can't become leaderless follower in current term");
6569
info!("Discovered new term {}", term);
6670
self.term = term;
6771
self.log.set_term(term, None)?;
68-
Ok(self.become_role(Follower::new(None, None)))
72+
Ok(self.into_role(Follower::new(None, None)))
6973
}
7074
}
7175

72-
/// Transition to leader role.
73-
pub(super) fn become_leader(self) -> Result<RawNode<Leader>> {
76+
/// Transitions the candidate to a leader. We won the election.
77+
pub(super) fn into_leader(self) -> Result<RawNode<Leader>> {
7478
info!("Won election for term {}, becoming leader", self.term);
7579
let peers = self.peers.clone();
7680
let (last_index, _) = self.log.get_last_index();
77-
let mut node = self.become_role(Leader::new(peers, last_index));
81+
let mut node = self.into_role(Leader::new(peers, last_index));
7882
node.heartbeat()?;
7983

8084
// Propose an empty command when assuming leadership, to disambiguate
@@ -98,7 +102,7 @@ impl RawNode<Candidate> {
98102
// follower in it and step the message. If the message is a Heartbeat or
99103
// AppendEntries from the leader, stepping it will follow the leader.
100104
if msg.term > self.term {
101-
return self.become_follower(msg.term, None)?.step(msg);
105+
return self.into_follower(msg.term, None)?.step(msg);
102106
}
103107

104108
match msg.event {
@@ -110,14 +114,14 @@ impl RawNode<Candidate> {
110114
Event::GrantVote => {
111115
self.role.votes.insert(msg.from.unwrap());
112116
if self.role.votes.len() as u64 >= self.quorum() {
113-
return Ok(self.become_leader()?.into());
117+
return Ok(self.into_leader()?.into());
114118
}
115119
}
116120

117121
// If we receive a heartbeat or entries in this term, we lost the
118122
// election and have a new leader. Follow it and step the message.
119123
Event::Heartbeat { .. } | Event::AppendEntries { .. } => {
120-
return self.become_follower(msg.term, Some(msg.from.unwrap()))?.step(msg);
124+
return self.into_follower(msg.term, Some(msg.from.unwrap()))?.step(msg);
121125
}
122126

123127
// Abort any inbound client requests while candidate.

src/raft/node/follower.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,25 @@ impl RawNode<Follower> {
7373
Ok(())
7474
}
7575

76-
/// Transforms the node into a candidate, by campaigning for leadership in a
77-
/// new term.
78-
pub(super) fn become_candidate(mut self) -> Result<RawNode<Candidate>> {
76+
/// Transitions the follower into a candidate, by campaigning for
77+
/// leadership in a new term.
78+
pub(super) fn into_candidate(mut self) -> Result<RawNode<Candidate>> {
7979
// Abort any forwarded requests. These must be retried with new leader.
8080
self.abort_forwarded()?;
8181

82-
let mut node = self.become_role(Candidate::new());
82+
let mut node = self.into_role(Candidate::new());
8383
node.campaign()?;
8484
Ok(node)
8585
}
8686

87-
/// Transforms the node into a follower, either a leaderless follower in a
88-
/// new term or following a leader in the current term.
89-
fn become_follower(mut self, leader: Option<NodeID>, term: Term) -> Result<RawNode<Follower>> {
87+
/// Transitions the candidate into a follower, either a leaderless follower
88+
/// in a new term (e.g. if someone holds a new election) or following a
89+
/// leader in the current term once someone wins the election.
90+
pub(super) fn into_follower(
91+
mut self,
92+
leader: Option<NodeID>,
93+
term: Term,
94+
) -> Result<RawNode<Follower>> {
9095
assert!(term >= self.term, "Term regression {} -> {}", self.term, term);
9196

9297
// Abort any forwarded requests. These must be retried with new leader.
@@ -125,7 +130,7 @@ impl RawNode<Follower> {
125130
// follower in it and step the message. If the message is a Heartbeat or
126131
// AppendEntries from the leader, stepping it will follow the leader.
127132
if msg.term > self.term {
128-
return self.become_follower(None, msg.term)?.step(msg);
133+
return self.into_follower(None, msg.term)?.step(msg);
129134
}
130135

131136
// Record when we last saw a message from the leader (if any).
@@ -142,7 +147,7 @@ impl RawNode<Follower> {
142147
let from = msg.from.unwrap();
143148
match self.role.leader {
144149
Some(leader) => assert_eq!(from, leader, "Multiple leaders in term"),
145-
None => self = self.become_follower(Some(from), msg.term)?,
150+
None => self = self.into_follower(Some(from), msg.term)?,
146151
}
147152

148153
// Advance commit index and apply entries if possible.
@@ -165,7 +170,7 @@ impl RawNode<Follower> {
165170
let from = msg.from.unwrap();
166171
match self.role.leader {
167172
Some(leader) => assert_eq!(from, leader, "Multiple leaders in term"),
168-
None => self = self.become_follower(Some(from), msg.term)?,
173+
None => self = self.into_follower(Some(from), msg.term)?,
169174
}
170175

171176
// Append the entries, if possible.
@@ -250,7 +255,7 @@ impl RawNode<Follower> {
250255

251256
self.role.leader_seen += 1;
252257
if self.role.leader_seen >= self.role.election_timeout {
253-
return Ok(self.become_candidate()?.into());
258+
return Ok(self.into_candidate()?.into());
254259
}
255260
Ok(self.into())
256261
}

src/raft/node/leader.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,18 @@ impl RawNode<Leader> {
4545
Ok(())
4646
}
4747

48-
/// Transforms the leader into a follower. This can only happen if we find a
49-
/// new term, so we become a leaderless follower.
50-
fn become_follower(mut self, term: Term) -> Result<RawNode<Follower>> {
48+
/// Transitions the leader into a follower. This can only happen if we
49+
/// discover a new term, so we become a leaderless follower. Subsequently
50+
/// stepping the received message may discover the leader, if there is one.
51+
pub(super) fn into_follower(mut self, term: Term) -> Result<RawNode<Follower>> {
5152
assert!(term >= self.term, "Term regression {} -> {}", self.term, term);
5253
assert!(term > self.term, "Can only become follower in later term");
5354

5455
info!("Discovered new term {}", term);
5556
self.term = term;
5657
self.log.set_term(term, None)?;
5758
self.state_tx.send(Instruction::Abort)?;
58-
Ok(self.become_role(Follower::new(None, None)))
59+
Ok(self.into_role(Follower::new(None, None)))
5960
}
6061

6162
/// Processes a message.
@@ -73,7 +74,7 @@ impl RawNode<Leader> {
7374
// follower in it and step the message. If the message is a Heartbeat or
7475
// AppendEntries from the leader, stepping it will follow the leader.
7576
if msg.term > self.term {
76-
return self.become_follower(msg.term)?.step(msg);
77+
return self.into_follower(msg.term)?.step(msg);
7778
}
7879

7980
match msg.event {

src/raft/node/mod.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ pub struct Status {
4848
pub storage_size: u64,
4949
}
5050

51-
/// The local Raft node state machine.
51+
/// A Raft node, with a dynamic role. The node is driven synchronously by
52+
/// processing inbound messages via step() or by advancing time via tick().
53+
/// These methods consume the current node, and return a new one with a possibly
54+
/// different type. Outbound messages are sent via the given node_tx channel.
55+
///
56+
/// This enum wraps the NodeState<Role> types, which implement the actual
57+
/// node logic. It exists for ergonomic use across role transitions, i.e
58+
/// node = node.step()?.
5259
pub enum Node {
5360
Candidate(RawNode<Candidate>),
5461
Follower(RawNode<Follower>),
@@ -73,7 +80,7 @@ impl Node {
7380
let node = RawNode::new(id, peers, log, node_tx, state_tx)?;
7481
if node.peers.is_empty() {
7582
// If there are no peers, become leader immediately.
76-
return Ok(node.become_candidate()?.become_leader()?.into());
83+
return Ok(node.into_candidate()?.into_leader()?.into());
7784
}
7885
Ok(node.into())
7986
}
@@ -128,7 +135,10 @@ impl From<RawNode<Leader>> for Node {
128135
/// A Raft role: leader, follower, or candidate.
129136
pub trait Role: Clone + std::fmt::Debug + PartialEq {}
130137

131-
// A Raft node with role R
138+
/// A Raft node with the concrete role R.
139+
///
140+
/// This implements the typestate pattern, where individual node states (roles)
141+
/// are encoded as RawNode<Role>. See: http://cliffle.com/blog/rust-typestate/
132142
pub struct RawNode<R: Role = Follower> {
133143
id: NodeID,
134144
peers: HashSet<NodeID>,
@@ -140,8 +150,8 @@ pub struct RawNode<R: Role = Follower> {
140150
}
141151

142152
impl<R: Role> RawNode<R> {
143-
/// Transforms the node into another role.
144-
fn become_role<T: Role>(self, role: T) -> RawNode<T> {
153+
/// Helper for role transitions.
154+
fn into_role<T: Role>(self, role: T) -> RawNode<T> {
145155
RawNode {
146156
id: self.id,
147157
peers: self.peers,
@@ -481,10 +491,10 @@ mod tests {
481491
}
482492

483493
#[test]
484-
fn become_role() -> Result<()> {
494+
fn into_role() -> Result<()> {
485495
let (node, _) = setup_rolenode()?;
486496
let role = Candidate::new();
487-
let new = node.become_role(role.clone());
497+
let new = node.into_role(role.clone());
488498
assert_eq!(new.id, 1);
489499
assert_eq!(new.term, 1);
490500
assert_eq!(new.peers, HashSet::from([2, 3]));

0 commit comments

Comments
 (0)