@@ -8,6 +8,16 @@ model. It uses state reduction techniques to avoid combinatorial explosion.
88
99[ Documentation] ( https://docs.rs/loom )
1010
11+ ## Overview
12+
13+ Loom is an implementation of techniques described in [ CDSChecker: Checking
14+ Concurrent Data Structures Written with C/C++ Atomics] [ cdschecker ] . It is a
15+ library for writing unit tests where all possible thread interleavings are
16+ checked. It also is check all possible atomic cell behaviors and validate
17+ correct access to ` UnsafeCell ` .
18+
19+ [ cdschecker ] : http://demsky.eecs.uci.edu/publications/c11modelcheck.pdf
20+
1121## Getting started
1222
1323To use ` loom ` , first add this to your ` Cargo.toml ` :
@@ -50,32 +60,164 @@ fn buggy_concurrent_inc() {
5060}
5161```
5262
53- ## Overview
63+ ## Usage
5464
55- Loom is an implementation of techniques described in [ CDSChecker: Checking
56- Concurrent Data Structures Written with C/C++ Atomics] [ cdschecker ] .
65+ Currently, using Loom comes with a bit of friction. Libraries must be written to
66+ be Loom-aware, and doing so comes with some boilerplate. Over time, the friction
67+ will be removed.
5768
58- [ cdschecker ] : http://demsky.eecs.uci.edu/publications/c11modelcheck.pdf
69+ The following provides a brief overview of how to usage Loom as part of the
70+ testing workflow of a Rust crate.
71+
72+ ### Structuring tests
73+
74+ When running Loom tests, the Loom concurrency types must be used in place of the
75+ ` std ` types. However, when ** not** running loom tests, the ` std ` should be used.
76+ This means that library code will need to use conditional compilation to decide
77+ which types to use.
78+
79+ It is recommended to use a ` loom ` cfg flag to signal using the Loom types. Then,
80+ when running Loom tests, include ` RUSTFLAGS="--cfg loom" ` as part of the
81+ command.
82+
83+ One strategy is to create module in your crate named ` sync ` or any other name of
84+ your choosing. In this module, list out the types that need to be toggled
85+ between Loom and ` std ` :
86+
87+ ``` rust
88+ #[cfg(loom)]
89+ pub (crate ) use loom :: sync :: atomic :: AtomicUsize ;
90+
91+ #[cfg(not(loom))]
92+ pub (crate ) use std :: sync :: atomic :: AtomicUsize ;
93+ ```
94+
95+ Then, elsewhere in the library:
96+
97+ ``` rust
98+ use crate :: sync :: AtomicUsize ;
99+ ```
100+
101+ ### Handling Loom API differences.
102+
103+ If your library must use Loom APIs that differ from ` std ` types, then the
104+ library will be required to implement those APIs for ` std ` . For example, for
105+ ` UnsafeCell ` , in the library's source, add the following:
106+
107+ ``` rust
108+ #![cfg(not(loom))]
109+
110+ #[derive(Debug )]
111+ pub (crate ) struct UnsafeCell <T >(std :: cell :: UnsafeCell <T >);
112+
113+ impl <T > UnsafeCell <T > {
114+ pub (crate ) fn new (data : T ) -> UnsafeCell <T > {
115+ UnsafeCell (std :: cell :: UnsafeCell :: new (data ))
116+ }
117+
118+ pub (crate ) fn with <R >(& self , f : impl FnOnce (* const T ) -> R ) -> R {
119+ f (self . 0. get ())
120+ }
121+
122+ pub (crate ) fn with_mut <R >(& self , f : impl FnOnce (* mut T ) -> R ) -> R {
123+ f (self . 0. get ())
124+ }
125+ }
126+ ```
127+
128+ ### Running Loom tests
129+
130+ Loom tests must be run separately, with ` RUSTFLAGS="--cfg loom" ` specified. For
131+ example, if the library includes a test file: ` tests/loom_my_struct.rs ` that
132+ includes tests with ` loom::model ` , then run the following command:
59133
60- ### Thread ordering
134+ ```
135+ RUSTFLAGS="--cfg loom" cargo test --test loom_my_struct
136+ ```
61137
62- TODO
138+ #### Handling large models
63139
64- ### Atomics
140+ By default, Loom runs an ** exhaustive** model. All possible execution paths are
141+ checked. Loom's state reduction algorithms significantly reduce the state space
142+ that must be explored, however, complex models can still take ** significant**
143+ time. There are two strategies to deal with this.
65144
66- In the C++11 memory model, stores to a single atomic cell are totally ordered.
67- This is the modification order. Loom permutes the modification order of each
68- atomic cell within the bounds of the coherence rules.
145+ The first strategy is to run loom tests with ` --release ` . This will greatly
146+ speed up execution time.
69147
148+ The second strategy is to ** not** run an exhaustive check. Loom is able to set a
149+ thread pre-emption bound. This means that Loom will check all possible
150+ executions that include ** at most** ` n ` thread pre-emptions. In practice,
151+ setting the thread pre-emption bound to 2 or 3 is enough to catch most bugs.
70152
71- ## Limitations
153+ To set the thread pre-emption bound, set the ` LOOM_MAX_PREEMPTIONS ` environment
154+ variable when running tests. For example:
72155
73- While already very useful, loom is in its early stages and has a number of
74- limitations.
156+ ```
157+ LOOM_MAX_PREEMPTIONS=3 RUSTFLAGS="--cfg loom" cargo test --test loom_my_struct
158+ ```
75159
76- * Execution is slow (#5 ).
77- * The full C11 memory model is not implemented (#6 ).
78- * No fence support (#7 ).
160+ ### Debugging failed tests
161+
162+ Loom's deterministic execution helps with debugging. The specific chain of
163+ events leading to a test failure can be isolated.
164+
165+ When a loom test fails, the first step is to isolate the exact execution path
166+ that resulted in the failure. To do this, Loom is able to output the execution
167+ path to a file. Two environment variables are useful for this process:
168+
169+ - ` LOOM_CHECKPOINT_FILE `
170+ - ` LOOM_CHECKPOINT_INTERVAL `
171+
172+ The first specifies the file to write to and read from. The second specifies how
173+ often to write to the file. If the execution fails on the 10,000,000th
174+ permutation, it is faster to write to a file every 10,0000 iterations instead of
175+ every single one.
176+
177+ To isolate the exact failing path, run the following commands:
178+
179+ ```
180+ LOOM_CHECKPOINT_FILE=my_test.json [other env vars] \
181+ cargo test --test loom_my_struct [failing test]
182+ ```
183+
184+ Then, the following:
185+
186+ ```
187+ LOOM_CHECKPOINT_INTERVAL=1 LOOM_CHECKPOINT_FILE=my_test.json [other env vars] \
188+ cargo test --test loom_my_struct [failing test]
189+ ```
190+
191+ The test should fail on the first permutation, effectively isolating the failure
192+ scenario.
193+
194+ The next step is to enable additional log output. Again, there are some
195+ environment variables for this:
196+
197+ - ` LOOM_LOG `
198+ - ` LOOM_LOCATION ` (nightly Rust only)
199+
200+ The first environment variable, ` LOOM_LOG ` , outputs a marker on every thread switch.
201+ This helps with tracing the exact steps in a threaded environment that results
202+ in the test failure.
203+
204+ The second, ` LOOM_LOCATION ` , enables location tracking. This includes additional
205+ information in panic messages that helps identify which specific field resulted
206+ in the error. To enable this, ` RUSTFLAGS="--cfg loom_nightly" ` must also be
207+ specified.
208+
209+ Put together, the command becomes (yes, we know this is not great... but it
210+ works):
211+
212+ ```
213+ LOOM_LOG=1 \
214+ LOOM_LOCATION=1 \
215+ LOOM_CHECKPOINT_INTERVAL=1 \
216+ LOOM_CHECKPOINT_FILE=my_test.json \
217+ RUSTFLAGS="--cfg loom --cfg loom_nightly" \
218+ [other env vars] \
219+ cargo test --test loom_my_struct [failing test]
220+ ```
79221
80222## License
81223
0 commit comments