Skip to content

Commit 88e4225

Browse files
authored
Overhaul internals to support faster execution (#111)
The internals are overhauled to avoid as much allocation as possible. This is done by leveraging the object store as much as possible and hard coding settings such as max threads. Additionally, the history tracked by atomic cells is limited. These changes enable tracking state without the use of vecs and other allocated collections. The atomic abstraction now has improved coherence tracking. The "backtrace" feature is removed in favor of the nightly `Location` API. To enable this, a nightly Rust must be used and `--cfg loom_nightly` specified at compile time. This patch comes with a number of breaking changes, including: * `CausalCell` deferred checks is removed as doing so is undefined behavior. * `CausalCell` is renamed to `UnsafeCell`.
1 parent 977067f commit 88e4225

40 files changed

+2098
-1667
lines changed

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "loom"
99
# - Cargo.toml
1010
# - README.md
1111
# - Create git tag
12-
version = "0.2.15"
12+
version = "0.3.0"
1313
edition = "2018"
1414
license = "MIT"
1515
authors = ["Carl Lerche <[email protected]>"]
@@ -38,4 +38,3 @@ serde = { version = "1.0.92", features = ["derive"], optional = true }
3838
serde_json = { version = "1.0.33", optional = true }
3939

4040
futures-util = { version = "0.3.0", optional = true }
41-
backtrace = { version = "0.3.44", optional = true }

README.md

Lines changed: 158 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1323
To 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

Comments
 (0)