use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
thread_local! {
static ACTIVE_EXECUTION_SCOPE_STACK: RefCell<Vec<Arc<str>>> = const { RefCell::new(Vec::new()) };
}
static SCOPE_COUNTER: AtomicU64 = AtomicU64::new(1);
pub fn mint_execution_scope() -> Arc<str> {
let n = SCOPE_COUNTER.fetch_add(1, Ordering::SeqCst);
Arc::from(format!("hxs-{:x}-{n}", std::process::id()).as_str())
}
#[must_use = "dropping the guard immediately pops the execution scope"]
pub struct ExecutionScopeGuard {
_private: (),
}
impl Drop for ExecutionScopeGuard {
fn drop(&mut self) {
ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| {
stack.borrow_mut().pop();
});
}
}
pub fn enter_execution_scope(scope: Arc<str>) -> ExecutionScopeGuard {
ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| stack.borrow_mut().push(scope));
ExecutionScopeGuard { _private: () }
}
pub fn current_execution_scope() -> Option<Arc<str>> {
ACTIVE_EXECUTION_SCOPE_STACK.with(|stack| stack.borrow().last().cloned())
}
pub(crate) fn swap_execution_scope_stack(replacement: Vec<Arc<str>>) -> Vec<Arc<str>> {
ACTIVE_EXECUTION_SCOPE_STACK
.with(|stack| std::mem::replace(&mut *stack.borrow_mut(), replacement))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_returns_none_when_nothing_pushed() {
std::thread::spawn(|| {
assert_eq!(current_execution_scope(), None);
})
.join()
.unwrap();
}
#[test]
fn guard_pops_on_drop_and_inner_shadows_outer() {
std::thread::spawn(|| {
let outer = mint_execution_scope();
let inner = mint_execution_scope();
assert_ne!(outer, inner);
let _o = enter_execution_scope(outer.clone());
assert_eq!(current_execution_scope().as_deref(), Some(&*outer));
{
let _i = enter_execution_scope(inner.clone());
assert_eq!(current_execution_scope().as_deref(), Some(&*inner));
}
assert_eq!(current_execution_scope().as_deref(), Some(&*outer));
})
.join()
.unwrap();
}
}