loom/lib.rs
1#![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Loom is a tool for testing concurrent programs.
5//!
6//! At a high level, it runs tests many times, permuting the possible concurrent executions of each
7//! test according to what constitutes valid executions under the [C11 memory model][spec]. It then
8//! uses state reduction techniques to avoid combinatorial explosion of the number of possible
9//! executions.
10//!
11//! # Background
12//!
13//! Testing concurrent programs is challenging; concurrent strands of execution can interleave in
14//! all sorts of ways, and each such interleaving might expose a concurrency bug in the program.
15//! Some bugs may be so rare that they only occur under a very small set of possible executions,
16//! and may not surface even if you run the code millions or billions of times.
17//!
18//! Loom provides a way to deterministically explore the various possible execution permutations
19//! without relying on random executions. This allows you to write tests that verify that your
20//! concurrent code is correct under _all_ executions, not just "most of the time".
21//!
22//! Consider a simple example:
23//!
24//! ```no_run
25//! use std::sync::Arc;
26//! use std::sync::atomic::AtomicUsize;
27//! use std::sync::atomic::Ordering::SeqCst;
28//! use std::thread;
29//!
30//! # /*
31//! #[test]
32//! # */
33//! fn test_concurrent_logic() {
34//! let v1 = Arc::new(AtomicUsize::new(0));
35//! let v2 = v1.clone();
36//!
37//! thread::spawn(move || {
38//! v1.store(1, SeqCst);
39//! });
40//!
41//! assert_eq!(0, v2.load(SeqCst));
42//! }
43//! ```
44//!
45//! This program is incorrect: the main thread might yield between spawning the thread that stores
46//! to `v1` and loading from `v2`, during which time the spawned thread may get to run and store 1
47//! into `v1`. **Most** of the time, the main thread will get to the assertion before the spawned
48//! thread executes, so the assertion will succeed. But, every once in a while, the spawned thread
49//! manages to run just in time and the assertion will fail! This is obviously a contrived example,
50//! but in practice many concurrent programs exhibit similar behavior -- they operate correctly
51//! under most executions, but _some_ executions end up producing buggy behavior.
52//!
53//! Historically, the strategy for testing concurrent code has been to run tests in loops and hope
54//! that an execution fails. Or to place the testing host under load while running the test suite
55//! in an attempt to produce less frequently exercised executions. However, this kind of testing is
56//! not reliable, and, in the event an iteration should fail, debugging the cause is exceedingly
57//! difficult.
58//!
59//! The problem is compounded when other memory orderings than `SeqCst` are considered, where bugs
60//! may only occur on hardware with particular memory characteristics, and thus **no** amount of
61//! iteration will demonstrate the bug on different hardware!
62//!
63//! # Solution
64//!
65//! Loom fixes the problem by simulating the operating system's scheduler and Rust's memory model
66//! such that all possible valid behaviors are explored and tested. To see how this works out in
67//! practice, the above example can be rewritten to use loom's concurrency types as:
68//!
69//! ```no_run
70//! use loom::sync::atomic::AtomicUsize;
71//! use loom::thread;
72//!
73//! use std::sync::Arc;
74//! use std::sync::atomic::Ordering::SeqCst;
75//!
76//! # /*
77//! #[test]
78//! # */
79//! fn test_concurrent_logic() {
80//! loom::model(|| {
81//! let v1 = Arc::new(AtomicUsize::new(0));
82//! let v2 = v1.clone();
83//!
84//! thread::spawn(move || {
85//! v1.store(1, SeqCst);
86//! });
87//!
88//! assert_eq!(0, v2.load(SeqCst));
89//! });
90//! }
91//! ```
92//!
93//! Loom will run the closure provided to `loom::model` many times over, and each time a different
94//! thread scheduling will be used. That is, one execution will have the spawned thread run after
95//! the load from `v2`, and another will have the spawned thread run before the store to `v2`.
96//! Thus, the test is guaranteed to fail.
97//!
98//! # Writing tests
99//!
100//! Test cases using loom must be fully deterministic. All sources of non-determism must be via loom
101//! types so that loom can expose different possible values on each execution of the test closure.
102//! Other sources of non-determinism like random number generation or system calls cannot be
103//! modeled directly by loom, and must be mocked to be testable by loom.
104//!
105//! To model synchronization non-determinism, tests must use the loom synchronization types, such
106//! as [`Atomic*`](sync::atomic), [`Mutex`](sync::Mutex), [`RwLock`](sync::RwLock),
107//! [`Condvar`](sync::Condvar), as well as other concurrency primitives like [`thread::spawn`],
108//! [`UnsafeCell`](cell::UnsafeCell), and [`lazy_static!`]. However, when **not** running loom
109//! tests, the `std` should be used, since the loom runtime won't be active. This means that
110//! library code will need to use conditional compilation to decide which types to use.
111//!
112//! It is recommended to use a `loom` cfg flag to signal using the loom types. You can do this by
113//! passing `RUSTFLAGS="--cfg loom"` as part of the command when you want to run the loom tests.
114//! Then modify your `Cargo.toml` to include loom like this:
115//!
116//! ```toml
117//! [target.'cfg(loom)'.dependencies]
118//! loom = "0.7"
119//! ```
120//!
121//! One common strategy to use the right types with and without loom is to create a module in your
122//! crate named `sync` or any other name of your choosing. In this module, list out the types that
123//! need to be toggled between loom and `std`:
124//!
125//! ```
126//! #[cfg(loom)]
127//! pub(crate) use loom::sync::atomic::AtomicUsize;
128//!
129//! #[cfg(not(loom))]
130//! pub(crate) use std::sync::atomic::AtomicUsize;
131//! ```
132//!
133//! Then, elsewhere in the library:
134//!
135//! ```ignore
136//! use crate::sync::AtomicUsize;
137//! ```
138//!
139//! ## Handling Loom API differences.
140//!
141//! Most of loom's type are drop-in replacements for their counterpart in `std`, but sometimes
142//! there are minor API differences that you must work around. If your library must use Loom APIs
143//! that differ from `std` types, then the library will be required to implement those APIs for
144//! `std`. For example, for `UnsafeCell`, in the library's source, add the following:
145//!
146//! ```
147//! #![cfg(not(loom))]
148//!
149//! #[derive(Debug)]
150//! pub(crate) struct UnsafeCell<T>(std::cell::UnsafeCell<T>);
151//!
152//! impl<T> UnsafeCell<T> {
153//! pub(crate) fn new(data: T) -> UnsafeCell<T> {
154//! UnsafeCell(std::cell::UnsafeCell::new(data))
155//! }
156//!
157//! pub(crate) fn with<R>(&self, f: impl FnOnce(*const T) -> R) -> R {
158//! f(self.0.get())
159//! }
160//!
161//! pub(crate) fn with_mut<R>(&self, f: impl FnOnce(*mut T) -> R) -> R {
162//! f(self.0.get())
163//! }
164//! }
165//! ```
166//!
167//! ## Yielding
168//!
169//! Some concurrent algorithms assume a fair scheduler. For example, a spin lock assumes that, at
170//! some point, another thread will make enough progress for the lock to become available. This
171//! presents a challenge for loom as its scheduler is, by design, not fair. It is specifically
172//! trying to emulate every _possible_ execution, which may mean that another thread does not get
173//! to run for a very long time (see also [Spinlocks Considered Harmful]). In such cases, loops
174//! must include calls to [`loom::thread::yield_now`](thread::yield_now). This tells loom that
175//! another thread needs to be scheduled in order for the current one to make progress.
176//!
177//! # Running Loom Tests
178//!
179//! Loom tests must be run separately, with `RUSTFLAGS="--cfg loom"` specified (assuming you went
180//! with the `cfg` approach suggested above). For example, if the library includes a test file:
181//! `tests/loom_my_struct.rs` that includes tests with [`loom::model`](mod@model), then run the
182//! following command:
183//!
184//! ```console
185//! RUSTFLAGS="--cfg loom" cargo test --test loom_my_struct --release
186//! ```
187//!
188//! Note that you will generally want to run loom tests with `--release` since loom must execute
189//! each test closure a large number of times, at which point the speed win from optimized code
190//! makes a big difference.
191//!
192//! # Debugging Loom Failures
193//!
194//! Loom's deterministic execution allows the specific chain of events leading to a test failure
195//! can be isolated for debugging. When a loom test fails, the first step is to isolate the exact
196//! execution path that resulted in the failure. To do this, Loom is able to output the execution
197//! path to a file. Two environment variables are useful for this process:
198//!
199//! - `LOOM_CHECKPOINT_FILE`
200//! - `LOOM_CHECKPOINT_INTERVAL`
201//!
202//! The first specifies the file to write to and read from. The second specifies how often to write
203//! to the file. If the execution fails on the 10,000,000th permutation, it is faster to write to a
204//! file every 10,000 iterations instead of every single one.
205//!
206//! To isolate the exact failing path, first run the following command to generate the checkpoint
207//! for the failing scenario:
208//!
209//! ```console
210//! LOOM_CHECKPOINT_FILE=my_test.json [other env vars] \
211//! cargo test --test loom_my_struct --release [failing test]
212//! ```
213//!
214//! Then this to check that the next permutation indeed triggers the fault:
215//!
216//! ```console
217//! LOOM_CHECKPOINT_INTERVAL=1 LOOM_CHECKPOINT_FILE=my_test.json [other env vars] \
218//! cargo test --test loom_my_struct --release [failing test]
219//! ```
220//!
221//! The test should fail on the first permutation, effectively isolating the failure
222//! scenario.
223//!
224//! The next step is to enable additional log output for just the failing permutation. Again, there
225//! are some environment variables for this:
226//!
227//! - `LOOM_LOG`
228//! - `LOOM_LOCATION`
229//!
230//! The first environment variable, `LOOM_LOG`, outputs a marker on every thread switch. This helps
231//! with tracing the exact steps in a threaded environment that results in the test failure.
232//!
233//! The second, `LOOM_LOCATION`, enables location tracking. This includes additional information in
234//! panic messages that helps identify which specific field resulted in the error.
235//!
236//! Put together, the command becomes (yes, we know this is not great... but it works):
237//!
238//! ```console
239//! LOOM_LOG=trace \
240//! LOOM_LOCATION=1 \
241//! LOOM_CHECKPOINT_INTERVAL=1 \
242//! LOOM_CHECKPOINT_FILE=my_test.json \
243//! RUSTFLAGS="--cfg loom" \
244//! [other env vars] \
245//! cargo test --test loom_my_struct --release [failing test]
246//! ```
247//!
248//! This should provide you with a trace of all the concurrency events leading up to the failure,
249//! which should allow you to identify how the bug is triggered.
250//!
251//! # Limitations and Caveats
252//!
253//! ## Intrusive Implementation
254//!
255//! Loom works by intercepting all loads, stores, and other concurrency-sensitive operations (like
256//! spawning threads) that may trigger concurrency bugs in an applications. But this interception
257//! is not automatic -- it requires that the code being tested specifically uses the loom
258//! replacement types. Any code that does not use loom's replacement types is invisible to loom,
259//! and thus won't be subject to the loom model's permutation.
260//!
261//! While it is relatively simple to utilize loom's types in a single crate through the root-level
262//! `#[cfg(loom)] mod sync` approach suggested earlier, more complex use-cases may require the use
263//! of a library that itself uses concurrent constructs like locks and channels. In such cases,
264//! that library must _also_ be augmented to support loom to achieve complete execution coverage.
265//!
266//! Note that loom still works if some concurrent operations are hidden from it (for example, if
267//! you use `std::sync::Arc` instead of `loom::sync::Arc`). It just means that loom won't be able
268//! to reason about the interaction between those operations and the other concurrent operations in
269//! your program, and thus certain executions that are possible in the real world won't be modeled.
270//!
271//! ## Large Models
272//!
273//! By default, loom runs an **exhaustive** check of your program's possible concurrent executions
274//! where **all** possible interleavings are checked. Loom's state reduction algorithms (see
275//! "Implementation" below) significantly reduce the state space that must be explored, but complex
276//! models can still take **significant** time to complete.
277//!
278//! To handle such large models in a more reasonable amount of time, you may need to **not** run
279//! an exhaustive check, and instead tell loom to prune out interleavings that are unlikely to
280//! reveal additional bugs. You do this by providing loom with a _thread pre-emption bound_. If you
281//! set such a bound, loom will check all possible executions that include **at most** `n` thread
282//! pre-emptions (where one thread is forcibly stopped and another one runs in its place. **In
283//! practice, setting the thread pre-emption bound to 2 or 3 is enough to catch most bugs** while
284//! significantly reducing the number of possible executions.
285//!
286//! To set the thread pre-emption bound, set the `LOOM_MAX_PREEMPTIONS` environment
287//! variable when running tests (or set
288//! [`Builder::preemption_bound`](model::Builder::preemption_bound)). For example:
289//!
290//! ```console
291//! LOOM_MAX_PREEMPTIONS=3 RUSTFLAGS="--cfg loom" cargo test --test loom_my_struct --release
292//! ```
293//!
294//! ## Relaxed Memory Ordering
295//!
296//! The [`Relaxed` memory ordering](std::sync::atomic::Ordering::Relaxed) allows particularly
297//! strange executions. For example, in the following code snippet, it is [completely
298//! legal][spec-relaxed] for `r1 == r2 == 42`!
299//!
300//! ```rust,no_run
301//! # use std::sync::atomic::{AtomicUsize, Ordering};
302//! # use std::thread;
303//! # let x: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
304//! # let y: &'static _ = Box::leak(Box::new(AtomicUsize::new(0)));
305//! thread::spawn(move || {
306//! let r1 = y.load(Ordering::Relaxed); // A
307//! x.store(r1, Ordering::Relaxed); // B
308//! });
309//! thread::spawn(move || {
310//! let r2 = x.load(Ordering::Relaxed); // C
311//! y.store(42, Ordering::Relaxed); // D
312//! });
313//! ```
314//!
315//! Unfortunately, it is not possible for loom to completely model all the interleavings that
316//! relaxed memory ordering allows. This is because the relaxed memory ordering allows memory
317//! operations to be re-ordered within a single thread -- B can run *before* A -- which loom cannot
318//! emulate. The same restriction applies to certain reorderings that are possible across different
319//! atomic variables with other memory orderings, and means that there are certain concurrency bugs
320//! that loom cannot catch.
321//!
322//! ## Combinatorial Explosion with Many Threads
323//!
324//! The number of possible execution interleavings grows exponentially with the number of threads,
325//! as each possible execution of each additional thread must be taken into account for each
326//! possible execution of the current threads. Loom mitigates this to an extent by reducing the
327//! state space (see "Implementation" below) through _equivalent execution elimination_. For
328//! example, if two threads **read** from the same atomic variable, loom does not attempt another
329//! execution given that the order in which two threads read from the same atomic cannot impact the
330//! execution.
331//!
332//! However, even with equivalent execution elimination, the number of possible executions grows
333//! significantly with each new thread, to the point where checking becomes infeasible. Loom
334//! therefore specifically limits the number of threads it will model (see [`MAX_THREADS`]), and
335//! tailors its implementation to that limit.
336//!
337//! # Implementation
338//!
339//! Loom is an implementation of techniques described in [CDSChecker: Checking Concurrent Data
340//! Structures Written with C/C++ Atomics][cdschecker]. Please see the paper for much more detail
341//! on equivalent execution elimination and the other techniques loom uses to accurately model the
342//! [C11 memory model][spec].
343//!
344//! [spec]: https://en.cppreference.com/w/cpp/atomic/memory_order
345//! [spec-relaxed]: https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering
346//! [Spinlocks Considered Harmful]: https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html
347//! [cdschecker]: http://demsky.eecs.uci.edu/publications/c11modelcheck.pdf
348
349macro_rules! if_futures {
350 ($($t:tt)*) => {
351 cfg_if::cfg_if! {
352 if #[cfg(feature = "futures")] {
353 #[cfg_attr(docsrs, doc(cfg(feature = "futures")))]
354 $($t)*
355 }
356 }
357 }
358}
359
360macro_rules! dbg {
361 ($($t:tt)*) => {
362 $($t)*
363 };
364}
365
366#[macro_use]
367mod rt;
368
369pub use rt::{explore, skip_branch, stop_exploring};
370// Expose for documentation purposes.
371pub use rt::MAX_THREADS;
372
373pub mod alloc;
374pub mod cell;
375pub mod hint;
376pub mod lazy_static;
377pub mod model;
378pub mod sync;
379pub mod thread;
380
381#[doc(inline)]
382pub use crate::model::model;
383
384if_futures! {
385 pub mod future;
386}
387
388/// Mock version of `std::thread_local!`.
389// This is defined *after* all other code in `loom`, since we use
390// `scoped_thread_local!` internally, which uses the `std::thread_local!` macro
391// without namespacing it. Defining this after all other `loom` modules
392// prevents internal code from accidentally using the mock thread local instead
393// of the real one.
394#[macro_export]
395macro_rules! thread_local {
396 // empty (base case for the recursion)
397 () => {};
398
399 // process multiple declarations
400 ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => (
401 $crate::__thread_local_inner!($(#[$attr])* $vis $name, $t, $init);
402 $crate::thread_local!($($rest)*);
403 );
404
405 // handle a single declaration
406 ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => (
407 $crate::__thread_local_inner!($(#[$attr])* $vis $name, $t, $init);
408 );
409}
410
411/// Mock version of `lazy_static::lazy_static!`.
412#[macro_export]
413macro_rules! lazy_static {
414 ($(#[$attr:meta])* static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
415 // use `()` to explicitly forward the information about private items
416 $crate::__lazy_static_internal!($(#[$attr])* () static ref $N : $T = $e; $($t)*);
417 };
418 ($(#[$attr:meta])* pub static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
419 $crate::__lazy_static_internal!($(#[$attr])* (pub) static ref $N : $T = $e; $($t)*);
420 };
421 ($(#[$attr:meta])* pub ($($vis:tt)+) static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => {
422 $crate::__lazy_static_internal!($(#[$attr])* (pub ($($vis)+)) static ref $N : $T = $e; $($t)*);
423 };
424 () => ()
425}
426
427#[macro_export]
428#[doc(hidden)]
429macro_rules! __thread_local_inner {
430 ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $init:expr) => {
431 $(#[$attr])* $vis static $name: $crate::thread::LocalKey<$t> =
432 $crate::thread::LocalKey {
433 init: (|| { $init }) as fn() -> $t,
434 _p: std::marker::PhantomData,
435 };
436 };
437}
438
439#[macro_export]
440#[doc(hidden)]
441macro_rules! __lazy_static_internal {
442 // optional visibility restrictions are wrapped in `()` to allow for
443 // explicitly passing otherwise implicit information about private items
444 ($(#[$attr:meta])* ($($vis:tt)*) static ref $N:ident : $T:ty = $init:expr; $($t:tt)*) => {
445 #[allow(missing_copy_implementations)]
446 #[allow(non_camel_case_types)]
447 #[allow(dead_code)]
448 $(#[$attr])*
449 $($vis)* struct $N {__private_field: ()}
450 #[doc(hidden)]
451 $($vis)* static $N: $N = $N {__private_field: ()};
452 impl ::core::ops::Deref for $N {
453 type Target = $T;
454 // this and the two __ functions below should really also be #[track_caller]
455 fn deref(&self) -> &$T {
456 #[inline(always)]
457 fn __static_ref_initialize() -> $T { $init }
458
459 #[inline(always)]
460 fn __stability() -> &'static $T {
461 static LAZY: $crate::lazy_static::Lazy<$T> =
462 $crate::lazy_static::Lazy {
463 init: __static_ref_initialize,
464 _p: core::marker::PhantomData,
465 };
466 LAZY.get()
467 }
468 __stability()
469 }
470 }
471 $crate::lazy_static!($($t)*);
472 };
473 () => ()
474}