use crate::rt::{self, Execution, Scheduler};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::{info, subscriber};
use tracing_subscriber::{fmt, EnvFilter};
const DEFAULT_MAX_THREADS: usize = 5;
const DEFAULT_MAX_BRANCHES: usize = 1_000;
#[derive(Debug)]
#[non_exhaustive] pub struct Builder {
pub max_threads: usize,
pub max_branches: usize,
pub max_permutations: Option<usize>,
pub max_duration: Option<Duration>,
pub preemption_bound: Option<usize>,
pub checkpoint_file: Option<PathBuf>,
pub checkpoint_interval: usize,
pub expect_explicit_explore: bool,
pub location: bool,
pub log: bool,
}
impl Builder {
pub fn new() -> Builder {
use std::env;
let checkpoint_interval = env::var("LOOM_CHECKPOINT_INTERVAL")
.map(|v| {
v.parse()
.expect("invalid value for `LOOM_CHECKPOINT_INTERVAL`")
})
.unwrap_or(20_000);
let max_branches = env::var("LOOM_MAX_BRANCHES")
.map(|v| v.parse().expect("invalid value for `LOOM_MAX_BRANCHES`"))
.unwrap_or(DEFAULT_MAX_BRANCHES);
let location = env::var("LOOM_LOCATION").is_ok();
let log = env::var("LOOM_LOG").is_ok();
let max_duration = env::var("LOOM_MAX_DURATION")
.map(|v| {
let secs = v.parse().expect("invalid value for `LOOM_MAX_DURATION`");
Duration::from_secs(secs)
})
.ok();
let max_permutations = env::var("LOOM_MAX_PERMUTATIONS")
.map(|v| {
v.parse()
.expect("invalid value for `LOOM_MAX_PERMUTATIONS`")
})
.ok();
let preemption_bound = env::var("LOOM_MAX_PREEMPTIONS")
.map(|v| v.parse().expect("invalid value for `LOOM_MAX_PREEMPTIONS`"))
.ok();
let checkpoint_file = env::var("LOOM_CHECKPOINT_FILE")
.map(|v| v.parse().expect("invalid value for `LOOM_CHECKPOINT_FILE`"))
.ok();
Builder {
max_threads: DEFAULT_MAX_THREADS,
max_branches,
max_duration,
max_permutations,
preemption_bound,
checkpoint_file,
checkpoint_interval,
expect_explicit_explore: false,
location,
log,
}
}
pub fn checkpoint_file(&mut self, file: &str) -> &mut Self {
self.checkpoint_file = Some(file.into());
self
}
pub fn check<F>(&self, f: F)
where
F: Fn() + Sync + Send + 'static,
{
let mut i = 1;
let mut _span = tracing::info_span!("iter", message = i).entered();
let mut execution = Execution::new(
self.max_threads,
self.max_branches,
self.preemption_bound,
!self.expect_explicit_explore,
);
let mut scheduler = Scheduler::new(self.max_threads);
if let Some(ref path) = self.checkpoint_file {
if path.exists() {
execution.path = checkpoint::load_execution_path(path);
execution.path.set_max_branches(self.max_branches);
}
}
execution.log = self.log;
execution.location = self.location;
let f = Arc::new(f);
let start = Instant::now();
loop {
if i % self.checkpoint_interval == 0 {
info!(parent: None, "");
info!(
parent: None,
" ================== Iteration {} ==================", i
);
info!(parent: None, "");
if let Some(ref path) = self.checkpoint_file {
checkpoint::store_execution_path(&execution.path, path);
}
if let Some(max_permutations) = self.max_permutations {
if i >= max_permutations {
return;
}
}
if let Some(max_duration) = self.max_duration {
if start.elapsed() >= max_duration {
return;
}
}
}
let f = f.clone();
scheduler.run(&mut execution, move || {
f();
let lazy_statics = rt::execution(|execution| execution.lazy_statics.drop());
drop(lazy_statics);
rt::thread_done();
});
execution.check_for_leaks();
i += 1;
_span = tracing::info_span!(parent: None, "iter", message = i).entered();
if let Some(next) = execution.step() {
execution = next;
} else {
info!(parent: None, "Completed in {} iterations", i - 1);
return;
}
}
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
pub fn model<F>(f: F)
where
F: Fn() + Sync + Send + 'static,
{
let subscriber = fmt::Subscriber::builder()
.with_env_filter(EnvFilter::from_env("LOOM_LOG"))
.with_test_writer()
.without_time()
.finish();
subscriber::with_default(subscriber, || {
Builder::new().check(f);
});
}
#[cfg(feature = "checkpoint")]
mod checkpoint {
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
pub(crate) fn load_execution_path(fs_path: &Path) -> crate::rt::Path {
let mut file = File::open(fs_path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
serde_json::from_str(&contents).unwrap()
}
pub(crate) fn store_execution_path(path: &crate::rt::Path, fs_path: &Path) {
let serialized = serde_json::to_string(path).unwrap();
let mut file = File::create(fs_path).unwrap();
file.write_all(serialized.as_bytes()).unwrap();
}
}
#[cfg(not(feature = "checkpoint"))]
mod checkpoint {
use std::path::Path;
pub(crate) fn load_execution_path(_fs_path: &Path) -> crate::rt::Path {
panic!("not compiled with `checkpoint` feature")
}
pub(crate) fn store_execution_path(_path: &crate::rt::Path, _fs_path: &Path) {
panic!("not compiled with `checkpoint` feature")
}
}