use std::future::Future;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::OnceLock;
use std::time::Duration;
use deno_core::v8;
use deno_core::v8::IsolateHandle;
use deno_core::ModuleSpecifier;
use deno_runtime::code_cache::CodeCache;
use deno_runtime::code_cache::CodeCacheType;
use deno_runtime::deno_node::ops::ipc::ChildIpcSerialization;
use deno_runtime::worker::MainWorker;
pub(crate) fn isolate_create_params(max_heap_bytes: Option<usize>) -> Option<v8::CreateParams> {
max_heap_bytes
.map(|bytes| v8::CreateParams::default().set_max_old_generation_size_in_bytes(bytes))
}
const CODE_CACHE_MAX_ENTRIES: usize = 1024;
type CodeCacheKey = (String, CodeCacheType, u64);
type CodeCacheEntry = (CodeCacheKey, Vec<u8>);
struct InMemoryCodeCache {
entries: Mutex<Vec<CodeCacheEntry>>,
}
impl Default for InMemoryCodeCache {
fn default() -> Self {
Self {
entries: Mutex::new(Vec::new()),
}
}
}
impl CodeCache for InMemoryCodeCache {
fn get_sync(
&self,
specifier: &ModuleSpecifier,
code_cache_type: CodeCacheType,
source_hash: u64,
) -> Option<Vec<u8>> {
let key = (specifier.as_str().to_owned(), code_cache_type, source_hash);
self.entries
.lock()
.unwrap()
.iter()
.find(|(k, _)| *k == key)
.map(|(_, data)| data.clone())
}
fn set_sync(
&self,
specifier: ModuleSpecifier,
code_cache_type: CodeCacheType,
source_hash: u64,
data: &[u8],
) {
let key = (specifier.as_str().to_owned(), code_cache_type, source_hash);
let mut entries = self.entries.lock().unwrap();
if let Some(entry) = entries.iter_mut().find(|(k, _)| *k == key) {
entry.1 = data.to_vec();
return;
}
entries.push((key, data.to_vec()));
if entries.len() > CODE_CACHE_MAX_ENTRIES {
entries.remove(0);
}
}
}
static CODE_CACHE: OnceLock<Arc<InMemoryCodeCache>> = OnceLock::new();
pub(crate) fn in_process_code_cache() -> Arc<dyn CodeCache> {
CODE_CACHE
.get_or_init(|| Arc::new(InMemoryCodeCache::default()))
.clone()
}
pub(crate) async fn run_with_deadline<F, T, E>(
fut: F,
deadline: Option<Duration>,
isolate_handle: IsolateHandle,
) -> Result<Result<T, E>, Duration>
where
F: Future<Output = Result<T, E>>,
{
const GRACE: Duration = Duration::from_secs(2);
let Some(deadline) = deadline else {
return Ok(fut.await);
};
let fired = Arc::new(AtomicBool::new(false));
let (done_tx, done_rx) = std::sync::mpsc::channel::<()>();
let terminator = {
let fired = fired.clone();
std::thread::spawn(move || match done_rx.recv_timeout(deadline) {
Ok(()) | Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
fired.store(true, Ordering::SeqCst);
isolate_handle.terminate_execution();
}
})
};
let result = match tokio::time::timeout(deadline.saturating_add(GRACE), fut).await {
Ok(result) => result,
Err(_) => return Err(deadline),
};
let _ = done_tx.send(());
let _ = terminator.join();
if fired.load(Ordering::SeqCst) {
Err(deadline)
} else {
Ok(result)
}
}
pub(crate) async fn run_worker(
worker: &mut MainWorker,
main_module: &ModuleSpecifier,
execution_deadline: Option<Duration>,
isolate_handle: IsolateHandle,
) -> Result<Result<(), crate::LibdenoError>, Duration> {
let run = async {
worker.execute_main_module(main_module).await?;
worker.run_event_loop(false).await?;
worker.dispatch_load_event()?;
worker.run_event_loop(false).await?;
worker.dispatch_beforeunload_event()?;
worker.dispatch_unload_event()?;
worker.dispatch_process_beforeexit_event()?;
worker.dispatch_process_exit_event()?;
Ok::<(), crate::LibdenoError>(())
};
run_with_deadline(run, execution_deadline, isolate_handle).await
}
pub(crate) const LIBDENO_SPAWNED_IPC: &str = "LIBDENO_SPAWNED_IPC";
static NODE_IPC_MARKER: OnceLock<bool> = OnceLock::new();
pub(crate) fn capture_spawned_ipc_marker() {
NODE_IPC_MARKER.get_or_init(|| {
let spawned = std::env::var(LIBDENO_SPAWNED_IPC).as_deref() == Ok("1");
std::env::set_var(LIBDENO_SPAWNED_IPC, "1");
spawned
});
}
pub(crate) fn node_ipc_init() -> Option<(i64, ChildIpcSerialization)> {
if !NODE_IPC_MARKER.get().copied().unwrap_or(false) {
return None;
}
let fd = std::env::var("NODE_CHANNEL_FD").ok()?.parse::<i64>().ok()?;
let serialization = match std::env::var("NODE_CHANNEL_SERIALIZATION_MODE").as_deref() {
Ok("advanced") => ChildIpcSerialization::Advanced,
_ => ChildIpcSerialization::Json,
};
Some((fd, serialization))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn isolate_create_params_maps_heap_cap() {
assert!(isolate_create_params(None).is_none());
let params = isolate_create_params(Some(12345)).unwrap();
assert_eq!(params.max_old_generation_size_in_bytes(), 12345);
}
#[test]
fn code_cache_fifo_evicts_oldest_entry() {
let cache = InMemoryCodeCache::default();
let spec = |i: u64| {
ModuleSpecifier::parse(&format!("file:///libdeno-code-cache-test/{i}.js")).unwrap()
};
let hash = 7u64;
for i in 0..=CODE_CACHE_MAX_ENTRIES as u64 {
cache.set_sync(spec(i), CodeCacheType::EsModule, hash, &[0u8, 1, 2]);
}
assert!(
cache
.get_sync(&spec(0), CodeCacheType::EsModule, hash)
.is_none(),
"oldest entry must be evicted"
);
assert!(
cache
.get_sync(
&spec(CODE_CACHE_MAX_ENTRIES as u64),
CodeCacheType::EsModule,
hash
)
.is_some(),
"newest entry must be present"
);
assert!(cache
.get_sync(&spec(5), CodeCacheType::EsModule, 999)
.is_none());
}
#[test]
fn code_cache_replace_and_type_keying() {
let cache = InMemoryCodeCache::default();
let spec = ModuleSpecifier::parse("file:///libdeno-code-cache-test/update.js").unwrap();
cache.set_sync(spec.clone(), CodeCacheType::Script, 1, b"old");
cache.set_sync(spec.clone(), CodeCacheType::Script, 1, b"new");
assert_eq!(
cache.get_sync(&spec, CodeCacheType::Script, 1).unwrap(),
b"new"
);
cache.set_sync(spec.clone(), CodeCacheType::EsModule, 1, b"esm");
assert_eq!(
cache.get_sync(&spec, CodeCacheType::Script, 1).unwrap(),
b"new"
);
assert_eq!(
cache.get_sync(&spec, CodeCacheType::EsModule, 1).unwrap(),
b"esm"
);
}
#[test]
fn node_ipc_requires_paired_spawn_marker() {
std::env::set_var("NODE_CHANNEL_FD", "10");
assert!(node_ipc_init().is_none());
std::env::set_var("LIBDENO_SPAWNED_IPC", "1");
capture_spawned_ipc_marker();
assert_eq!(node_ipc_init().map(|(fd, _)| fd), Some(10));
assert!(matches!(
node_ipc_init(),
Some((10, ChildIpcSerialization::Json))
));
std::env::set_var("NODE_CHANNEL_SERIALIZATION_MODE", "advanced");
assert!(matches!(
node_ipc_init(),
Some((10, ChildIpcSerialization::Advanced))
));
std::env::set_var("NODE_CHANNEL_FD", "not-a-fd");
assert!(node_ipc_init().is_none());
}
}