use std::collections::BTreeSet;
use std::sync::Arc;
use std::sync::Condvar;
use std::sync::Mutex;
use std::time::Duration;
use std::time::Instant;
use deno_core::v8;
use crate::lock;
pub(crate) struct Watchdog {
shared: Arc<Shared>,
thread: Option<std::thread::JoinHandle<()>>,
}
struct Shared {
state: Mutex<State>,
cv: Condvar,
}
struct State {
next_id: u64,
armed: BTreeSet<(Instant, u64)>,
shutdown: bool,
}
impl Watchdog {
pub(crate) fn spawn(handle: v8::IsolateHandle) -> Watchdog {
let shared = Arc::new(Shared {
state: Mutex::new(State {
next_id: 0,
armed: BTreeSet::new(),
shutdown: false,
}),
cv: Condvar::new(),
});
let thread = {
let shared = Arc::clone(&shared);
std::thread::Builder::new()
.name("oj-js-watchdog".into())
.spawn(move || run(&shared, &handle))
.expect("spawn watchdog thread")
};
Watchdog {
shared,
thread: Some(thread),
}
}
}
impl Drop for Watchdog {
fn drop(&mut self) {
lock(&self.shared.state).shutdown = true;
self.shared.cv.notify_all();
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
fn run(shared: &Shared, handle: &v8::IsolateHandle) {
let mut state = lock(&shared.state);
while !state.shutdown {
let now = Instant::now();
state = match state.armed.first().copied() {
Some((at, _)) if at <= now => {
state.armed.pop_first();
handle.terminate_execution();
state
}
Some((at, _)) => {
shared
.cv
.wait_timeout(state, at.saturating_duration_since(now))
.unwrap_or_else(std::sync::PoisonError::into_inner)
.0
}
None => shared
.cv
.wait(state)
.unwrap_or_else(std::sync::PoisonError::into_inner),
};
}
}
pub(crate) struct DeadlineGuard {
key: (Instant, u64),
shared: Arc<Shared>,
}
impl DeadlineGuard {
pub(crate) fn arm(watchdog: &Watchdog, deadline: Duration) -> Self {
let key = {
let mut state = lock(&watchdog.shared.state);
let key = (Instant::now() + deadline, state.next_id);
state.next_id += 1;
state.armed.insert(key);
key
};
watchdog.shared.cv.notify_all();
DeadlineGuard {
key,
shared: Arc::clone(&watchdog.shared),
}
}
pub(crate) fn disarm(self) -> bool {
!lock(&self.shared.state).armed.remove(&self.key)
}
pub(crate) fn fired(&self) -> bool {
!lock(&self.shared.state).armed.contains(&self.key)
}
}