raft
Raft leader election
What to watch
Randomized timeouts, votes, a leader crash, a new term; "never two leaders in a term"
Seed: 3. The program asks a question at the interesting moment; with Predict on, the run stops there so you can answer before you see what happens.
Run it
Edit any file and press Run. The tabs hold the program and the packages it uses.
# package main
use github.com/synodlang/examples/raft
use github.com/synodlang/examples/raftlook
use std/lesson
use std/net
use std/sim
define main
sim.Seed 3
net.Latency between 5ms and 15ms
set nodes to (sim.Cluster of 5 running raft)
raft.Meet nodes
after 2s
set old to (item 0 of raft.Leaders)
sim.Crash old
lesson.Ask "The leader just crashed. What do the followers do now?" choosing {"Wait for the old leader to return", "Time out and start an election", "Ask a coordinator service to pick a new leader", "Stop accepting writes for good"}
after 3s
sim.Restart old
lesson.Note "The old leader returns as a follower: it sees a higher term."
sim.StopAt 8s
# package raft
use std/lists
use std/math
use std/net
use std/random
use std/sim
use std/time
Elected has
set Term to 0
RequestVote has
set term to 0
Vote has
set term to 0
set granted to false
Heartbeat has
set term to 0
Node has
set Role to "follower"
set Term to 0
set VotedFor to sim.Nobody
set Peers to {}
set votes to 0
set lastHeard to 0s
define Meet [ns: list of Node]
each n in ns
set n.Peers to (lists.Without n from ns)
define Majority of [n: number]
result ((math.Floor (n divided by 2)) plus 1)
define Leaders
set found to {}
each n in (sim.NodesRunning raft)
if ((n.Role is "leader") and (sim.IsUp n))
set found to (lists.Append n to found)
result found
define LeadersAgree
set terms to {}
each n in (sim.NodesRunning raft)
if (n.Role is "leader")
if (n.Term is in terms)
result false
set terms to (lists.Append n.Term to terms)
result true
# Election safety: never two leaders in the same term
always LeadersAgree
define [n: Node] BecomeFollower [t: number]
set n.Term to t
set n.Role to "follower"
set n.VotedFor to sim.Nobody
define [n: Node] StartElection
set n.Role to "candidate"
set n.Term to (n.Term plus 1)
set n.VotedFor to n
set n.votes to 1
set n.lastHeard to time.Now
each p in n.Peers
net.Send (message RequestVote with term n.Term) from n to p
# A randomized timeout keeps candidates from colliding forever
define [n: Node] WatchElection
after (random.TimeBetween 150ms and 300ms)
if ((n.Role is not "leader") and ((time.Now minus n.lastHeard) at least 150ms))
n.StartElection
n.WatchElection
define [n: Node] start
n.WatchElection
time.Every 50ms
if (n.Role is "leader")
each p in n.Peers
net.Send (message Heartbeat with term n.Term) from n to p
when [n: Node] (net.Received RequestVote from candidate)
if (RequestVote.term more than n.Term)
n.BecomeFollower RequestVote.term
set grant to ((RequestVote.term is n.Term) and ((n.VotedFor is sim.Nobody) or (n.VotedFor is candidate)))
if grant
set n.VotedFor to candidate
set n.lastHeard to time.Now
net.Send (message Vote with term n.Term, granted grant) from n to candidate
when [n: Node] (net.Received Vote from voter)
if (Vote.term more than n.Term)
n.BecomeFollower Vote.term
else
if (((n.Role is "candidate") and Vote.granted) and (Vote.term is n.Term))
set n.votes to (n.votes plus 1)
if (n.votes is (Majority of ((lists.Size n.Peers) plus 1)))
set n.Role to "leader"
emit (message Elected with Term n.Term) by n
when [n: Node] (net.Received Heartbeat from leader)
if (Heartbeat.term at least n.Term)
if (Heartbeat.term more than n.Term)
n.BecomeFollower Heartbeat.term
set n.Role to "follower"
set n.lastHeard to time.Now
# package raftlook
use github.com/synodlang/examples/raft
use std/look
use std/look/basic
use std/sim
use std/theme
define RoleColor [r: text]
if (r is "leader")
result theme.Accent
if (r is "candidate")
result theme.Warning
result theme.Ink
# Replace the basic card with one whose fill follows the node's role
when (sim.Added [n: raft.Node])
draw (look.Card for n) as card for n
look.Fill card with (RoleColor n.raft.Role)
when (sim.Emitted raft.Elected by n)
look.Pulse (card of n)
look.Effect "glow" on (card of n)
On your own machine: synod run ./raft. Source on GitHub · all examples