Skip to content

Dining Philosophers Tutorial - #246

Merged
lemmy merged 14 commits into
masterfrom
mku-dining
Oct 22, 2026
Merged

lemmy merged 14 commits into
masterfrom
mku-dining

Conversation

@lemmy

@lemmy lemmy commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Dijkstra's resource hierarchy (TLA+)

DiningPhilosophers.tla models the forks directly. Odd philosophers take their left fork first, and even philosophers take their right fork first, which breaks the circular wait. Strong fairness makes the spec free of deadlock, livelock, and starvation.

The git history shows how the spec got there. The table lists, for each commit, the properties for which TLC finds a counterexample:

Step Solution Known as Deadlock Livelock Starvation
1 Take the forks in any order Unordered fork acquisition yes yes yes
2 Take the left fork before the right fork Naive (symmetric) solution yes yes yes
3 Put the left fork back if the right fork is taken Back-off and retry no yes yes
4 Take both forks at once Dijkstra's semaphore solution (Tanenbaum's monitor) no no yes
5 Let a philosopher who ate wait for its neighbors Priority to waiting neighbors no no no
6 Let a waiter serve philosophers in order Waiter (arbitrator) with a queue no no no
7 Let a footman seat at most N-1 philosophers Footman (butler) solution no no no
8 Let philosopher N take its right fork first Resource hierarchy (asymmetric) solution no no no
9 Let even philosophers take their right fork first Odd/even (alternating) solution no no no

In steps 1 and 2, every philosopher can end up holding one fork. In step 4, two neighbors can take turns eating and starve the philosopher between them. Steps 5 and 6 fix that: in step 5 a philosopher who ate waits for its neighbors, and in step 6 a waiter serves neighbors in the order in which they asked. Both still take two forks in one step, which no lock on a single fork provides. Step 7 goes back to one fork at a time, with a footman whose guard reads the
state of every philosopher. Steps 8 and 9 replace the footman with a fork order that each philosopher follows on its own.

This repository has specs of two other solutions:

  • Chandy-Misra: philosophers hand clean forks to their neighbors on
    request. See DiningPhilosophersPCal.tla
    below.
  • Bakery: hungry philosophers take a ticket, and the lowest ticket eats
    first. The ticket order gives a fair, first-come-first-served solution.
    Lamport's Bakery algorithm and its Boulangerie variant are in
    Bakery-Boulangerie. They solve mutual exclusion,
    not dining, but the ticket idea carries over.

TLC checks the spec only for N = 5. DiningPhilosophers_proof.tla proves with
TLAPS that the spec is free of deadlock, livelock, and starvation for every N:

tlapm --strict DiningPhilosophers_proof.tla

DiningPhilosophers_anim.tla draws the table, forks, and philosophers for any N and writes one SVG per state. The animation comes from Spectacle.

Animation of DiningPhilosophers

…rsPCal

The Apalache wrapper instantiates DiningPhilosophersPCal, so give it
the same PCal suffix. Rename the module and its config, and update
manifest.json.

The non-PlusCal DiningPhilosophers gets no Apalache wrapper. Apalache
has nothing useful to check there: NoDeadlock uses ENABLED, which
Apalache does not support, and NoLivelock and NoStarvation are
liveness properties under SF fairness, which Apalache also rejects.
That leaves TypeOK, which tells us little.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
@lemmy
lemmy force-pushed the mku-dining branch 3 times, most recently from 43961d2 to 508323e Compare October 21, 2026 18:27
lemmy and others added 13 commits October 21, 2026 11:29
The footman solution belongs after taking both forks at once and before
the fork-order solutions. The commits on master already end with the
fork-order solutions, so the footman cannot go in between. Remove
DiningPhilosophers, its animation, and its proof, and add them back one
solution per commit, so that the history reads in order and the README
can link to each step.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The folder holds two independent solutions, and the TLA+ one is best
read as a series of commits. Add the README before the series, so that
each of the following commits adds its step to the table. A commit
cannot link to itself, so each commit links the step before it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The PlusCal spec fixes one solution in code, so it cannot show why the
naive solutions fail. DiningPhilosophers models the forks directly:
philosophers take their left and right forks in any order and release
both after eating. Strong fairness of each philosopher makes
NoStarvation counterexamples show actual starvation. NoDeadlock and
NoLivelock tell whether the table as a whole makes progress. The
ASSUMEs reject fork layouts in which a fork is outside 1..N, a
philosopher's two forks are the same, or a fork is no one's right fork.

TLC finds the deadlock where every philosopher holds one fork.

DiningPhilosophers_anim draws the table, plates, forks, and
philosophers for any N, colored by state, and writes one SVG per state
with SVGSerialize. The spec and animation come from Spectacle:
https://github.com/will62794/spectacle

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
TakeRight had no ordering guard, so philosophers could grab either
fork first. Require the left fork to be held before taking the right
one, which models the naive left-then-right algorithm. TLC finds the
classic deadlock where every philosopher holds only the left fork.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The naive left-then-right algorithm deadlocks once every philosopher
holds a left fork. Let a philosopher who cannot get the right fork put
the left fork back and retry. This removes the deadlock, but TLC still
finds NoStarvation violations: philosophers can livelock by taking and
returning their left forks, or starve while a neighbor keeps eating.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Philosophers who put the left fork back can livelock. Replace TakeLeft,
TakeRight, and PutBack with TakeBoth, which takes both forks in one
step and only when both are free (Dijkstra's solution, as in
Tanenbaum's monitor version). No philosopher ever holds a single fork,
so there is no deadlock or livelock. TLC still finds a NoStarvation
violation: a philosopher's two neighbors can eat in overlapping turns,
so the philosopher's forks are never both free, and not even strong
fairness forces it to eat.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Taking both forks at once lets a philosopher's neighbors starve it by
eating in overlapping turns. Make a philosopher who releases its forks
wait until both neighbors have eaten before it takes them again. The
neighbors of a hungry philosopher thus cannot eat twice while it waits.
Philosophers who wait for each other never form a cycle: of two
neighbors, the one who ate last waits for the other. TLC finds no
violation of NoDeadlock, NoLivelock, or NoStarvation for N = 2 to 7,
even under weak fairness.

The new variable waits relates only neighbors. The solution promises
that every philosopher eats eventually, but not that hungry philosophers
eat in the order in which they got hungry.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
Waiting for the neighbors makes every philosopher eat eventually, but
hungry philosophers do not eat in the order in which they got hungry.
Replace waits with a waiter who keeps a queue of hungry philosophers and
lets a philosopher take both forks once no neighbor asked before it.
Neighbors thus eat first come, first served, and philosophers who share
no fork do not wait for each other. TLC finds no violation of
NoDeadlock, NoLivelock, or NoStarvation for N = 2 to 7, even under weak
fairness.

The queue holds every hungry philosopher, so the waiter is global state.

DiningPhilosophers_stats.tla measures with TLC how many philosophers
eat at once, on average. The README now shows the average per step.

TLC's -generate does not choose uniformly among the philosophers who
can act: tlaplus/tlaplus#1485. Keeping Next
one action reduces the bias, so TLC's averages come close to the exact
ones computed by PRISM.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The waiter keeps a queue of every hungry philosopher, and TakeBoth takes
two forks in one step, which no lock on a single fork provides. Go
back to TakeLeft and TakeRight, with every philosopher taking the left
fork first, and add a footman who seats at most N-1 philosophers, as in
Hoare's CSP book. Not every philosopher can hold one fork, so the
circular wait cannot close. TLC finds no violation of NoDeadlock,
NoLivelock, or NoStarvation for N = 2 to 7.

The footman needs strong fairness: under weak fairness, TLC finds a
NoStarvation violation in which a philosopher never gets a seat. Strong
fairness plays the part of a fair semaphore. The footman's guard reads
the state of every philosopher, which makes the footman global state.

The animation shows the number of seated philosophers and fades a
philosopher who waits for a seat.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
The footman reads the state of every philosopher. Replace it with a
rule that each philosopher checks alone, the order in which it takes
its forks: all but philosopher N take the left fork
first, and N takes the right fork first. Each philosopher thus takes
the lower-numbered of its two forks first (Dijkstra's resource
hierarchy), which breaks the circular wait. TLC finds no violation of
NoDeadlock, NoLivelock, or NoStarvation: under strong fairness, a
philosopher who holds its first fork eventually gets the second,
because that fork becomes free again and again.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
TLC checks NoStarvation only for N = 5. The proof shows Spec =>
NoStarvation for every N. Forks are freed infinitely often, by
induction from the highest fork down: a hungry philosopher holds its
low fork and waits only for a higher one. Strong fairness then lets
each philosopher take both forks.

Check with: tlapm --strict DiningPhilosophers_proof.tla

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
If only philosopher N takes its right fork first, philosophers 1 to N-1
can wait for each other in one long chain. Alternating the fork order
between odd and even philosophers keeps these chains short, so more
philosophers can eat at once. TLC finds no violation for N = 2 to 7.
The animation shows pairs of philosophers eating at once.

The proof now ranks the forks in place of their numbers: odd forks but
fork 1, then fork 1, then the even forks.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
A commit cannot link to itself, so the odd/even step is the only one
in the table without a link.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Markus Alexander Kuppe <github.com@lemmster.de>
@lemmy
lemmy force-pushed the mku-dining branch 4 times, most recently from 350fb29 to 1868d4c Compare October 22, 2026 00:23
@lemmy
lemmy marked this pull request as ready for review October 22, 2026 00:32
@lemmy
lemmy merged commit 2f20a5b into master Oct 22, 2026
7 checks passed
@lemmy
lemmy deleted the mku-dining branch October 22, 2026 01:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant