Repository navigation
Dining Philosophers Tutorial - #246
Merged
Merged
Conversation
…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
force-pushed
the
mku-dining
branch
3 times, most recently
from
October 21, 2026 18:27
43961d2 to
508323e
Compare
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
force-pushed
the
mku-dining
branch
4 times, most recently
from
October 22, 2026 00:23
350fb29 to
1868d4c
Compare
lemmy
marked this pull request as ready for review
October 22, 2026 00:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dijkstra's resource hierarchy (TLA+)
DiningPhilosophers.tlamodels 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:
N-1philosophersNtake its right fork firstIn 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:
request. See
DiningPhilosophersPCal.tlabelow.
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.tlaproves withTLAPS that the spec is free of deadlock, livelock, and starvation for every
N:DiningPhilosophers_anim.tladraws the table, forks, and philosophers for anyNand writes one SVG per state. The animation comes from Spectacle.