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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Loom models for the arena memtable and the engine's read horizon.
//!
//! Loom replays each model under every thread interleaving the C11
//! memory model permits, so what it checks is *ordering*: which value an
//! `Acquire` load may legally return given the `Release` stores that
//! preceded it. That is exactly the shape of the memtable's publication
//! protocol (invariant S1 in `engine::skiplist`) and of the read
//! horizon (H1 to H3 in `engine::read_horizon`).
//!
//! # What loom here does and does not prove
//!
//! Loom sees the atomics that come from `crate::sync` and the locks
//! built on them, and nothing else. It therefore checks:
//!
//! - that a reader which observes a node observes the links that were
//! published before it, so a concurrent insert can neither hide an
//! already-published key nor expose a half-linked tower;
//! - that a value read out of a node always belongs to the key and
//! sequence the reader matched on;
//! - that the read horizon never advertises a sequence whose memtable
//! insert the reader cannot yet see;
//! - that the flush handoff leaves every key reachable at every instant;
//! - that a reader pinning one version walks a whole snapshot across a
//! concurrent compaction, and that a flush and a compaction
//! publishing at once cannot lose one another's edits.
//!
//! The version models in [`version`] are protocol models and say so:
//! the production `VersionSet` writes a manifest record and opens an
//! SSTable reader inside `apply`, and its locks come from `std::sync`
//! rather than from `crate::sync`, so loom can neither run it nor see
//! its ordering. What they reproduce is the part loom can decide - the
//! `Arc<Version>` pin, the clone-mutate-store, and the lock scope around
//! it - with the table contents stood in for.
//!
//! It does **not** check the raw key and value bytes for a data race:
//! those are plain arena memory, not a `loom::cell::UnsafeCell`, and
//! loom's cooperative scheduler physically sequences the writes anyway.
//! Byte-level aliasing, provenance and use-after-free are miri's job,
//! and `tests/loom_memtable.rs` documents which invariant each tool
//! covers.
//!
//! # Model size
//!
//! Every model is two or three threads doing two or three operations.
//! Nothing about the structure is shrunk for the checker: the tower is
//! its production `MAX_HEIGHT` of 12, because loom's partial-order
//! reduction prunes the levels no thread ever writes to, and the search
//! is the same size at 3 levels as at 12. [`explore`] counts both the
//! interleavings loom ran and the ones that reached the state being
//! checked, and fails the model if either is implausibly small: a model
//! that explores one schedule, or whose conditional assertion never
//! fires, is worse than no model at all.
//!
//! # Calibration
//!
//! Every positive model is paired with one that deliberately gets the
//! ordering wrong - the two-step seek `seek_ge` replaced, a flush that
//! retires the frozen memtable before it installs the table, a relaxed
//! read horizon, a compaction that publishes its removals and its
//! addition as two versions, two version swaps with nothing serializing
//! them - and the pair is only meaningful if the wrong one fails. They
//! are run as `#[should_panic]` tests in `tests/loom_memtable.rs`.
//!
//! A calibration stops at the first schedule that trips its assertion,
//! so [`explore`]'s floors never execute for one: the panic unwinds past
//! them. [`Report`] prints on the way out regardless, so the number a
//! calibration reports is how many schedules loom searched before it
//! found the bug, not how large its search space is.
use Arc as StdArc;
use ;
use ArenaProfile;
use LookupKey;
use ;
/// Per-memtable arena budget. One 4 KiB chunk holds every entry any
/// model inserts, so no model allocates a chunk once its threads are
/// running and the shared chunk pool stays uncontended.
const BUDGET: usize = 4 * 1024;
/// Column family every model writes into.
const CF: u32 = 0;
/// A fresh memtable on the embedded arena profile with a `budget`-byte
/// arena and its own chunk pool.
/// A fresh memtable on the embedded arena profile.
/// A lookup key for `user_key` at the newest visible sequence.
/// A count of the executions that reached the state a model exists to
/// check.
///
/// An interleaving count alone cannot tell a real search from one whose
/// conditional assertion never fired: a reader that never happens to
/// observe the writer's key passes every `if let Some(..)` body without
/// executing one. The witness count is that missing half, and [`explore`]
/// fails a model whose witness stays at zero.
pub ;
/// Prints the interleaving and witness counts on the way out, including
/// when the model failed and the stack is unwinding: the size of the
/// search is part of a failure report, not only of a success.
/// Run `model` under loom and report how large the search was.
///
/// `min_interleavings` is the calibration on the search: it is how many
/// distinct schedules the model must produce for its assertions to mean
/// anything. `min_witnesses` is the calibration on the assertions: it is
/// how many of those schedules must have reached the state the model
/// exists to check. A model that fails either floor fails, because a
/// model that explores one schedule, or that never reaches its own
/// interesting branch, is worse than no model at all.