------------------------------ MODULE SleepSort ------------------------------ (***************************************************************************) (* A TLA+ model of the sleep sort in sort/sleep.go. *) (* *) (* This verifies a *model*, not the Go source. It abstracts the goroutines *) (* + WaitGroup + unbuffered channel of sleep.go into: *) (* *) (* - a global discrete clock (stand-in for wall-clock time / sleeps), *) (* - one "timer" per input element that fires when the clock reaches *) (* that element's value (a value-v element sleeps v seconds), and *) (* - a single collector that receives one fired value at a time *) (* (an unbuffered channel with one receiver hands off exactly one *) (* value per rendezvous, with no buffering or reordering). *) (* *) (* Crucially, sortedness of the output is NOT baked in: it must *emerge* *) (* from the timing mechanism. The only thing that enforces order is Tick's *) (* urgency guard (time cannot pass while a fired-but-unreceived timer is *) (* waiting). Remove that guard and TLC finds an unsorted counterexample -- *) (* which is exactly what makes this a real check rather than a tautology. *) (***************************************************************************) EXTENDS Naturals, Sequences, FiniteSets CONSTANT Input \* the values to sort, as a sequence, e.g. <<3, 1, 2>> \* Concrete value for the model. TLC config files cannot parse a sequence \* literal in a "CONSTANT Input = ..." assignment, so SleepSort.cfg overrides the \* constant with this operator via "CONSTANT Input <- InputValue". InputValue == <<3, 1, 4, 1, 2>> Idx == DOMAIN Input Values == { Input[i] : i \in Idx } VARIABLES clock, \* global time; the sleeps are measured against it sent, \* sent[i] = TRUE once element i has been received output \* the values collected so far, in arrival order vars == <> \* Multiplicity of value v in a sequence s (compares the two as multisets). Count(v, s) == Cardinality({ k \in DOMAIN s : s[k] = v }) TypeOK == /\ clock \in Nat /\ sent \in [Idx -> BOOLEAN] /\ \A k \in DOMAIN output : output[k] \in Values /\ Len(output) =< Cardinality(Idx) Init == /\ clock = 0 /\ sent = [i \in Idx |-> FALSE] /\ output = << >> \* Element i's timer has fired (its sleep of Input[i] seconds has elapsed) and \* it has not yet been received. Ready(i) == /\ ~sent[i] /\ clock >= Input[i] AllDone == \A i \in Idx : sent[i] \* The collector receives one ready value (channel rendezvous: exactly one \* value transfers, chosen nondeterministically among those currently ready). Receive == /\ \E i \in Idx : /\ Ready(i) /\ sent' = [sent EXCEPT ![i] = TRUE] /\ output' = Append(output, Input[i]) /\ clock' = clock \* Time advances only when no fired timer is waiting to be received: every \* not-yet-received element still has its deadline strictly in the future. \* This urgency is what guarantees shorter sleeps deliver before longer ones. Tick == /\ ~AllDone /\ \A i \in Idx : ~sent[i] => clock < Input[i] /\ clock' = clock + 1 /\ UNCHANGED <> \* Once everything has been collected, stutter (models the closed channel / \* finished range loop) so termination is not mistaken for a deadlock. Done == /\ AllDone /\ UNCHANGED vars Next == Receive \/ Tick \/ Done Spec == Init /\ [][Next]_vars /\ WF_vars(Receive) /\ WF_vars(Tick) ----------------------------------------------------------------------------- \* Properties checked by TLC (see SleepSort.cfg). \* SAFETY: whatever has been collected so far is always non-decreasing. IsSorted(s) == \A a, b \in DOMAIN s : a < b => s[a] <= s[b] OutputSorted == IsSorted(output) \* SAFETY: the output never invents or duplicates values, and once finished it \* is exactly a permutation (multiset) of the input. PermutationWhenDone == AllDone => \A v \in Values : Count(v, output) = Count(v, Input) \* LIVENESS: the collector eventually receives every value (no deadlock, no \* value left sleeping forever) -- i.e. the algorithm terminates. Terminates == <>AllDone \* Note: the state space is already finite without a constraint -- Tick is \* disabled once clock reaches the largest input value, so clock never grows \* unbounded and TLC explores every reachable state. =============================================================================