1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! Loom model-checking tests for [`crate::ReadyGate`] and
//! [`crate::counter::SharedCounter`].
//!
//! These verify the crate's atomic disciplines under *all* bounded
//! interleavings:
//!
//! 1. `ReadyGate` Release/Acquire pairing: a payload stored (Relaxed) before
//! `set_ready()` (Release) is always visible to a thread that observed
//! `is_ready() == true` (Acquire) — the readiness flag doubles as a
//! publication fence, so no torn/stale payload reads are possible.
//! 2. `ReadyGate` store-observation: a single `set_ready()` is never lost —
//! every waiter thread eventually observes it (no lost wakeups in the
//! atomic flag discipline), and each waiter's observation is monotone.
//!
//! 3. `SharedCounter` update discipline: balanced increment/decrement pairs
//! across racing threads always return the counter to its start value
//! (no lost updates) and `increment()` never overshoots the number of
//! in-flight increments.
//!
//! What loom does NOT cover (trusted components):
//! - `ReadyGate::wait_until_ready` — the tokio poll/sleep loop is a tokio
//! scheduling concern; only the atomic flag parts are model-checked.
//! - `TtlCache` — correctness is time-based (`Instant` expiry under a
//! `std::sync::Mutex`); loom does not model the passage of time, and the
//! Mutex is std-trusted. Mutex-protected state has no interleavings loom
//! could expose beyond what the type system already guarantees.
//!
//! Run with:
//! ```text
//! RUSTFLAGS="--cfg loom" cargo test --release --lib -- --test-threads=1 loom
//! ```
use crateReadyGate;
use crateSharedCounter;
use Arc;
use ;
/// Model 1: Release/Acquire message passing through the readiness flag.
///
/// The producer publishes a payload with a Relaxed store, then marks the
/// gate ready. The consumer spins on `is_ready()` (Acquire) and reads the
/// payload. For every interleaving, the consumer must observe the payload —
/// if the gate's Release/Acquire pairing were broken (e.g. downgraded to
/// Relaxed on either side), this model would catch the stale read.
/// Model 2: racing `set_ready` stores are never lost.
///
/// Two threads race to mark the gate ready while an observer polls
/// `is_ready()`. For every interleaving, the observer must unblock — the
/// idempotent stores must not be lost or observed inconsistently — and the
/// gate must remain ready afterwards.
///
/// (More than one *spinning* waiter exceeds loom's practical exploration
/// budget: every yield in a spin loop is a scheduling point, so the branch
/// count grows multiplicatively. One spinning observer keeps the model
/// bounded while still covering the set/wait discipline.)
/// Model 3: `SharedCounter` balanced pairs never lose updates.
///
/// Two threads each perform an increment/decrement pair. Invariants for
/// every interleaving: the counter returns to exactly 0 (no lost update),
/// no `increment()` overshoots the number of in-flight increments (result
/// is 1 or 2, never more), and the counter is never observed above the
/// live-increment bound.
///
/// Note: the two `increment()` results are *not* asserted distinct — when
/// the pairs nest (inc, inc, dec, dec) they differ, but when they serialize
/// (inc, dec, inc, dec) both legitimately return 1. Overshoot and lost
/// updates are the invariants that must hold unconditionally.