paxos

Single-decree Paxos (the Synod protocol)

What to watch

Two racing proposers; the higher ballot adopts any accepted value; the invariant "at most one chosen value"

Seed: 105. 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/paxos
use std/lesson
use std/net
use std/sim

define main
  sim.Seed 105
  net.Latency between 10ms and 40ms
  set nodes to (sim.Cluster of 3 running paxos)
  paxos.Meet nodes
  deliver (message paxos.Start with Value "x") to (item 0 of nodes)
  after 15ms
    deliver (message paxos.Start with Value "y") to (item 2 of nodes)
    lesson.Ask "Two proposers are racing. Can both x and y be chosen?" choosing {"Yes: each proposer's value is chosen at its own ballot", "No: the higher ballot wins, so y alone is chosen", "No: the second proposer learns of x and proposes x too", "Neither: the proposers block each other forever"}
  sim.StopAt 2s
# package paxos
use std/lists
use std/math
use std/net
use std/sim
use std/text

Start has
  set Value to ""

Learned has
  set Value to ""

Prepare has
  set number to 0

Promise has
  set number to 0
  set prior to 0
  set priorValue to ""

Accept has
  set number to 0
  set value to ""

Accepted has
  set number to 0
  set value to ""

Node has
  set Peers to {}
  set Promised to 0
  set AcceptedBallot to 0
  set AcceptedValue to ""
  set Chosen to ""
  set round to 0
  set ballot to 0
  set proposal to ""
  set promises to {}
  set votes to {}

define Meet [ns: list of Node]
  each n in ns
    set n.Peers to ns

define Majority of [n: number]
  result ((math.Floor (n divided by 2)) plus 1)

# Proposer, phase 1: ask every acceptor to promise a fresh, unique ballot
define [n: Node] Propose [v: text]
  set n.round to (n.round plus 1)
  set n.ballot to ((n.round times 100) plus (sim.Id n))
  set n.proposal to v
  set n.promises to {}
  each p in n.Peers
    net.Send (message Prepare with number n.ballot) from n to p

when [n: Node] (sim.Delivered Start)
  n.Propose Start.Value

# Acceptor: promise to ignore every lower ballot
when [n: Node] (net.Received Prepare from proposer)
  if (Prepare.number more than n.Promised)
    set n.Promised to Prepare.number
    net.Send (message Promise with number n.Promised, prior n.AcceptedBallot, priorValue n.AcceptedValue) from n to proposer

# Proposer, phase 2: with a majority of promises, adopt the highest prior value
when [n: Node] (net.Received Promise from acceptor)
  if (Promise.number is n.ballot)
    set n.promises to (lists.Append Promise to n.promises)
    if ((lists.Size n.promises) is (Majority of (lists.Size n.Peers)))
      set best to 0
      each p in n.promises
        if (p.prior more than best)
          set best to p.prior
          set n.proposal to p.priorValue
      each a in n.Peers
        net.Send (message Accept with number n.ballot, value n.proposal) from n to a

# Acceptor: accept unless it promised a higher ballot
when [n: Node] (net.Received Accept from proposer)
  if (Accept.number at least n.Promised)
    set n.Promised to Accept.number
    set n.AcceptedBallot to Accept.number
    set n.AcceptedValue to Accept.value
    each p in n.Peers
      net.Send (message Accepted with number Accept.number, value Accept.value) from n to p

# Learner: a value is chosen once a majority accepted the same ballot
when [n: Node] (net.Received Accepted from acceptor)
  set key to (text.From Accepted.number)
  if (key is in n.votes)
    set item key of n.votes to ((item key of n.votes) plus 1)
  else
    set item key of n.votes to 1
  if ((item key of n.votes) is (Majority of (lists.Size n.Peers)))
    set n.Chosen to Accepted.value
    emit (message Learned with Value n.Chosen) by n

define ChosenValues
  set values to {}
  each n in (sim.NodesRunning paxos)
    set c to n.Chosen
    if ((c is not "") and (c is not in values))
      set values to (lists.Append c to values)
  result values

# Safety: at most one value is ever chosen
always ((lists.Size ChosenValues) at most 1)

On your own machine: synod run ./paxos. Source on GitHub · all examples