use std::cell::RefCell;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
#[derive(Clone, Default)]
struct OpInterrupt {
cancel: Option<Arc<AtomicBool>>,
deadline: Option<Instant>,
handler_deadline: Option<Instant>,
operation_budget: Option<Instant>,
}
thread_local! {
static CURRENT: RefCell<Option<OpInterrupt>> = const { RefCell::new(None) };
}
pub struct OpInterruptGuard {
#[allow(clippy::option_option)]
prev: Option<Option<OpInterrupt>>,
}
impl Drop for OpInterruptGuard {
fn drop(&mut self) {
if let Some(prev) = self.prev.take() {
CURRENT.with(|slot| *slot.borrow_mut() = prev);
}
}
}
pub fn install(cancel: Option<Arc<AtomicBool>>, deadline: Option<Instant>) -> OpInterruptGuard {
install_context(OpInterrupt {
cancel,
deadline,
handler_deadline: None,
operation_budget: None,
})
}
#[derive(Clone, Default)]
pub struct InterruptSources {
pub cancel: Option<Arc<AtomicBool>>,
pub scope_deadline: Option<Instant>,
pub handler_deadline: Option<Instant>,
}
impl InterruptSources {
pub fn earliest_deadline(&self) -> Option<Instant> {
match (self.scope_deadline, self.handler_deadline) {
(Some(scope), Some(handler)) => Some(scope.min(handler)),
(scope, handler) => scope.or(handler),
}
}
pub fn is_armed(&self) -> bool {
self.cancel.is_some() || self.scope_deadline.is_some() || self.handler_deadline.is_some()
}
pub fn install(self) -> OpInterruptGuard {
install_context(OpInterrupt {
cancel: self.cancel,
deadline: self.scope_deadline,
handler_deadline: self.handler_deadline,
operation_budget: None,
})
}
}
#[cfg(test)]
pub(crate) fn install_for_vm(
cancel: Option<Arc<AtomicBool>>,
scope_deadline: Option<Instant>,
handler_deadline: Option<Instant>,
) -> OpInterruptGuard {
InterruptSources {
cancel,
scope_deadline,
handler_deadline,
}
.install()
}
fn install_context(context: OpInterrupt) -> OpInterruptGuard {
let prev = CURRENT.with(|slot| slot.borrow_mut().replace(context));
OpInterruptGuard { prev: Some(prev) }
}
pub fn with_deadline(deadline: Instant) -> OpInterruptGuard {
let parent = CURRENT
.with(|slot| slot.borrow().clone())
.unwrap_or_default();
install_context(OpInterrupt {
deadline: Some(
parent
.deadline
.map_or(deadline, |earlier| earlier.min(deadline)),
),
..parent
})
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn with_operation_budget(budget: Instant) -> OpInterruptGuard {
let parent = CURRENT
.with(|slot| slot.borrow().clone())
.unwrap_or_default();
install_context(OpInterrupt {
operation_budget: Some(
parent
.operation_budget
.map_or(budget, |earlier| earlier.min(budget)),
),
..parent
})
}
pub fn installed() -> bool {
CURRENT.with(|slot| slot.borrow().is_some())
}
pub fn requested() -> bool {
requested_reason().is_some()
}
enum InterruptReason {
HandlerTimeout,
Cancelled,
Deadline,
OperationBudget,
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn operation_budget_expired() -> bool {
matches!(requested_reason(), Some(InterruptReason::OperationBudget))
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn requested_error() -> Option<crate::VmError> {
requested_reason().and_then(|reason| match reason {
InterruptReason::HandlerTimeout => Some(crate::Vm::interrupt_handler_timeout_error()),
InterruptReason::Cancelled => Some(crate::cancellation::cancelled_without_machine()),
InterruptReason::Deadline => Some(crate::Vm::deadline_exceeded_error()),
InterruptReason::OperationBudget => None,
})
}
fn requested_reason() -> Option<InterruptReason> {
CURRENT.with(|slot| {
let ctx = slot.borrow();
let ctx = ctx.as_ref()?;
let now = Instant::now();
if ctx.handler_deadline.is_some_and(|deadline| now >= deadline) {
return Some(InterruptReason::HandlerTimeout);
}
if ctx
.cancel
.as_ref()
.is_some_and(|token| token.load(Ordering::SeqCst))
{
return Some(InterruptReason::Cancelled);
}
if ctx.deadline.is_some_and(|deadline| now >= deadline) {
return Some(InterruptReason::Deadline);
}
if ctx.operation_budget.is_some_and(|budget| now >= budget) {
return Some(InterruptReason::OperationBudget);
}
None
})
}