1use 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#[derive(Debug)]
16#[non_exhaustive] pub struct Builder {
18 pub max_threads: usize,
23
24 pub max_branches: usize,
28
29 pub max_permutations: Option<usize>,
33
34 pub max_duration: Option<Duration>,
38
39 pub preemption_bound: Option<usize>,
43
44 pub checkpoint_file: Option<PathBuf>,
49
50 pub checkpoint_interval: usize,
54
55 pub expect_explicit_explore: bool,
58
59 pub location: bool,
67
68 pub log: bool,
72}
73
74impl Builder {
75 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 pub fn checkpoint_file(&mut self, file: &str) -> &mut Self {
132 self.checkpoint_file = Some(file.into());
133 self
134 }
135
136 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(lazy_statics);
200
201 rt::thread_done();
202 });
203
204 execution.check_for_leaks();
205
206 i += 1;
207
208 _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
228pub 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}