use super::ScopedEnv;
use super::state::{EnvLockOps, ThreadStateInner};
use loom::sync::Arc;
use loom::sync::atomic::{AtomicUsize, Ordering};
use loom::thread;
use std::cell::RefCell;
use std::ffi::OsString;
loom::lazy_static! {
static ref LOOM_ENV_LOCK: loom::sync::Mutex<()> = loom::sync::Mutex::new(());
}
struct LoomEnvLock;
impl EnvLockOps for LoomEnvLock {
type Guard = loom::sync::MutexGuard<'static, ()>;
fn lock_env_mutex() -> Self::Guard {
LOOM_ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn ensure_lock_is_clean() {}
}
loom::thread_local! {
static LOOM_THREAD_STATE: RefCell<ThreadStateInner<LoomEnvLock>> =
RefCell::new(ThreadStateInner::new());
}
fn enter_scope_loom(vars: Vec<(OsString, Option<OsString>)>) -> usize {
LOOM_THREAD_STATE.with(|cell| {
let mut state = cell.borrow_mut();
state.enter_scope(vars)
})
}
fn exit_scope_loom(index: usize) {
LOOM_THREAD_STATE.with(|cell| {
let mut state = cell.borrow_mut();
state.exit_scope(index);
});
}
fn apply_loom(vars: &[(String, Option<String>)]) -> ScopedEnv {
let owned: Vec<(OsString, Option<OsString>)> = vars
.iter()
.map(|(key, value)| (OsString::from(key), value.as_ref().map(OsString::from)))
.collect();
ScopedEnv::apply_owned_with_state(owned, enter_scope_loom, exit_scope_loom)
}
fn run_loom_model<F>(f: F)
where
F: Fn() + Send + Sync + 'static,
{
let mut builder = loom::model::Builder::new();
builder.max_threads = 3;
builder.max_branches = 64;
builder.preemption_bound = Some(3);
builder.check(f);
}
#[test]
#[ignore = "requires Loom model checking"]
fn scoped_env_serialises_concurrent_scopes() {
run_loom_model(|| {
let active_counter = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for _ in 0..2 {
let active_clone = Arc::clone(&active_counter);
handles.push(thread::spawn(move || {
let empty: &[(String, Option<String>)] = &[];
let _guard = apply_loom(empty);
let previous = active_clone.fetch_add(1, Ordering::SeqCst);
assert_eq!(
previous, 0,
"ScopedEnv must serialise concurrent environment scopes"
);
let current = active_clone.fetch_sub(1, Ordering::SeqCst);
assert_eq!(current, 1, "ScopedEnv must release the scope cleanly");
}));
}
for handle in handles {
handle.join().expect("thread should join cleanly");
}
assert_eq!(active_counter.load(Ordering::SeqCst), 0);
});
}
#[test]
#[ignore = "requires Loom model checking"]
fn scoped_env_allows_reentrant_scopes_on_one_thread() {
run_loom_model(|| {
let active_counter = Arc::new(AtomicUsize::new(0));
let active_thread = Arc::clone(&active_counter);
let handle = thread::spawn(move || {
let empty: &[(String, Option<String>)] = &[];
let outer = apply_loom(empty);
let inner = apply_loom(empty);
let previous = active_thread.fetch_add(1, Ordering::SeqCst);
assert_eq!(previous, 0, "outer scope should hold the lock");
let current = active_thread.fetch_sub(1, Ordering::SeqCst);
assert_eq!(current, 1, "inner scope should not release the lock");
drop(inner);
drop(outer);
});
handle.join().expect("thread should join cleanly");
assert_eq!(active_counter.load(Ordering::SeqCst), 0);
});
}