twopc

Two-phase commit

What to watch

The blocking problem when the coordinator crashes between phases; the atomicity invariant

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

define main
  sim.Seed 1
  net.Latency between 10ms and 30ms
  set coordinator to (sim.Spawn twopc)
  set participants to (sim.Cluster of 3 running twopc)
  twopc.Coordinate participants from coordinator
  deliver (message twopc.Begin with Tx 1) to coordinator
  after 45ms
    sim.Crash coordinator
    lesson.Ask "The coordinator crashed while votes were in flight. What can a prepared participant do?" choosing {"Commit, because it voted yes", "Abort on its own, because the coordinator is gone", "Wait for the coordinator: it voted yes, so it cannot decide alone", "Elect a new coordinator among the participants"}
  sim.StopAt 3s
# package twopc
use std/lists
use std/net
use std/random
use std/sim

Begin has
  set Tx to 0

Prepare has
  set tx to 0

Vote has
  set tx to 0
  set yes to false

Decision has
  set tx to 0
  set commit to false

Node has
  set State to "idle"
  set Participants to {}
  set tx to 0
  set yesVotes to 0

define Coordinate [ps: list of Node] from [c: Node]
  set c.Participants to ps

define [n: Node] Decide [commit: bool]
  if commit
    set n.State to "committed"
  else
    set n.State to "aborted"
  each p in n.Participants
    net.Send (message Decision with tx n.tx, commit commit) from n to p

# Coordinator, phase 1: ask every participant to prepare
when [n: Node] (sim.Delivered Begin)
  set n.tx to Begin.Tx
  set n.State to "waiting"
  set n.yesVotes to 0
  each p in n.Participants
    net.Send (message Prepare with tx n.tx) from n to p
  after 500ms
    if (n.State is "waiting")
      n.Decide false

# Participant: vote, and once prepared, wait for the decision
when [n: Node] (net.Received Prepare from coordinator)
  set yes to (random.Chance 0.9)
  if yes
    set n.State to "prepared"
  else
    set n.State to "aborted"
  net.Send (message Vote with tx Prepare.tx, yes yes) from n to coordinator

# Coordinator, phase 2: commit only if everyone voted yes
when [n: Node] (net.Received Vote from participant)
  if (n.State is "waiting")
    if Vote.yes
      set n.yesVotes to (n.yesVotes plus 1)
      if (n.yesVotes is (lists.Size n.Participants))
        n.Decide true
    else
      n.Decide false

when [n: Node] (net.Received Decision from coordinator)
  if Decision.commit
    set n.State to "committed"
  else
    set n.State to "aborted"

define Atomic
  set committed to false
  set aborted to false
  each n in (sim.NodesRunning twopc)
    if (n.State is "committed")
      set committed to true
    if (n.State is "aborted")
      set aborted to true
  result (not (committed and aborted))

# Atomicity: never some nodes committed while others aborted
always Atomic

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