use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::time::Duration;
use deno_core::url::Url;
use deno_core::v8;
use deno_core::PollEventLoopOptions;
use deno_runtime::worker::MainWorker;
use crate::bridge;
use crate::bridge::EngineHooks;
use crate::host::ModuleHost;
use crate::watchdog::DeadlineGuard;
use crate::watchdog::Watchdog;
use crate::worker::build_worker;
use crate::EngineConfig;
use crate::EngineError;
pub(crate) enum Verdict {
Clean,
Deadline,
MemoryLimit,
}
impl Verdict {
pub(crate) fn apply(
self,
result: Result<serde_json::Value, EngineError>,
) -> Result<serde_json::Value, EngineError> {
match (self, result) {
(Verdict::MemoryLimit, Err(_)) => Err(EngineError::MemoryLimit),
(Verdict::Deadline, Err(_)) => Err(EngineError::Deadline),
(_, result) => result,
}
}
pub(crate) fn cutoff(self) -> EngineError {
match self {
Verdict::MemoryLimit => EngineError::MemoryLimit,
_ => EngineError::Deadline,
}
}
}
pub(crate) struct Isolate {
pub(crate) worker: MainWorker,
watchdog: Watchdog,
oom: Option<Arc<AtomicBool>>,
condemned: bool,
}
impl Isolate {
pub(crate) fn boot(
config: &EngineConfig,
main_module: &Url,
module_host: Option<ModuleHost>,
) -> Result<Isolate, EngineError> {
let mut worker = build_worker(config, main_module, module_host)?;
let watchdog = Watchdog::spawn(worker.js_runtime.v8_isolate().thread_safe_handle());
Ok(Isolate {
worker,
watchdog,
oom: None,
condemned: false,
})
}
pub(crate) fn handle(&mut self) -> v8::IsolateHandle {
self.worker.js_runtime.v8_isolate().thread_safe_handle()
}
pub(crate) fn install(&mut self, hooks: Option<EngineHooks>, capped: bool) {
if let Some(hooks) = hooks {
bridge::install(&mut self.worker, hooks);
}
if capped {
let flag = Arc::new(AtomicBool::new(false));
self.oom = Some(Arc::clone(&flag));
let handle = self.worker.js_runtime.v8_isolate().thread_safe_handle();
self.worker
.js_runtime
.add_near_heap_limit_callback(move |current, _initial| {
flag.store(true, Ordering::SeqCst);
handle.terminate_execution();
current * 2
});
}
}
pub(crate) fn is_condemned(&self) -> bool {
self.condemned
}
pub(crate) fn arm(&self, deadline: Option<Duration>) -> Option<DeadlineGuard> {
deadline.map(|d| DeadlineGuard::arm(&self.watchdog, d))
}
pub(crate) fn settle(&mut self, guard: Option<DeadlineGuard>) -> Verdict {
let deadline_fired = guard.map(DeadlineGuard::disarm).unwrap_or(false);
let oom_fired = self.take_oom();
if deadline_fired || oom_fired {
self.cancel_termination();
}
if oom_fired {
self.condemned = true;
Verdict::MemoryLimit
} else if deadline_fired {
Verdict::Deadline
} else {
Verdict::Clean
}
}
pub(crate) fn oom_pending(&self) -> bool {
self.oom
.as_ref()
.is_some_and(|flag| flag.load(Ordering::SeqCst))
}
pub(crate) fn take_oom(&mut self) -> bool {
let fired = self
.oom
.as_ref()
.is_some_and(|flag| flag.swap(false, Ordering::SeqCst));
if fired {
self.condemned = true;
}
fired
}
pub(crate) fn cancel_termination(&mut self) {
self.worker
.js_runtime
.v8_isolate()
.cancel_terminate_execution();
}
pub(crate) fn collect_garbage(&mut self) {
self.worker
.js_runtime
.v8_isolate()
.low_memory_notification();
}
pub(crate) fn poll_events(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Result<(), deno_core::error::CoreError>> {
self.worker
.js_runtime
.poll_event_loop(cx, PollEventLoopOptions::default())
}
}