use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Instant;
pub(crate) struct ExecutionDeadlineState {
origin: Instant,
deadline_offset: AtomicU64,
pause_depth: AtomicU64,
pause_window: parking_lot::Mutex<Option<PauseWindow>>,
changed: tokio::sync::Notify,
abandoned: AtomicBool,
}
impl ExecutionDeadlineState {
pub(crate) fn new(origin: Instant, deadline: Option<Instant>) -> Arc<Self> {
Arc::new(Self {
origin,
deadline_offset: AtomicU64::new(Self::encode(origin, deadline)),
pause_depth: AtomicU64::new(0),
pause_window: parking_lot::Mutex::new(None),
changed: tokio::sync::Notify::new(),
abandoned: AtomicBool::new(false),
})
}
#[inline]
pub(crate) fn is_active(&self) -> bool {
self.deadline_offset.load(Ordering::Acquire) != 0
}
#[inline]
pub(crate) fn is_abandoned(&self) -> bool {
self.abandoned.load(Ordering::Acquire)
}
pub(crate) fn fork(&self) -> Arc<Self> {
let state = Self::new(self.origin, self.raw_current());
state
.abandoned
.store(self.is_abandoned(), Ordering::Release);
state
}
pub(crate) fn current(&self) -> Option<Instant> {
if self.pause_depth.load(Ordering::Acquire) != 0 {
return None;
}
self.raw_current()
}
fn raw_current(&self) -> Option<Instant> {
let encoded = self.deadline_offset.load(Ordering::Acquire);
(encoded != 0)
.then(|| self.origin + std::time::Duration::from_nanos(encoded.saturating_sub(1)))
}
#[cfg(test)]
pub(crate) fn encoded_offset_for_test(&self) -> u64 {
self.deadline_offset.load(Ordering::Acquire)
}
pub(crate) fn pause(
self: &Arc<Self>,
clock: Arc<dyn harn_clock::Clock>,
) -> Option<ExecutionDeadlinePauseGuard> {
if !self.is_active() {
return None;
}
let mut window = self.pause_window.lock();
let previous = self.pause_depth.fetch_add(1, Ordering::AcqRel);
if previous == 0 {
*window = Some(PauseWindow {
started_ms: clock.monotonic_ms(),
clock,
});
self.changed.notify_one();
}
drop(window);
Some(ExecutionDeadlinePauseGuard {
state: Arc::clone(self),
})
}
pub(crate) fn changed(&self) -> tokio::sync::futures::Notified<'_> {
self.changed.notified()
}
pub(crate) fn install(self: &Arc<Self>, deadline: Instant) -> ExecutionDeadlineGuard {
let previous = self.deadline_offset.load(Ordering::Acquire);
let requested = Self::encode(self.origin, Some(deadline));
let active = if previous == 0 {
requested
} else {
previous.min(requested)
};
self.deadline_offset.store(active, Ordering::Release);
ExecutionDeadlineGuard {
state: Arc::clone(self),
previous,
completed: false,
}
}
fn encode(origin: Instant, deadline: Option<Instant>) -> u64 {
deadline.map_or(0, |deadline| {
let nanos = deadline.saturating_duration_since(origin).as_nanos();
u64::try_from(nanos)
.unwrap_or(u64::MAX - 1)
.saturating_add(1)
})
}
}
pub(crate) struct ExecutionDeadlinePauseGuard {
state: Arc<ExecutionDeadlineState>,
}
struct PauseWindow {
started_ms: i64,
clock: Arc<dyn harn_clock::Clock>,
}
impl Drop for ExecutionDeadlinePauseGuard {
fn drop(&mut self) {
let mut window = self.state.pause_window.lock();
let depth = self.state.pause_depth.load(Ordering::Acquire);
debug_assert!(depth > 0, "execution deadline pause depth underflow");
if depth == 1 {
let paused_for_ms = window
.take()
.map(|window| {
window
.clock
.monotonic_ms()
.saturating_sub(window.started_ms)
})
.unwrap_or_default();
let add = u64::try_from(paused_for_ms)
.unwrap_or_default()
.saturating_mul(1_000_000);
let _ = self.state.deadline_offset.fetch_update(
Ordering::AcqRel,
Ordering::Acquire,
|encoded| (encoded != 0).then(|| encoded.saturating_add(add)),
);
}
self.state.pause_depth.fetch_sub(1, Ordering::AcqRel);
drop(window);
self.state.changed.notify_one();
}
}
pub(crate) struct ExecutionDeadlineGuard {
state: Arc<ExecutionDeadlineState>,
previous: u64,
completed: bool,
}
impl ExecutionDeadlineGuard {
pub(crate) fn complete(mut self) {
self.completed = true;
}
}
impl Drop for ExecutionDeadlineGuard {
fn drop(&mut self) {
self.state
.deadline_offset
.store(self.previous, Ordering::Release);
if !self.completed {
self.state.abandoned.store(true, Ordering::Release);
}
}
}