use std::{cell::RefCell, collections::BTreeMap, ffi::OsString};
use loom::sync::{
Mutex,
MutexGuard,
atomic::{AtomicUsize, Ordering},
};
use crate::env::{
ScopedEnv,
state::{EnvLockOps, ThreadStateInner},
};
type FakeEnv = BTreeMap<OsString, Option<OsString>>;
type Snapshot = Vec<(String, Option<String>)>;
loom::lazy_static! {
static ref LOOM_ENV_LOCK: Mutex<FakeEnv> = Mutex::new(BTreeMap::new());
static ref FAKE_ENV_MUTATIONS: AtomicUsize = AtomicUsize::new(0);
static ref USE_THREAD_LOCAL_SNAPSHOT: AtomicUsize = AtomicUsize::new(0);
}
struct LoomEnvLock;
impl EnvLockOps for LoomEnvLock {
type Guard = MutexGuard<'static, FakeEnv>;
fn lock_env_mutex() -> Self::Guard {
LOOM_ENV_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn ensure_lock_is_clean() {}
fn var_os(guard: &Self::Guard, key: &OsString) -> Option<OsString> {
guard.get(key).cloned().flatten()
}
fn set_var(guard: &mut Self::Guard, key: &OsString, value: OsString) {
FAKE_ENV_MUTATIONS.fetch_add(1, Ordering::SeqCst);
guard.insert(key.clone(), Some(value));
record_thread_local_snapshot(guard);
}
fn remove_var(guard: &mut Self::Guard, key: &OsString) {
FAKE_ENV_MUTATIONS.fetch_add(1, Ordering::SeqCst);
guard.insert(key.clone(), None);
record_thread_local_snapshot(guard);
}
}
loom::thread_local! {
static LOOM_THREAD_STATE: RefCell<ThreadStateInner<LoomEnvLock>> =
RefCell::new(ThreadStateInner::new());
static LAST_FAKE_ENV_SNAPSHOT: RefCell<Snapshot> = RefCell::new(Vec::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);
});
}
pub(super) 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)
}
pub(super) fn vars(input: &[(&str, Option<&str>)]) -> Vec<(String, Option<String>)> {
input
.iter()
.map(|(key, value)| ((*key).to_owned(), value.map(str::to_owned)))
.collect()
}
fn snapshot_from_map(map: &FakeEnv) -> Snapshot {
map.iter()
.map(|(key, value)| {
(
key.to_string_lossy().into_owned(),
value
.as_ref()
.map(|stored| stored.to_string_lossy().into_owned()),
)
})
.collect()
}
fn record_thread_local_snapshot(map: &FakeEnv) {
let snapshot = snapshot_from_map(map);
LAST_FAKE_ENV_SNAPSHOT.with(|cell| *cell.borrow_mut() = snapshot);
}
fn snapshot_fake_env() -> Snapshot {
if USE_THREAD_LOCAL_SNAPSHOT.load(Ordering::SeqCst) != 0 {
return LAST_FAKE_ENV_SNAPSHOT.with(|cell| cell.borrow().clone());
}
let guard = LoomEnvLock::lock_env_mutex();
snapshot_from_map(&guard)
}
fn snapshot_current_scope() -> Snapshot {
LOOM_THREAD_STATE.with(|cell| {
cell.borrow()
.with_lock_guard(|guard| snapshot_from_map(guard))
})
}
pub(super) fn current_thread_depth() -> usize {
LOOM_THREAD_STATE.with(|cell| cell.borrow().depth())
}
pub(super) fn current_thread_state_is_reset() -> bool {
LOOM_THREAD_STATE.with(|cell| {
let state = cell.borrow();
state.depth() == 0 && state.is_stack_empty() && !state.has_lock()
})
}
pub(super) fn seed_fake_env(input: &[(&str, Option<&str>)]) {
let mut guard = LoomEnvLock::lock_env_mutex();
guard.clear();
for (key, value) in input {
guard.insert(OsString::from(key), value.map(OsString::from));
}
record_thread_local_snapshot(&guard);
FAKE_ENV_MUTATIONS.store(0, Ordering::SeqCst);
USE_THREAD_LOCAL_SNAPSHOT.store(0, Ordering::SeqCst);
}
pub(super) fn assert_fake_env(expected: &[(&str, Option<&str>)]) {
assert_eq!(snapshot_fake_env(), vars(expected));
}
pub(super) fn assert_current_scope_env(expected: &[(&str, Option<&str>)]) {
assert_eq!(snapshot_current_scope(), vars(expected));
}
pub(super) fn fake_env_mutations() -> usize { FAKE_ENV_MUTATIONS.load(Ordering::SeqCst) }
pub(super) fn use_thread_local_snapshot() { USE_THREAD_LOCAL_SNAPSHOT.store(1, Ordering::SeqCst); }
pub(super) 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);
}