hraness
Theme
Appearance

tla+: checking every interleaving a test can't reach

bugs caused by the order of events

by hraness · drafted with ai assistance

the rest of this lesson is free: add your email to keep reading.

Many concurrency bugs live in the order of events rather than in any one function: replica B answers before replica A finishes its write, a lock is released between the check and the use, or a crash lands between two writes that were meant to be atomic. A model checker for TLA+ finds these by trying every ordering of a small model of the protocol, which no test suite can do.

For the language itself, see Hillel Wayne’s Learn TLA+ and Leslie Lamport’s TLA+ page. This lesson covers where vhalla and Gobstopper use it and what a checked spec cannot tell you.

why tests miss ordering bugs

A concurrent system’s behavior depends on its code and on every allowed ordering of its steps. Tests exercise a few orderings, and production eventually exercises the bad ones. The number of orderings grows combinatorially: three actors with four steps each already have more interleavings than a test suite will ever run. Unit tests have no way to call “the scheduler,” so the ordering itself goes untested.

TLA+ and similar tools check a model of the system against all interleavings within a state limit you set. You write the protocol as a state machine: the state variables, the atomic steps, and the invariants that must hold in every reachable state. The model checker enumerates states until it has seen them all or found one that breaks an invariant.

what a spec models

A TLA+ spec is a protocol skeleton: which messages exist, which states a node can be in, and what a step may change. It is only as faithful as the writer’s choice of which details matter, and it is exhaustive over whatever it does model.

When the checker finds a violating state, it reports the step sequence that reached it: send, crash, retry, ack, in that order. A trace like that names the bug precisely enough to fix, and it becomes a regression test once you reproduce the interleaving in the real test harness and keep it in the suite.

where vhalla and Gobstopper use specs

vhalla keeps specifications for delivery and recovery, where ordering decides whether the product works. The private-agent delivery path has a relay, member acceptances, a durable journal, and a controller that can pause. The spec checks the claim “retention is not member acceptance” against every schedule the model allows. Mutant configurations run the same spec against deliberately wrong variants of the protocol to confirm it catches them; a spec that never fails a mutant has shown nothing.

Gobstopper keeps vault specifications for the same reason. The vault promises that a transcript copy preserves structure, that a snapshot can be recovered, and that a compaction does not drop protected recent output. Each promise is about a sequence of reads and writes, and the spec turns “recoverable” into a property of the operation order that the checker can test.

what a checked spec cannot tell you

A checked spec proves the model, not the implementation. If the spec leaves out a step the real code performs, such as a retry path or a second writer, the result is about a different, friendlier system. Mutation is the defense here as it is for tests: a spec that cannot catch a deliberately wrong version of the protocol is decoration, which is why the vhalla specs run against mutants.

State space limits what can be checked. A model with too many variables or too deep a trace does not finish. The practical answer is to model the protocol at the level where order matters (messages, states, invariants) and leave out payload content, timing values, and counters that do not affect safety. A spec that tries to be the whole system becomes uncheckable.

A spec cannot choose its own invariants. Someone has to write the property that matters. An invariant that only says “nothing crashes” misses the bugs that do, such as a message delivered twice, lost on restore, or accepted without quorum. Writing that property down is much of the design work, and teams often first learn there that their protocol had no precise safety statement.

Across the portfolio, the specs enumerate orderings a reviewer cannot hold in their head, and their verdicts are evidence about the protocol design. Whole-system correctness still depends on the other techniques in this series: tests that tie the implementation to the model, stateful tests, and replayable records of what actually ran.

keep reading: free for subscribers

the rest of this lesson is free. enter your email to subscribe, and every subscriber lesson unlocks in this browser.

already subscribed? enter the same email to unlock.