[go: up one dir, main page]

loom/
model.rs

1//! Model concurrent programs.
2
3use crate::rt::{self, Execution, Scheduler};
4use std::path::PathBuf;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7
8use tracing::{info, subscriber};
9use tracing_subscriber::{fmt, EnvFilter};
10
11const DEFAULT_MAX_THREADS: usize = 5;
12const DEFAULT_MAX_BRANCHES: usize = 1_000;
13
14/// Configure a model
15#[derive(Debug)]
16#[non_exhaustive] // Support adding more fields in the future
17pub struct Builder {
18    /// Max number of threads to check as part of the execution.
19    ///
20    /// This should be set as low as possible and must be less than
21    /// [`MAX_THREADS`](crate::MAX_THREADS).
22    pub max_threads: usize,
23
24    /// Maximum number of thread switches per permutation.
25    ///
26    /// Defaults to `LOOM_MAX_BRANCHES` environment variable.
27    pub max_branches: usize,
28
29    /// Maximum number of permutations to explore.
30    ///
31    /// Defaults to `LOOM_MAX_PERMUTATIONS` environment variable.
32    pub max_permutations: Option<usize>,
33
34    /// Maximum amount of time to spend on checking
35    ///
36    /// Defaults to `LOOM_MAX_DURATION` environment variable.
37    pub max_duration: Option<Duration>,
38
39    /// Maximum number of thread preemptions to explore
40    ///
41    /// Defaults to `LOOM_MAX_PREEMPTIONS` environment variable.
42    pub preemption_bound: Option<usize>,
43
44    /// When doing an exhaustive check, uses the file to store and load the
45    /// check progress
46    ///
47    /// Defaults to `LOOM_CHECKPOINT_FILE` environment variable.
48    pub checkpoint_file: Option<PathBuf>,
49
50    /// How often to write the checkpoint file
51    ///
52    /// Defaults to `LOOM_CHECKPOINT_INTERVAL` environment variable.
53    pub checkpoint_interval: usize,
54
55    /// When `true` loom won't start state exploration until `explore_state` is
56    /// called.
57    pub expect_explicit_explore: bool,
58
59    /// When `true`, locations are captured on each loom operation.
60    ///
61    /// Note that is is **very** expensive. It is recommended to first isolate a
62    /// failing iteration using `LOOM_CHECKPOINT_FILE`, then enable location
63    /// tracking.
64    ///
65    /// Defaults to `LOOM_LOCATION` environment variable.
66    pub location: bool,
67
68    /// Log execution output to stdout.
69    ///
70    /// Defaults to existence of `LOOM_LOG` environment variable.
71    pub log: bool,
72}
73
74impl Builder {
75    /// Create a new `Builder` instance with default values.
76    pub fn new() -> Builder {
77        use std::env;
78
79        let checkpoint_interval = env::var("LOOM_CHECKPOINT_INTERVAL")
80            .map(|v| {
81                v.parse()
82                    .expect("invalid value for `LOOM_CHECKPOINT_INTERVAL`")
83            })
84            .unwrap_or(20_000);
85
86        let max_branches = env::var("LOOM_MAX_BRANCHES")
87            .map(|v| v.parse().expect("invalid value for `LOOM_MAX_BRANCHES`"))
88            .unwrap_or(DEFAULT_MAX_BRANCHES);
89
90        let location = env::var("LOOM_LOCATION").is_ok();
91
92        let log = env::var("LOOM_LOG").is_ok();
93
94        let max_duration = env::var("LOOM_MAX_DURATION")
95            .map(|v| {
96                let secs = v.parse().expect("invalid value for `LOOM_MAX_DURATION`");
97                Duration::from_secs(secs)
98            })
99            .ok();
100
101        let max_permutations = env::var("LOOM_MAX_PERMUTATIONS")
102            .map(|v| {
103                v.parse()
104                    .expect("invalid value for `LOOM_MAX_PERMUTATIONS`")
105            })
106            .ok();
107
108        let preemption_bound = env::var("LOOM_MAX_PREEMPTIONS")
109            .map(|v| v.parse().expect("invalid value for `LOOM_MAX_PREEMPTIONS`"))
110            .ok();
111
112        let checkpoint_file = env::var("LOOM_CHECKPOINT_FILE")
113            .map(|v| v.parse().expect("invalid value for `LOOM_CHECKPOINT_FILE`"))
114            .ok();
115
116        Builder {
117            max_threads: DEFAULT_MAX_THREADS,
118            max_branches,
119            max_duration,
120            max_permutations,
121            preemption_bound,
122            checkpoint_file,
123            checkpoint_interval,
124            expect_explicit_explore: false,
125            location,
126            log,
127        }
128    }
129
130    /// Set the checkpoint file.
131    pub fn checkpoint_file(&mut self, file: &str) -> &mut Self {
132        self.checkpoint_file = Some(file.into());
133        self
134    }
135
136    /// Check the provided model.
137    pub fn check<F>(&self, f: F)
138    where
139        F: Fn() + Sync + Send + 'static,
140    {
141        let mut i = 1;
142        let mut _span = tracing::info_span!("iter", message = i).entered();
143
144        let mut execution = Execution::new(
145            self.max_threads,
146            self.max_branches,
147            self.preemption_bound,
148            !self.expect_explicit_explore,
149        );
150        let mut scheduler = Scheduler::new(self.max_threads);
151
152        if let Some(ref path) = self.checkpoint_file {
153            if path.exists() {
154                execution.path = checkpoint::load_execution_path(path);
155                execution.path.set_max_branches(self.max_branches);
156            }
157        }
158
159        execution.log = self.log;
160        execution.location = self.location;
161
162        let f = Arc::new(f);
163
164        let start = Instant::now();
165        loop {
166            if i % self.checkpoint_interval == 0 {
167                info!(parent: None, "");
168                info!(
169                    parent: None,
170                    " ================== Iteration {} ==================", i
171                );
172                info!(parent: None, "");
173
174                if let Some(ref path) = self.checkpoint_file {
175                    checkpoint::store_execution_path(&execution.path, path);
176                }
177
178                if let Some(max_permutations) = self.max_permutations {
179                    if i >= max_permutations {
180                        return;
181                    }
182                }
183
184                if let Some(max_duration) = self.max_duration {
185                    if start.elapsed() >= max_duration {
186                        return;
187                    }
188                }
189            }
190
191            let f = f.clone();
192
193            scheduler.run(&mut execution, move || {
194                f();
195
196                let lazy_statics = rt::execution(|execution| execution.lazy_statics.drop());
197
198                // drop outside of execution
199                drop(lazy_statics);
200
201                rt::thread_done();
202            });
203
204            execution.check_for_leaks();
205
206            i += 1;
207
208            // Create the next iteration's `tracing` span before trying to step to the next
209            // execution, as the `Execution` will capture the current span when
210            // it's reset.
211            _span = tracing::info_span!(parent: None, "iter", message = i).entered();
212            if let Some(next) = execution.step() {
213                execution = next;
214            } else {
215                info!(parent: None, "Completed in {} iterations", i - 1);
216                return;
217            }
218        }
219    }
220}
221
222impl Default for Builder {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228/// Run all concurrent permutations of the provided closure.
229///
230/// Uses a default [`Builder`] which can be affected by environment variables.
231pub fn model<F>(f: F)
232where
233    F: Fn() + Sync + Send + 'static,
234{
235    let subscriber = fmt::Subscriber::builder()
236        .with_env_filter(EnvFilter::from_env("LOOM_LOG"))
237        .with_test_writer()
238        .without_time()
239        .finish();
240
241    subscriber::with_default(subscriber, || {
242        Builder::new().check(f);
243    });
244}
245
246#[cfg(feature = "checkpoint")]
247mod checkpoint {
248    use std::fs::File;
249    use std::io::prelude::*;
250    use std::path::Path;
251
252    pub(crate) fn load_execution_path(fs_path: &Path) -> crate::rt::Path {
253        let mut file = File::open(fs_path).unwrap();
254        let mut contents = String::new();
255        file.read_to_string(&mut contents).unwrap();
256        serde_json::from_str(&contents).unwrap()
257    }
258
259    pub(crate) fn store_execution_path(path: &crate::rt::Path, fs_path: &Path) {
260        let serialized = serde_json::to_string(path).unwrap();
261
262        let mut file = File::create(fs_path).unwrap();
263        file.write_all(serialized.as_bytes()).unwrap();
264    }
265}
266
267#[cfg(not(feature = "checkpoint"))]
268mod checkpoint {
269    use std::path::Path;
270
271    pub(crate) fn load_execution_path(_fs_path: &Path) -> crate::rt::Path {
272        panic!("not compiled with `checkpoint` feature")
273    }
274
275    pub(crate) fn store_execution_path(_path: &crate::rt::Path, _fs_path: &Path) {
276        panic!("not compiled with `checkpoint` feature")
277    }
278}