use std::any::TypeId;
use std::cell::RefCell;
use std::fmt;
use std::panic;
use std::rc::Rc;
use std::sync::Arc;
use data::{LocalMap, Opaque};
lazy_static! {
static ref DEFAULT_ACTIVE_CONTEXT: Arc<ExecutionContextImpl> =
Arc::new(ExecutionContextImpl {
flow_propagation: FlowPropagation::Active,
locals: Default::default(),
});
static ref DEFAULT_DISABLED_CONTEXT: Arc<ExecutionContextImpl> =
Arc::new(ExecutionContextImpl {
flow_propagation: FlowPropagation::Disabled,
locals: Default::default(),
});
}
thread_local! {
static CURRENT_CONTEXT: RefCell<Arc<ExecutionContextImpl>> =
RefCell::new(DEFAULT_ACTIVE_CONTEXT.clone());
}
#[derive(PartialEq, Debug, Copy, Clone)]
enum FlowPropagation {
Active,
Suppressed,
Disabled,
}
#[derive(Clone)]
pub(crate) struct ExecutionContextImpl {
flow_propagation: FlowPropagation,
locals: LocalMap,
}
impl ExecutionContextImpl {
fn into_arc(self) -> Arc<ExecutionContextImpl> {
match (self.flow_propagation, self.locals.is_empty()) {
(FlowPropagation::Active, true) => DEFAULT_ACTIVE_CONTEXT.clone(),
(FlowPropagation::Disabled, true) => DEFAULT_DISABLED_CONTEXT.clone(),
_ => Arc::new(self),
}
}
fn has_active_flow(&self) -> bool {
self.flow_propagation == FlowPropagation::Active
}
}
#[derive(Clone)]
pub struct ExecutionContext {
inner: Arc<ExecutionContextImpl>,
}
impl fmt::Debug for ExecutionContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ExecutionContext").finish()
}
}
#[derive(Clone)]
pub struct FlowGuard(Rc<FlowPropagation>);
impl ExecutionContext {
pub fn capture() -> ExecutionContext {
ExecutionContext {
inner: CURRENT_CONTEXT.with(|ctx| {
let current = ctx.borrow();
match current.flow_propagation {
FlowPropagation::Active => current.clone(),
FlowPropagation::Suppressed => DEFAULT_ACTIVE_CONTEXT.clone(),
FlowPropagation::Disabled => DEFAULT_DISABLED_CONTEXT.clone(),
}
}),
}
}
pub fn suppress_flow() -> FlowGuard {
ExecutionContext::modify_context(|ctx| {
let old = ctx.flow_propagation;
ctx.flow_propagation = FlowPropagation::Suppressed;
FlowGuard(Rc::new(old))
})
}
pub fn disable_flow() -> FlowGuard {
ExecutionContext::modify_context(|ctx| {
let old = ctx.flow_propagation;
ctx.flow_propagation = FlowPropagation::Disabled;
FlowGuard(Rc::new(old))
})
}
pub fn restore_flow() {
ExecutionContext::modify_context(|ctx| {
ctx.flow_propagation = FlowPropagation::Active;
})
}
pub fn is_flow_suppressed() -> bool {
CURRENT_CONTEXT.with(|ctx| !ctx.borrow().has_active_flow())
}
pub fn run<F: FnOnce() -> R, R>(&self, f: F) -> R {
if let Some(old_ctx) = CURRENT_CONTEXT.with(|ctx| {
let mut ptr = ctx.borrow_mut();
if &**ptr as *const _ == &*self.inner as *const _ {
None
} else {
let old = (*ptr).clone();
*ptr = self.inner.clone();
Some(old)
}
}) {
let rv = panic::catch_unwind(panic::AssertUnwindSafe(|| f()));
CURRENT_CONTEXT.with(|ctx| *ctx.borrow_mut() = old_ctx);
match rv {
Err(err) => panic::resume_unwind(err),
Ok(rv) => rv,
}
} else {
f()
}
}
fn modify_context<F: FnOnce(&mut ExecutionContextImpl) -> R, R>(f: F) -> R {
CURRENT_CONTEXT.with(|ctx| {
let mut ptr = ctx.borrow_mut();
let mut new = ExecutionContextImpl {
flow_propagation: ptr.flow_propagation,
locals: ptr.locals.clone(),
};
let rv = f(&mut new);
*ptr = new.into_arc();
rv
})
}
pub(crate) fn set_local_value(key: TypeId, new_value: Arc<Box<Opaque>>) {
let new_locals = CURRENT_CONTEXT.with(|ctx| ctx.borrow().locals.insert(key, new_value));
ExecutionContext::modify_context(|ctx| {
ctx.locals = new_locals;
});
}
pub(crate) fn get_local_value(key: TypeId) -> Option<Arc<Box<Opaque>>> {
CURRENT_CONTEXT.with(|ctx| ctx.borrow().locals.get(&key))
}
}
impl Drop for FlowGuard {
fn drop(&mut self) {
if let Some(old) = Rc::get_mut(&mut self.0) {
ExecutionContext::modify_context(|ctx| ctx.flow_propagation = *old);
}
}
}