aboutsummaryrefslogtreecommitdiff
path: root/design/68578/spinbit.tla
diff options
context:
space:
mode:
Diffstat (limited to 'design/68578/spinbit.tla')
-rw-r--r--design/68578/spinbit.tla533
1 files changed, 533 insertions, 0 deletions
diff --git a/design/68578/spinbit.tla b/design/68578/spinbit.tla
new file mode 100644
index 0000000..3023b20
--- /dev/null
+++ b/design/68578/spinbit.tla
@@ -0,0 +1,533 @@
+------------------------------ MODULE spinbit ------------------------------
+EXTENDS TLC, Integers, Sequences, FiniteSets
+
+CONSTANT NumThreads
+CONSTANT NumAcquires
+CONSTANT NumSpins \* performance tunable "spin"
+CONSTANT WakeAny \* reduce state space: wake any of the sleepers
+CONSTANT NULL \* [ model value ]
+
+ASSUME NumThreads >= 1
+ASSUME NumAcquires >= 0
+ASSUME NumSpins >= 0
+ASSUME WakeAny \in BOOLEAN
+
+Threads == 1..NumThreads
+
+(* --algorithm spinbit
+
+variables
+
+ \* logical owner
+ owner = NULL;
+
+ \* lock state, stored in l.key word
+ l_key_locked = FALSE; \* bit 0
+ l_key_sleeping = FALSE; \* bit 1
+ l_key_spinning = FALSE; \* bit 63
+
+ \* local variables
+ v_locked = [t \in Threads |-> FALSE];
+ v_sleeping = [t \in Threads |-> FALSE];
+ v_spinning = [t \in Threads |-> FALSE];
+ v8_locked = [t \in Threads |-> FALSE];
+ v8_sleeping = [t \in Threads |-> FALSE];
+ weSpin = [t \in Threads |-> FALSE];
+ i = [t \in Threads |-> 0];
+
+ \* OS state
+ sleepers = {};
+
+ \*
+ acquisitions = 0;
+
+ \* performance tunables
+ spin = NumSpins;
+
+define
+
+ TypeInvariant ==
+ /\ owner \in Threads \union {NULL}
+ /\ l_key_locked \in BOOLEAN
+ /\ l_key_sleeping \in BOOLEAN
+ /\ l_key_spinning \in BOOLEAN
+ /\ \A t \in Threads: v_locked[t] \in BOOLEAN
+ /\ \A t \in Threads: v_sleeping[t] \in BOOLEAN
+ /\ \A t \in Threads: v_spinning[t] \in BOOLEAN
+ /\ \A t \in Threads: v8_locked[t] \in BOOLEAN
+ /\ \A t \in Threads: v8_sleeping[t] \in BOOLEAN
+ /\ \A t \in Threads: weSpin[t] \in BOOLEAN
+ /\ \A t \in Threads: i[t] \in Nat
+ /\ sleepers \subseteq Threads
+ /\ acquisitions \in Nat
+
+ NoLostWakeups ==
+ (Cardinality(sleepers) > 0) ~> (Cardinality(sleepers) = 0)
+
+ HaveAcquisitions ==
+ <>[](acquisitions >= NumAcquires)
+
+end define;
+
+\* This is a lock, so if a process dies while holding it the rest of the simulation
+\* won't be able to make progress. We need "fair process" so the threads don't die.
+fair process thread \in Threads
+begin
+ NonCriticalSection:
+ if acquisitions >= NumAcquires then
+ goto Done;
+ end if;
+
+ SpeculativeGrab:
+ \* v8 = atomic.Xchg8(k8, mutexLocked)
+ v8_locked[self] := l_key_locked;
+ v8_sleeping[self] := l_key_sleeping;
+ l_key_locked := TRUE;
+ l_key_sleeping := FALSE;
+ SpeculativeGrabCheck:
+ if v8_locked[self] = FALSE then
+ if v8_sleeping[self] = TRUE then
+ \* atomic.Or8(k8, mutexSleeping)
+ l_key_sleeping := TRUE;
+ end if;
+ goto Locked;
+ else
+ \* var v uintptr = mutexLocked
+ v_locked[self] := TRUE;
+ v_sleeping[self] := FALSE;
+ v_spinning[self] := FALSE;
+ weSpin[self] := FALSE;
+ end if;
+
+ EnterSlowPath:
+ i[self] := 0;
+
+ TryAcquire:
+ if v_locked[self] = TRUE then
+ goto SetSleepBit
+ else
+ if weSpin[self] then
+ \* next := (v &^ mutexSpinning) | mutexLocked | mutexSleeping
+ \* if atomic.Casuintptr(&l.key, v, next) {
+ if v_locked[self] = l_key_locked
+ /\ v_sleeping[self] = l_key_sleeping
+ /\ v_spinning[self] = l_key_spinning then
+ l_key_locked := TRUE;
+ l_key_sleeping := TRUE;
+ l_key_spinning := FALSE;
+ goto Locked;
+ end if;
+ else
+ \* prev8 := atomic.Xchg8(k8, mutexLocked|mutexSleeping)
+ \* if prev8&mutexLocked == 0 {
+ if l_key_locked = FALSE then
+ l_key_locked := TRUE;
+ l_key_sleeping := TRUE;
+ goto Locked;
+ else
+ l_key_locked := TRUE;
+ l_key_sleeping := TRUE;
+ end if;
+ end if;
+ end if;
+ TryAcquireLoop:
+ \* v = atomic.Loaduintptr(&l.key)
+ v_locked[self] := l_key_locked;
+ v_sleeping[self] := l_key_sleeping;
+ v_spinning[self] := l_key_spinning;
+ goto TryAcquire;
+
+ SetSleepBit:
+ \* atomic.Or8(k8, mutexSleeping)
+ if v_sleeping[self] = FALSE then
+ l_key_sleeping := TRUE;
+ v_sleeping[self] := TRUE;
+ end if;
+
+ SetSpinBit:
+ \* if !weSpin && atomic.Xchg8(key8Upper(&l.key), mutexSpinning>>((goarch.PtrSize-1)*8)) == 0 {
+ if weSpin[self] = FALSE then
+ weSpin[self] := (l_key_spinning = FALSE);
+ l_key_spinning := TRUE;
+ end if;
+
+ DoSpin:
+ if weSpin[self] = TRUE then
+ if i[self] < spin then
+ \* procyield(active_spin_cnt)
+ \* v = atomic.Loaduintptr(&l.key)
+ v_locked[self] := l_key_locked;
+ v_sleeping[self] := l_key_sleeping;
+ v_spinning[self] := l_key_spinning;
+ goto TryAcquire;
+ else
+ weSpin[self] := FALSE;
+ \* atomic.Xchg8(key8Upper(&l.key), 0)
+ l_key_spinning := FALSE;
+ end if;
+ end if;
+
+ Sleep:
+ \* v = atomic.Loaduintptr(&l.key)
+ v_locked[self] := l_key_locked;
+ v_sleeping[self] := l_key_sleeping;
+ v_spinning[self] := l_key_spinning;
+ Futex:
+ if v_locked[self] = FALSE then
+ goto TryAcquire;
+ else
+ \* futexsleep(k32, uint32(v|mutexSleeping), -1)
+ if l_key_locked = v_locked[self]
+ /\ l_key_sleeping = TRUE then
+ sleepers := sleepers \union {self};
+ end if;
+ end if;
+
+ Sleeping:
+ await self \notin sleepers;
+ i[self] := 0;
+ \* v = atomic.Loaduintptr(&l.key)
+ v_locked[self] := l_key_locked;
+ v_sleeping[self] := l_key_sleeping;
+ v_spinning[self] := l_key_spinning;
+ goto TryAcquire;
+
+ Locked:
+ owner := self;
+ CriticalSection:
+ acquisitions := acquisitions + 1;
+ Unlock:
+ owner := NULL;
+ \* prev8 := atomic.Xchg8(key8(&l.key), 0)
+ v8_locked[self] := l_key_locked;
+ v8_sleeping[self] := l_key_sleeping;
+ l_key_locked := FALSE;
+ l_key_sleeping := FALSE;
+ if v8_sleeping[self] = TRUE then
+ goto Wakeup;
+ else
+ goto NonCriticalSection;
+ end if;
+
+ Wakeup:
+ \* v := atomic.Loaduintptr(&l.key)
+ v_locked[self] := l_key_locked;
+ v_sleeping[self] := l_key_sleeping;
+ v_spinning[self] := l_key_spinning;
+ Wake2:
+ if v_spinning[self] = FALSE then
+ if Cardinality(sleepers) > 0 then
+ if WakeAny then
+ with other = CHOOSE other \in sleepers: TRUE do
+ sleepers := sleepers \ {other};
+ end with;
+ else
+ with other \in sleepers do
+ sleepers := sleepers \ {other};
+ end with;
+ end if;
+ end if;
+ end if;
+ goto NonCriticalSection;
+
+end process;
+
+end algorithm; *)
+\* BEGIN TRANSLATION (chksum(pcal) = "a993096" /\ chksum(tla) = "41bbe278")
+VARIABLES owner, l_key_locked, l_key_sleeping, l_key_spinning, v_locked,
+ v_sleeping, v_spinning, v8_locked, v8_sleeping, weSpin, i, sleepers,
+ acquisitions, spin, pc
+
+(* define statement *)
+TypeInvariant ==
+ /\ owner \in Threads \union {NULL}
+ /\ l_key_locked \in BOOLEAN
+ /\ l_key_sleeping \in BOOLEAN
+ /\ l_key_spinning \in BOOLEAN
+ /\ \A t \in Threads: v_locked[t] \in BOOLEAN
+ /\ \A t \in Threads: v_sleeping[t] \in BOOLEAN
+ /\ \A t \in Threads: v_spinning[t] \in BOOLEAN
+ /\ \A t \in Threads: v8_locked[t] \in BOOLEAN
+ /\ \A t \in Threads: v8_sleeping[t] \in BOOLEAN
+ /\ \A t \in Threads: weSpin[t] \in BOOLEAN
+ /\ \A t \in Threads: i[t] \in Nat
+ /\ sleepers \subseteq Threads
+ /\ acquisitions \in Nat
+
+NoLostWakeups ==
+ (Cardinality(sleepers) > 0) ~> (Cardinality(sleepers) = 0)
+
+HaveAcquisitions ==
+ <>[](acquisitions >= NumAcquires)
+
+
+vars == << owner, l_key_locked, l_key_sleeping, l_key_spinning, v_locked,
+ v_sleeping, v_spinning, v8_locked, v8_sleeping, weSpin, i,
+ sleepers, acquisitions, spin, pc >>
+
+ProcSet == (Threads)
+
+Init == (* Global variables *)
+ /\ owner = NULL
+ /\ l_key_locked = FALSE
+ /\ l_key_sleeping = FALSE
+ /\ l_key_spinning = FALSE
+ /\ v_locked = [t \in Threads |-> FALSE]
+ /\ v_sleeping = [t \in Threads |-> FALSE]
+ /\ v_spinning = [t \in Threads |-> FALSE]
+ /\ v8_locked = [t \in Threads |-> FALSE]
+ /\ v8_sleeping = [t \in Threads |-> FALSE]
+ /\ weSpin = [t \in Threads |-> FALSE]
+ /\ i = [t \in Threads |-> 0]
+ /\ sleepers = {}
+ /\ acquisitions = 0
+ /\ spin = NumSpins
+ /\ pc = [self \in ProcSet |-> "NonCriticalSection"]
+
+NonCriticalSection(self) == /\ pc[self] = "NonCriticalSection"
+ /\ IF acquisitions >= NumAcquires
+ THEN /\ pc' = [pc EXCEPT ![self] = "Done"]
+ ELSE /\ pc' = [pc EXCEPT ![self] = "SpeculativeGrab"]
+ /\ UNCHANGED << owner, l_key_locked,
+ l_key_sleeping, l_key_spinning,
+ v_locked, v_sleeping, v_spinning,
+ v8_locked, v8_sleeping, weSpin, i,
+ sleepers, acquisitions, spin >>
+
+SpeculativeGrab(self) == /\ pc[self] = "SpeculativeGrab"
+ /\ v8_locked' = [v8_locked EXCEPT ![self] = l_key_locked]
+ /\ v8_sleeping' = [v8_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ l_key_locked' = TRUE
+ /\ l_key_sleeping' = FALSE
+ /\ pc' = [pc EXCEPT ![self] = "SpeculativeGrabCheck"]
+ /\ UNCHANGED << owner, l_key_spinning, v_locked,
+ v_sleeping, v_spinning, weSpin, i,
+ sleepers, acquisitions, spin >>
+
+SpeculativeGrabCheck(self) == /\ pc[self] = "SpeculativeGrabCheck"
+ /\ IF v8_locked[self] = FALSE
+ THEN /\ IF v8_sleeping[self] = TRUE
+ THEN /\ l_key_sleeping' = TRUE
+ ELSE /\ TRUE
+ /\ UNCHANGED l_key_sleeping
+ /\ pc' = [pc EXCEPT ![self] = "Locked"]
+ /\ UNCHANGED << v_locked, v_sleeping,
+ v_spinning, weSpin >>
+ ELSE /\ v_locked' = [v_locked EXCEPT ![self] = TRUE]
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = FALSE]
+ /\ v_spinning' = [v_spinning EXCEPT ![self] = FALSE]
+ /\ weSpin' = [weSpin EXCEPT ![self] = FALSE]
+ /\ pc' = [pc EXCEPT ![self] = "EnterSlowPath"]
+ /\ UNCHANGED l_key_sleeping
+ /\ UNCHANGED << owner, l_key_locked,
+ l_key_spinning, v8_locked,
+ v8_sleeping, i, sleepers,
+ acquisitions, spin >>
+
+EnterSlowPath(self) == /\ pc[self] = "EnterSlowPath"
+ /\ i' = [i EXCEPT ![self] = 0]
+ /\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v_locked, v_sleeping,
+ v_spinning, v8_locked, v8_sleeping,
+ weSpin, sleepers, acquisitions, spin >>
+
+TryAcquire(self) == /\ pc[self] = "TryAcquire"
+ /\ IF v_locked[self] = TRUE
+ THEN /\ pc' = [pc EXCEPT ![self] = "SetSleepBit"]
+ /\ UNCHANGED << l_key_locked, l_key_sleeping,
+ l_key_spinning >>
+ ELSE /\ IF weSpin[self]
+ THEN /\ IF v_locked[self] = l_key_locked
+ /\ v_sleeping[self] = l_key_sleeping
+ /\ v_spinning[self] = l_key_spinning
+ THEN /\ l_key_locked' = TRUE
+ /\ l_key_sleeping' = TRUE
+ /\ l_key_spinning' = FALSE
+ /\ pc' = [pc EXCEPT ![self] = "Locked"]
+ ELSE /\ pc' = [pc EXCEPT ![self] = "TryAcquireLoop"]
+ /\ UNCHANGED << l_key_locked,
+ l_key_sleeping,
+ l_key_spinning >>
+ ELSE /\ IF l_key_locked = FALSE
+ THEN /\ l_key_locked' = TRUE
+ /\ l_key_sleeping' = TRUE
+ /\ pc' = [pc EXCEPT ![self] = "Locked"]
+ ELSE /\ l_key_locked' = TRUE
+ /\ l_key_sleeping' = TRUE
+ /\ pc' = [pc EXCEPT ![self] = "TryAcquireLoop"]
+ /\ UNCHANGED l_key_spinning
+ /\ UNCHANGED << owner, v_locked, v_sleeping, v_spinning,
+ v8_locked, v8_sleeping, weSpin, i,
+ sleepers, acquisitions, spin >>
+
+TryAcquireLoop(self) == /\ pc[self] = "TryAcquireLoop"
+ /\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
+ /\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v8_locked, v8_sleeping,
+ weSpin, i, sleepers, acquisitions,
+ spin >>
+
+SetSleepBit(self) == /\ pc[self] = "SetSleepBit"
+ /\ IF v_sleeping[self] = FALSE
+ THEN /\ l_key_sleeping' = TRUE
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = TRUE]
+ ELSE /\ TRUE
+ /\ UNCHANGED << l_key_sleeping, v_sleeping >>
+ /\ pc' = [pc EXCEPT ![self] = "SetSpinBit"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_spinning,
+ v_locked, v_spinning, v8_locked,
+ v8_sleeping, weSpin, i, sleepers,
+ acquisitions, spin >>
+
+SetSpinBit(self) == /\ pc[self] = "SetSpinBit"
+ /\ IF weSpin[self] = FALSE
+ THEN /\ weSpin' = [weSpin EXCEPT ![self] = (l_key_spinning = FALSE)]
+ /\ l_key_spinning' = TRUE
+ ELSE /\ TRUE
+ /\ UNCHANGED << l_key_spinning, weSpin >>
+ /\ pc' = [pc EXCEPT ![self] = "DoSpin"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ v_locked, v_sleeping, v_spinning,
+ v8_locked, v8_sleeping, i, sleepers,
+ acquisitions, spin >>
+
+DoSpin(self) == /\ pc[self] = "DoSpin"
+ /\ IF weSpin[self] = TRUE
+ THEN /\ IF i[self] < spin
+ THEN /\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
+ /\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
+ /\ UNCHANGED << l_key_spinning, weSpin >>
+ ELSE /\ weSpin' = [weSpin EXCEPT ![self] = FALSE]
+ /\ l_key_spinning' = FALSE
+ /\ pc' = [pc EXCEPT ![self] = "Sleep"]
+ /\ UNCHANGED << v_locked, v_sleeping,
+ v_spinning >>
+ ELSE /\ pc' = [pc EXCEPT ![self] = "Sleep"]
+ /\ UNCHANGED << l_key_spinning, v_locked,
+ v_sleeping, v_spinning, weSpin >>
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping, v8_locked,
+ v8_sleeping, i, sleepers, acquisitions, spin >>
+
+Sleep(self) == /\ pc[self] = "Sleep"
+ /\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
+ /\ pc' = [pc EXCEPT ![self] = "Futex"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v8_locked, v8_sleeping, weSpin,
+ i, sleepers, acquisitions, spin >>
+
+Futex(self) == /\ pc[self] = "Futex"
+ /\ IF v_locked[self] = FALSE
+ THEN /\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
+ /\ UNCHANGED sleepers
+ ELSE /\ IF l_key_locked = v_locked[self]
+ /\ l_key_sleeping = TRUE
+ THEN /\ sleepers' = (sleepers \union {self})
+ ELSE /\ TRUE
+ /\ UNCHANGED sleepers
+ /\ pc' = [pc EXCEPT ![self] = "Sleeping"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v_locked, v_sleeping,
+ v_spinning, v8_locked, v8_sleeping, weSpin, i,
+ acquisitions, spin >>
+
+Sleeping(self) == /\ pc[self] = "Sleeping"
+ /\ self \notin sleepers
+ /\ i' = [i EXCEPT ![self] = 0]
+ /\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
+ /\ pc' = [pc EXCEPT ![self] = "TryAcquire"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v8_locked, v8_sleeping,
+ weSpin, sleepers, acquisitions, spin >>
+
+Locked(self) == /\ pc[self] = "Locked"
+ /\ owner' = self
+ /\ pc' = [pc EXCEPT ![self] = "CriticalSection"]
+ /\ UNCHANGED << l_key_locked, l_key_sleeping, l_key_spinning,
+ v_locked, v_sleeping, v_spinning, v8_locked,
+ v8_sleeping, weSpin, i, sleepers, acquisitions,
+ spin >>
+
+CriticalSection(self) == /\ pc[self] = "CriticalSection"
+ /\ acquisitions' = acquisitions + 1
+ /\ pc' = [pc EXCEPT ![self] = "Unlock"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v_locked, v_sleeping,
+ v_spinning, v8_locked, v8_sleeping,
+ weSpin, i, sleepers, spin >>
+
+Unlock(self) == /\ pc[self] = "Unlock"
+ /\ owner' = NULL
+ /\ v8_locked' = [v8_locked EXCEPT ![self] = l_key_locked]
+ /\ v8_sleeping' = [v8_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ l_key_locked' = FALSE
+ /\ l_key_sleeping' = FALSE
+ /\ IF v8_sleeping'[self] = TRUE
+ THEN /\ pc' = [pc EXCEPT ![self] = "Wakeup"]
+ ELSE /\ pc' = [pc EXCEPT ![self] = "NonCriticalSection"]
+ /\ UNCHANGED << l_key_spinning, v_locked, v_sleeping,
+ v_spinning, weSpin, i, sleepers, acquisitions,
+ spin >>
+
+Wakeup(self) == /\ pc[self] = "Wakeup"
+ /\ v_locked' = [v_locked EXCEPT ![self] = l_key_locked]
+ /\ v_sleeping' = [v_sleeping EXCEPT ![self] = l_key_sleeping]
+ /\ v_spinning' = [v_spinning EXCEPT ![self] = l_key_spinning]
+ /\ pc' = [pc EXCEPT ![self] = "Wake2"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v8_locked, v8_sleeping, weSpin,
+ i, sleepers, acquisitions, spin >>
+
+Wake2(self) == /\ pc[self] = "Wake2"
+ /\ IF v_spinning[self] = FALSE
+ THEN /\ IF Cardinality(sleepers) > 0
+ THEN /\ IF WakeAny
+ THEN /\ LET other == CHOOSE other \in sleepers: TRUE IN
+ sleepers' = sleepers \ {other}
+ ELSE /\ \E other \in sleepers:
+ sleepers' = sleepers \ {other}
+ ELSE /\ TRUE
+ /\ UNCHANGED sleepers
+ ELSE /\ TRUE
+ /\ UNCHANGED sleepers
+ /\ pc' = [pc EXCEPT ![self] = "NonCriticalSection"]
+ /\ UNCHANGED << owner, l_key_locked, l_key_sleeping,
+ l_key_spinning, v_locked, v_sleeping,
+ v_spinning, v8_locked, v8_sleeping, weSpin, i,
+ acquisitions, spin >>
+
+thread(self) == NonCriticalSection(self) \/ SpeculativeGrab(self)
+ \/ SpeculativeGrabCheck(self) \/ EnterSlowPath(self)
+ \/ TryAcquire(self) \/ TryAcquireLoop(self)
+ \/ SetSleepBit(self) \/ SetSpinBit(self) \/ DoSpin(self)
+ \/ Sleep(self) \/ Futex(self) \/ Sleeping(self)
+ \/ Locked(self) \/ CriticalSection(self) \/ Unlock(self)
+ \/ Wakeup(self) \/ Wake2(self)
+
+(* Allow infinite stuttering to prevent deadlock on termination. *)
+Terminating == /\ \A self \in ProcSet: pc[self] = "Done"
+ /\ UNCHANGED vars
+
+Next == (\E self \in Threads: thread(self))
+ \/ Terminating
+
+Spec == /\ Init /\ [][Next]_vars
+ /\ \A self \in Threads : WF_vars(thread(self))
+
+Termination == <>(\A self \in ProcSet: pc[self] = "Done")
+
+\* END TRANSLATION
+
+=============================================================================
+\* Modification History
+\* Last modified Tue Oct 01 13:18:42 PDT 2024 by rhysh
+\* Created Tue Sep 17 12:10:43 PDT 2024 by rhysh