/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/core/runtime/jsruntime.rs
3 702 строки
130 KB
Bartek Iwańczuk
perf(core): run the common-path tick drain in Rust (#36157)
27 июл 2026, 15:59
Не верифицирован
27 июл 2026, 15:59
0c965f5
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::any::Any; use std::cell::Cell; use std::cell::RefCell; use std::collections::HashMap; use std::collections::VecDeque; use std::ffi::c_void; use std::future::Future; use std::future::poll_fn; use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; use std::sync::Mutex; use std::sync::OnceLock; use std::task::Context; use std::task::Poll; use std::task::Waker; use std::time::Instant; use deno_error::JsErrorBox; use futures::FutureExt; use futures::StreamExt; use futures::task::AtomicWaker; use smallvec::SmallVec; fn startup_phases_enabled() -> bool { static ENABLED: OnceLock<bool> = OnceLock::new(); *ENABLED.get_or_init(|| { // Diagnostic env var — only consulted once per process. Bypassing // `sys_traits` here is intentional: the snapshot/runtime layers don't // thread an `EnvProvider` through this code path, and this opt-in // tracing is only useful when running an actual `deno` binary anyway. #[allow( clippy::disallowed_methods, reason = "diagnostic env var; not part of the sys_traits surface" )] let v = std::env::var_os("DENO_STARTUP_PHASES"); v.is_some_and(|v| !v.is_empty() && v != "0") }) } #[inline] fn startup_phase_begin() -> Option<Instant> { if startup_phases_enabled() { Some(Instant::now()) } else { None } } #[inline] fn startup_phase_end(t: Option<Instant>, label: &str) { if let Some(start) = t { let elapsed = start.elapsed(); #[allow(clippy::print_stderr, reason = "diagnostic")] { eprintln!("[startup] {label:>32} {elapsed:?}"); } } } use super::SnapshotStoreDataStore; use super::SnapshottedData; use super::bindings; use super::bindings::create_exports_for_ops_virtual_module; use super::bindings::watch_promise; use super::exception_state::ExceptionState; use super::jsrealm::JsRealmInner; use super::op_driver::OpDriver; use super::setup; use super::snapshot; use super::stats::RuntimeActivityStatsFactory; use super::v8_static_strings::*; use crate::Extension; use crate::ExtensionArguments; use crate::ExtensionFileSource; use crate::ExtensionFileSourceCode; use crate::FastStaticString; use crate::FastString; use crate::ModuleCodeString; use crate::NoopModuleLoader; use crate::OpMetadata; use crate::OpMetricsEvent; use crate::OpStackTraceCallback; use crate::OpState; use crate::ascii_str; use crate::ascii_str_include; use crate::cppgc::FunctionTemplateData; use crate::error::CoreError; use crate::error::CoreErrorKind; use crate::error::CoreModuleExecuteError; use crate::error::CoreModuleParseError; use crate::error::ExtensionLazyInitCountMismatchError; use crate::error::ExtensionLazyInitOrderMismatchError; use crate::error::JsError; use crate::error::exception_to_err_result; use crate::extension_set; use crate::extension_set::LoadedSources; use crate::extensions::GlobalObjectMiddlewareFn; use crate::extensions::GlobalTemplateMiddlewareFn; use crate::inspector::JsRuntimeInspector; use crate::module_specifier::ModuleSpecifier; use crate::modules::CustomModuleEvaluationCb; use crate::modules::EvalContextCodeCacheReadyCb; use crate::modules::EvalContextGetCodeCacheCb; use crate::modules::ExtCodeCache; use crate::modules::ExtModuleLoader; use crate::modules::IntoModuleCodeString; use crate::modules::IntoModuleName; use crate::modules::ModuleId; use crate::modules::ModuleLoader; use crate::modules::ModuleMap; use crate::modules::ModuleName; use crate::modules::RequestedModuleType; use crate::modules::SideModuleKind; use crate::modules::ValidateImportAttributesCb; use crate::modules::recursive_load::RecursiveModuleLoad; use crate::modules::script_origin; use crate::ops_metrics::OpMetricsFactoryFn; use crate::ops_metrics::dispatch_metrics_async; use crate::runtime::ContextState; use crate::runtime::JsRealm; use crate::runtime::OpDriverImpl; use crate::runtime::jsrealm; use crate::runtime::jsrealm::IMM_IDX_COUNT; use crate::runtime::jsrealm::IMM_IDX_HAS_OUTSTANDING; use crate::runtime::jsrealm::IMM_IDX_REF_COUNT; use crate::source_map::SourceMapData; use crate::source_map::SourceMapper; use crate::stats::RuntimeActivityType; use crate::uv_compat; pub type WaitForInspectorDisconnectCallback = Box<dyn Fn()>; /// A per-isolate hook invoked when V8's host-initialize-import-meta callback /// fires for a module that is not registered in deno_core's module map (e.g. /// a `node:vm` `SourceTextModule`). The embedder is expected to populate the /// `meta` object as needed and return; the hook is not allowed to throw. /// /// Each argument carries its own lifetime parameter (rather than being tied /// to the scope's `'s`) so the unbound `Local`s V8 passes into the /// extern "C" callback can be forwarded without re-creating handles. pub type ExternalModuleImportMetaCb = for<'s, 'i, 'm, 'o> fn( scope: &mut v8::PinScope<'s, 'i>, module: v8::Local<'m, v8::Module>, meta: v8::Local<'o, v8::Object>, ); const STATE_DATA_OFFSET: u32 = 0; pub type ExtensionTranspiler = dyn Fn( ModuleName, ModuleCodeString, ) -> Result<(ModuleCodeString, Option<SourceMapData>), JsErrorBox>; /// Objects that need to live as long as the isolate #[derive(Default)] pub(crate) struct IsolateAllocations { pub(crate) externalized_sources: Box<[v8::OneByteConst]>, pub(crate) original_sources: Box<[FastString]>, /// Specifiers of the externalized `lazy_loaded_*` sources, parallel to the /// trailing entries of `original_sources` (only populated when building a /// snapshot). Used at serialize time to drop the bytes of *non-consumed* /// lazy sources from the snapshot sidecar — their external-reference slots /// must stay (for index alignment) but nothing references them, so storing /// empty bytes avoids duplicating residual sources (which the binary already /// ships via the residual table). pub(crate) lazy_source_specifiers: Box<[ModuleName]>, pub(crate) near_heap_limit_callback_data: Option<(Box<RefCell<dyn Any>>, v8::NearHeapLimitCallback)>, } /// ManuallyDrop<Rc<...>> is clone, but it returns a ManuallyDrop<Rc<...>> which is a massive /// memory-leak footgun. pub(crate) struct ManuallyDropRc<T>(ManuallyDrop<Rc<T>>); impl<T> ManuallyDropRc<T> { #[allow(unused, reason = "used in other configurations")] pub fn clone(&self) -> Rc<T> { self.0.deref().clone() } } impl<T> Deref for ManuallyDropRc<T> { type Target = Rc<T>; fn deref(&self) -> &Self::Target { self.0.deref() } } impl<T> DerefMut for ManuallyDropRc<T> { fn deref_mut(&mut self) -> &mut Self::Target { self.0.deref_mut() } } /// This struct contains the [`JsRuntimeState`] and [`v8::OwnedIsolate`] that are required /// to do an orderly shutdown of V8. We keep these in a separate struct to allow us to control /// the destruction more closely, as snapshots require the isolate to be destroyed by the /// snapshot process, not the destructor. /// /// The way rusty_v8 works w/snapshots is that the [`v8::OwnedIsolate`] gets consumed by a /// [`v8::snapshot::SnapshotCreator`] that is stored in its annex. It's a bit awkward, because this /// means we cannot let it drop (because we don't have it after a snapshot). On top of that, we have /// to consume it in the snapshot creator because otherwise it panics. /// /// This inner struct allows us to let the outer JsRuntime drop normally without a Drop impl, while we /// control dropping more closely here using ManuallyDrop. pub(crate) struct InnerIsolateState { will_snapshot: bool, extensions: Vec<&'static str>, op_count: usize, source_count: usize, addl_refs_count: usize, main_realm: ManuallyDrop<JsRealm>, pub(crate) state: ManuallyDropRc<JsRuntimeState>, v8_isolate: ManuallyDrop<v8::OwnedIsolate>, } impl InnerIsolateState { /// Clean out the opstate and take the inspector to prevent the inspector from getting destroyed /// after we've torn down the contexts. If the inspector is not correctly torn down, random crashes /// happen in tests (and possibly for users using the inspector). pub fn prepare_for_cleanup(&mut self) { // Explicitly shut down the op driver here, just in case there are other references to it // that prevent it from dropping after we invalidate the state. self.main_realm.0.context_state.pending_ops.shutdown(); let inspector = self.state.inspector.take(); self.state.op_state.borrow_mut().clear(); if let Some(inspector) = inspector { assert_eq!( Rc::strong_count(&inspector), 1, "The inspector must be dropped before the runtime" ); } } pub fn cleanup(&mut self) { // Shut down the op driver and take the inspector before realm destroy. self.main_realm.0.context_state.pending_ops.shutdown(); let inspector = self.state.inspector.take(); // Unregister isolate waker before dropping the isolate let isolate_ptr = unsafe { self.v8_isolate.as_raw_isolate_ptr() }; setup::unregister_isolate(setup::isolate_ptr_to_key(isolate_ptr)); let state_ptr = self.v8_isolate.get_data(STATE_DATA_OFFSET); // SAFETY: We are sure that it's a valid pointer for whole lifetime of // the runtime. _ = unsafe { Rc::from_raw(state_ptr as *const JsRuntimeState) }; // Destroy the realm while the uv_loop (stored in op_state's // gotham_state) is still alive. destroy() reads uv_loop_ptr to // release the Global<Context> stored in loop.data. If we clear // op_state first, the Box<UvLoop> is freed and destroy() would // dereference a dangling pointer (use-after-free). unsafe { ManuallyDrop::take(&mut self.main_realm).0.destroy(); } // Now safe to clear op_state (drops Box<UvLoop> etc.) since // destroy() has already released the Global from loop.data. self.state.op_state.borrow_mut().clear(); if let Some(inspector) = inspector { assert_eq!( Rc::strong_count(&inspector), 1, "The inspector must be dropped before the runtime" ); } debug_assert_eq!(Rc::strong_count(&self.state), 1); } pub fn prepare_for_snapshot(mut self) -> v8::OwnedIsolate { self.cleanup(); // SAFETY: We're copying out of self and then immediately forgetting self unsafe { ManuallyDrop::drop(&mut self.state.0); let isolate = ManuallyDrop::take(&mut self.v8_isolate); std::mem::forget(self); isolate } } } impl Drop for InnerIsolateState { fn drop(&mut self) { self.cleanup(); // SAFETY: We gotta drop these unsafe { ManuallyDrop::drop(&mut self.state.0); if self.will_snapshot { // Create the snapshot and just drop it. #[allow( clippy::print_stderr, reason = "intentional warning about leaked isolate" )] { eprintln!("WARNING: v8::OwnedIsolate for snapshot was leaked"); } } else { ManuallyDrop::drop(&mut self.v8_isolate); } } } } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub(crate) enum InitMode { /// We have no snapshot -- this is a pristine context. New, /// We are using a snapshot, thus certain initialization steps are skipped. FromSnapshot { // Can we skip the work of op registration? skip_op_registration: bool, }, } impl InitMode { fn from_options(options: &RuntimeOptions) -> Self { match options.startup_snapshot { None => Self::New, Some(_) => Self::FromSnapshot { skip_op_registration: options.skip_op_registration, }, } } #[inline] pub fn needs_ops_bindings(&self) -> bool { !matches!( self, InitMode::FromSnapshot { skip_op_registration: true } ) } } #[derive(Default)] struct PromiseFuture { resolved: Cell<Option<Result<v8::Global<v8::Value>, Box<JsError>>>>, waker: Cell<Option<Waker>>, } #[derive(Clone, Default)] struct RcPromiseFuture(Rc<PromiseFuture>); impl RcPromiseFuture { pub fn new(res: Result<v8::Global<v8::Value>, Box<JsError>>) -> Self { Self(Rc::new(PromiseFuture { resolved: Some(res).into(), ..Default::default() })) } } impl Future for RcPromiseFuture { type Output = Result<v8::Global<v8::Value>, Box<JsError>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { let this = self.get_mut(); match this.0.resolved.take() { Some(resolved) => Poll::Ready(resolved), _ => { this.0.waker.set(Some(cx.waker().clone())); Poll::Pending } } } } static VIRTUAL_OPS_MODULE_NAME: FastStaticString = ascii_str!("ext:core/ops"); pub(crate) struct InternalSourceFile { pub specifier: FastStaticString, pub source: FastStaticString, } macro_rules! internal_source_file { ($str_:literal) => {{ InternalSourceFile { specifier: ascii_str!(concat!("ext:core/", $str_)), source: ascii_str_include!(concat!("../", $str_)), } }}; } /// These files are executed just after a new context is created. They provided /// the necessary infrastructure to bind ops. pub(crate) static CONTEXT_SETUP_SOURCES: [InternalSourceFile; 2] = [ internal_source_file!("00_primordials.js"), internal_source_file!("00_infra.js"), ]; /// These files are executed when we start setting up extensions. They rely /// on ops being already fully set up. pub(crate) static BUILTIN_SOURCES: [InternalSourceFile; 2] = [ internal_source_file!("02_timers.js"), internal_source_file!("01_core.js"), ]; /// Executed after `BUILTIN_SOURCES` are executed. Provides a thin ES module /// that exports `core`, `internals` and `primordials` objects. pub(crate) static BUILTIN_ES_MODULES: [ExtensionFileSource; 1] = [ExtensionFileSource::new( "ext:core/mod.js", ascii_str_include!("../mod.js"), )]; /// We have `ext:core/ops` and `ext:core/mod.js` that are always provided. #[cfg(test)] pub(crate) const NO_OF_BUILTIN_MODULES: usize = 2; /// A single execution context of JavaScript. Corresponds roughly to the "Web /// Worker" concept in the DOM. /// /// The JsRuntime future completes when there is an error or when all /// pending ops have completed. /// /// Use [`JsRuntimeForSnapshot`] to be able to create a snapshot. /// /// Note: since V8 11.6, all runtimes must have a common parent thread that /// initalized the V8 platform. This can be done by calling /// [`JsRuntime::init_platform`] explicitly, or it will be done automatically on /// the calling thread when the first runtime is created. pub struct JsRuntime { pub(crate) inner: InnerIsolateState, pub(crate) allocations: IsolateAllocations, // Contains paths of source files that were executed in // [`JsRuntime::init_extension_js`]. This field is populated only if a // snapshot is being created. files_loaded_from_fs_during_snapshot: Vec<&'static str>, // Marks if this is considered the top-level runtime. Used only by inspector. is_main_runtime: bool, } /// The runtime type used for snapshot creation. pub struct JsRuntimeForSnapshot(JsRuntime); impl Deref for JsRuntimeForSnapshot { type Target = JsRuntime; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for JsRuntimeForSnapshot { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } pub struct CrossIsolateStore<T>(Arc<Mutex<CrossIsolateStoreInner<T>>>); struct CrossIsolateStoreInner<T> { map: HashMap<u32, T>, last_id: u32, } impl<T> CrossIsolateStore<T> { pub(crate) fn insert(&self, value: T) -> u32 { let mut store = self.0.lock().unwrap(); let last_id = store.last_id; store.map.insert(last_id, value); store.last_id += 1; last_id } pub(crate) fn take(&self, id: u32) -> Option<T> { let mut store = self.0.lock().unwrap(); store.map.remove(&id) } } impl<T> Default for CrossIsolateStore<T> { fn default() -> Self { CrossIsolateStore(Arc::new(Mutex::new(CrossIsolateStoreInner { map: Default::default(), last_id: 0, }))) } } impl<T> Clone for CrossIsolateStore<T> { fn clone(&self) -> Self { Self(self.0.clone()) } } pub type SharedArrayBufferStore = CrossIsolateStore<v8::SharedRef<v8::BackingStore>>; pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>; /// Internal state for JsRuntime which is stored in one of v8::Isolate's /// embedder slots. pub struct JsRuntimeState { pub(crate) source_mapper: Rc<RefCell<SourceMapper>>, pub(crate) op_state: Rc<RefCell<OpState>>, pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>, pub(crate) compiled_wasm_module_store: Option<CompiledWasmModuleStore>, wait_for_inspector_disconnect_callback: Option<WaitForInspectorDisconnectCallback>, pub(crate) validate_import_attributes_cb: Option<ValidateImportAttributesCb>, pub(crate) custom_module_evaluation_cb: Option<CustomModuleEvaluationCb>, pub(crate) vm_dynamic_import_callbacks: RefCell<HashMap<u32, v8::Global<v8::Function>>>, pub(crate) next_vm_dynamic_import_callback_id: Cell<u32>, /// Hook invoked by `host_initialize_import_meta_object_callback` when V8 /// asks to initialize `import.meta` for a module that is *not* tracked by /// the module map. This is the path `node:vm` `SourceTextModule` takes: /// the v8::Module is created directly by the `node:vm` op and is never /// registered in the deno_core module map, but V8 still invokes the /// per-isolate import-meta callback for it. Without this hook the /// callback used to `panic!("Module not found")`. pub(crate) external_module_import_meta_cb: Cell<Option<ExternalModuleImportMetaCb>>, pub(crate) eval_context_get_code_cache_cb: RefCell<Option<EvalContextGetCodeCacheCb>>, pub(crate) eval_context_code_cache_ready_cb: RefCell<Option<EvalContextCodeCacheReadyCb>>, pub(crate) cppgc_template: RefCell<Option<v8::Global<v8::FunctionTemplate>>>, pub(crate) function_templates: Rc<RefCell<FunctionTemplateData>>, pub(crate) callsite_prototype: RefCell<Option<v8::Global<v8::Object>>>, waker: Arc<AtomicWaker>, /// Foreground V8 tasks queued by the custom platform. Shared with the /// global isolate registry so background threads can push tasks, while /// the event loop drains them without touching the global map. foreground_tasks: setup::ForegroundTaskQueue, /// Accessed through [`JsRuntimeState::with_inspector`]. inspector: RefCell<Option<Rc<JsRuntimeInspector>>>, has_inspector: Cell<bool>, lazy_extensions: Vec<&'static str>, /// Counter for consecutive event loop iterations where module evaluation /// is pending but V8 reports no stalled top-level await. Used to detect /// deadlocks and avoid spinning the event loop indefinitely. tla_stall_retries: Cell<u32>, } #[derive(Default)] pub struct RuntimeOptions { /// Implementation of `ModuleLoader` which will be /// called when V8 requests to load ES modules in the main realm. /// /// If not provided runtime will error if code being /// executed tries to load modules. pub module_loader: Option<Rc<dyn ModuleLoader>>, /// If specified, enables V8 code cache for extension code. pub extension_code_cache: Option<Rc<dyn ExtCodeCache>>, /// If specified, transpiles extensions before loading. pub extension_transpiler: Option<Rc<ExtensionTranspiler>>, /// Provide a function that may optionally provide a metrics collector /// for a given op. pub op_metrics_factory_fn: Option<OpMetricsFactoryFn>, /// JsRuntime extensions, not to be confused with ES modules. /// Only ops registered by extensions will be initialized. If you need /// to execute JS code from extensions, pass source files in `js` or `esm` /// option on `ExtensionBuilder`. /// /// If you are creating a runtime from a snapshot take care not to include /// JavaScript sources in the extensions. pub extensions: Vec<Extension>, /// V8 snapshot that should be loaded on startup. /// /// For testing, use `runtime.snapshot()` and then [`Box::leak`] to acquire // a static slice. pub startup_snapshot: Option<&'static [u8]>, /// Source for `lazy_loaded_js` files that were *not* consumed at snapshot /// build time and therefore aren't reachable via the snapshot blob. The /// build script that produced `startup_snapshot` emits this table. /// Each entry is `(specifier, source)`. pub residual_lazy_js_sources: &'static [(&'static str, &'static str)], /// Source for `lazy_loaded_esm` files that were *not* consumed at snapshot /// build time. See [`Self::residual_lazy_js_sources`]. pub residual_lazy_esm_sources: &'static [(&'static str, &'static str)], /// Should op registration be skipped? pub skip_op_registration: bool, /// Isolate creation parameters. pub create_params: Option<v8::CreateParams>, /// V8 platform instance to use. Used when Deno initializes V8 /// (which it only does once), otherwise it's silently dropped. pub v8_platform: Option<v8::SharedRef<v8::Platform>>, /// The store to use for transferring SharedArrayBuffers between isolates. /// If multiple isolates should have the possibility of sharing /// SharedArrayBuffers, they should use the same [SharedArrayBufferStore]. If /// no [SharedArrayBufferStore] is specified, SharedArrayBuffer can not be /// serialized. pub shared_array_buffer_store: Option<SharedArrayBufferStore>, /// The store to use for transferring `WebAssembly.Module` objects between /// isolates. /// If multiple isolates should have the possibility of sharing /// `WebAssembly.Module` objects, they should use the same /// [CompiledWasmModuleStore]. If no [CompiledWasmModuleStore] is specified, /// `WebAssembly.Module` objects cannot be serialized. pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>, /// Start inspector instance to allow debuggers to connect. pub inspector: bool, /// Describe if this is the main runtime instance, used by debuggers in some /// situation - like disconnecting when program finishes running. pub is_main: bool, /// Worker ID for inspector context naming (e.g., "worker [1]", "worker [2]"). /// Only used when `is_main` is false. Starts at 1. pub worker_id: Option<u32>, #[cfg(any(test, feature = "unsafe_runtime_options"))] /// Should this isolate expose the v8 natives (eg: %OptimizeFunctionOnNextCall) and /// GC control functions (`gc()`)? WARNING: This should not be used for production code as /// this may expose the runtime to security vulnerabilities. pub unsafe_expose_natives_and_gc: bool, /// A callback that can be used to validate import attributes received at /// the import site. If no callback is provided, all attributes are allowed. /// /// Embedders might use this callback to eg. validate value of "type" /// attribute, not allowing other types than "JSON". /// /// To signal validation failure, users should throw an V8 exception inside /// the callback. pub validate_import_attributes_cb: Option<ValidateImportAttributesCb>, /// A callback that is called when the event loop has no more work to do, /// but there are active, non-blocking inspector session (eg. Chrome /// DevTools inspector is connected). The embedder can use this callback /// to eg. print a message notifying user about program finished running. /// This callback can be called multiple times, eg. after the program finishes /// more work can be scheduled from the DevTools. pub wait_for_inspector_disconnect_callback: Option<WaitForInspectorDisconnectCallback>, /// A callback that allows to evaluate a custom type of a module - eg. /// embedders might implement loading WASM or test modules. pub custom_module_evaluation_cb: Option<CustomModuleEvaluationCb>, /// Callbacks to retrieve and store code cache for scripts evaluated /// through evalContext. pub eval_context_code_cache_cbs: Option<(EvalContextGetCodeCacheCb, EvalContextCodeCacheReadyCb)>, /// A callback to specify how stack traces should be used when an op is /// annotated with `stack_trace` attribute. Use wisely, as it's very expensive /// to collect stack traces on each op invocation. pub maybe_op_stack_trace_callback: Option<OpStackTraceCallback>, } impl RuntimeOptions { #[cfg(any(test, feature = "unsafe_runtime_options"))] fn unsafe_expose_natives_and_gc(&self) -> bool { self.unsafe_expose_natives_and_gc } #[cfg(not(any(test, feature = "unsafe_runtime_options")))] fn unsafe_expose_natives_and_gc(&self) -> bool { false } } #[derive(Copy, Clone, Debug, Default)] pub struct PollEventLoopOptions { pub wait_for_inspector: bool, } #[derive(Default)] pub struct CreateRealmOptions { /// Implementation of `ModuleLoader` which will be /// called when V8 requests to load ES modules in the realm. /// /// If not provided, there will be an error if code being /// executed tries to load modules from the realm. pub module_loader: Option<Rc<dyn ModuleLoader>>, } #[macro_export] macro_rules! scope { ($scope: ident, $self: expr) => { let context = $self.main_context(); let isolate = &mut *$self.v8_isolate(); $crate::v8::scope!($scope, isolate); let context = $crate::v8::Local::new($scope, context); let $scope = &mut $crate::v8::ContextScope::new($scope, context); }; } impl JsRuntime { /// Explicitly initalizes the V8 platform using the passed platform. This /// should only be called once per process. Further calls will be silently /// ignored. #[cfg(not(any(test, feature = "unsafe_runtime_options")))] pub fn init_platform(v8_platform: Option<v8::SharedRef<v8::Platform>>) { setup::init_v8(v8_platform, cfg!(test), false); } /// Explicitly initalizes the V8 platform using the passed platform. This /// should only be called once per process. Further calls will be silently /// ignored. /// /// The `expose_natives` flag is used to expose the v8 natives /// (eg: %OptimizeFunctionOnNextCall) and GC control functions (`gc()`). /// WARNING: This should not be used for production code as /// this may expose the runtime to security vulnerabilities. #[cfg(any(test, feature = "unsafe_runtime_options"))] pub fn init_platform( v8_platform: Option<v8::SharedRef<v8::Platform>>, expose_natives: bool, ) { setup::init_v8(v8_platform, cfg!(test), expose_natives); } /// Only constructor, configuration is done through `options`. /// Panics if the runtime cannot be initialized. pub fn new(options: RuntimeOptions) -> JsRuntime { match Self::try_new(options) { Ok(runtime) => runtime, Err(err) => { panic!( "Failed to initialize a JsRuntime: {}", err.print_with_cause() ); } } } /// Only constructor, configuration is done through `options`. /// Returns an error if the runtime cannot be initialized. pub fn try_new(mut options: RuntimeOptions) -> Result<JsRuntime, CoreError> { let _t0 = startup_phase_begin(); setup::init_v8( options.v8_platform.take(), cfg!(test), options.unsafe_expose_natives_and_gc(), ); startup_phase_end(_t0, "init_v8"); JsRuntime::new_inner(options, false) } pub(crate) fn state_from(isolate: &v8::Isolate) -> Rc<JsRuntimeState> { let state_ptr = isolate.get_data(STATE_DATA_OFFSET); let state_rc = // SAFETY: We are sure that it's a valid pointer for whole lifetime of // the runtime. unsafe { Rc::from_raw(state_ptr as *const JsRuntimeState) }; let state = state_rc.clone(); std::mem::forget(state_rc); state } /// Returns the `OpState` associated with the passed `Isolate`. pub fn op_state_from(isolate: &v8::Isolate) -> Rc<RefCell<OpState>> { let state = Self::state_from(isolate); state.op_state.clone() } pub(crate) fn has_more_work(scope: &mut v8::PinScope) -> bool { EventLoopPendingState::new_from_scope(scope).is_pending() } /// Returns the `OpMetadata` associated with the op `name`. /// Note this is linear with respect to the number of ops registered. pub fn op_metadata(&self, name: &str) -> Option<OpMetadata> { let state = &self.inner.main_realm.0.context_state; state.op_ctxs.iter().find_map(|ctx| { if ctx.decl.name == name { Some(ctx.decl.metadata) } else { None } }) } fn new_inner( mut options: RuntimeOptions, will_snapshot: bool, ) -> Result<JsRuntime, CoreError> { let _phase_total = startup_phase_begin(); let init_mode = InitMode::from_options(&options); let mut extensions = std::mem::take(&mut options.extensions); let mut isolate_allocations = IsolateAllocations::default(); // `lazy_loaded_js` sources are externalized (and pre-wrapped) only when // building the snapshot, so consumed scripts bake in as clean external // sources. At runtime they stay as static registrations. let externalize_lazy_js = will_snapshot; let enable_stack_trace_in_ops = options.maybe_op_stack_trace_callback.is_some(); // First let's create an `OpState` and contribute to it from extensions... let _phase = startup_phase_begin(); let mut op_state = OpState::new(options.maybe_op_stack_trace_callback); let unrefed_ops = op_state.unrefed_ops.clone(); let lazy_extensions = extension_set::setup_op_state(&mut op_state, &mut extensions); startup_phase_end(_phase, "setup_op_state"); // Load the sources and source maps let mut files_loaded = Vec::with_capacity(128); let loader = options .module_loader .unwrap_or_else(|| Rc::new(NoopModuleLoader)); let mut source_mapper = SourceMapper::new(loader.clone()); let _phase = startup_phase_begin(); let (maybe_startup_snapshot, mut sidecar_data) = options .startup_snapshot .take() .map(snapshot::deconstruct) .unzip(); startup_phase_end(_phase, "snapshot::deconstruct"); let _phase = startup_phase_begin(); let mut sources = extension_set::into_sources_and_source_maps( options.extension_transpiler.as_deref(), &extensions, sidecar_data.as_ref().map(|s| &*s.snapshot_data.extensions), externalize_lazy_js, |source| { mark_as_loaded_from_fs_during_snapshot(&mut files_loaded, &source.code) }, )?; startup_phase_end(_phase, "into_sources_and_source_maps"); for loaded_source in sources .js .iter() .chain(sources.esm.iter()) .chain(sources.lazy_esm.iter()) .filter(|s| s.maybe_source_map.is_some()) { source_mapper.add_ext_source_map( loaded_source.specifier.try_clone().unwrap(), loaded_source.maybe_source_map.clone().unwrap(), ); } // ...now let's set up ` JsRuntimeState`, we'll need to set some fields // later, after `JsRuntime` is all set up... let waker = op_state.waker.clone(); let op_state = Rc::new(RefCell::new(op_state)); let (eval_context_get_code_cache_cb, eval_context_set_code_cache_cb) = options .eval_context_code_cache_cbs .map(|cbs| (Some(cbs.0), Some(cbs.1))) .unwrap_or_default(); let state_rc = Rc::new(JsRuntimeState { source_mapper: Rc::new(RefCell::new(source_mapper)), shared_array_buffer_store: options.shared_array_buffer_store, compiled_wasm_module_store: options.compiled_wasm_module_store, wait_for_inspector_disconnect_callback: options .wait_for_inspector_disconnect_callback, op_state: op_state.clone(), validate_import_attributes_cb: options.validate_import_attributes_cb, custom_module_evaluation_cb: options.custom_module_evaluation_cb, vm_dynamic_import_callbacks: Default::default(), next_vm_dynamic_import_callback_id: Cell::new(1), external_module_import_meta_cb: Cell::new(None), eval_context_get_code_cache_cb: RefCell::new( eval_context_get_code_cache_cb, ), eval_context_code_cache_ready_cb: RefCell::new( eval_context_set_code_cache_cb, ), waker: waker.clone(), foreground_tasks: Default::default(), // Some fields are initialized later after isolate is created inspector: None.into(), has_inspector: false.into(), cppgc_template: None.into(), function_templates: Default::default(), callsite_prototype: None.into(), lazy_extensions, tla_stall_retries: Cell::new(0), }); // ...now we're moving on to ops; set them up, create `OpCtx` for each op // and get ready to actually create V8 isolate... let (op_decls, mut op_method_decls) = extension_set::init_ops(crate::ops_builtin::BUILTIN_OPS, &mut extensions); let op_driver = Rc::new(OpDriverImpl::default()); let op_metrics_factory_fn = options.op_metrics_factory_fn.take(); let (mut op_ctxs, methods_ctx_offset) = extension_set::create_op_ctxs( op_decls, &mut op_method_decls, op_metrics_factory_fn, op_driver.clone(), op_state.clone(), state_rc.clone(), enable_stack_trace_in_ops, ); // ...ops are now almost fully set up; let's create a V8 isolate... let ( global_template_middleware, global_object_middlewares, additional_references, ) = extension_set::get_middlewares_and_external_refs(&mut extensions); // Capture the extension, op and source counts. `source_count` MUST mirror // the number of `v8::OneByteConst` external strings produced by // [`bindings::externalize_sources`] for the same `will_snapshot` value: // `js + esm + lazy_esm`, plus `lazy_loaded_js` only when building the // snapshot (where those scripts are externalized so consumed ones bake into // the snapshot as clean external sources). At runtime `lazy_loaded_js` is // not externalized here. let extensions = extensions.iter().map(|e| e.name).collect(); let op_count = op_ctxs.len(); let source_count = sources.js.len() + sources.esm.len() + sources.lazy_esm.len() + if externalize_lazy_js { sources.lazy_js.len() } else { 0 }; let addl_refs_count = additional_references.len(); let ops_in_snapshot = sidecar_data .as_ref() .map(|d| d.snapshot_data.op_count) .unwrap_or_default(); let sources_in_snapshot = sidecar_data .as_ref() .map(|d| d.snapshot_data.source_count) .unwrap_or_default(); let snapshot_sources: Vec<&[u8]> = sidecar_data .as_mut() .map(|s| std::mem::take(&mut s.snapshot_data.external_strings)) .unwrap_or_default(); ( isolate_allocations.externalized_sources, isolate_allocations.original_sources, isolate_allocations.lazy_source_specifiers, ) = bindings::externalize_sources( &mut sources, snapshot_sources, externalize_lazy_js, ); let external_references = bindings::create_external_references( &op_ctxs, &additional_references, &isolate_allocations.externalized_sources, ops_in_snapshot, sources_in_snapshot, ); let has_snapshot = maybe_startup_snapshot.is_some(); let _phase = startup_phase_begin(); let mut isolate = setup::create_isolate( will_snapshot, options.create_params.take(), maybe_startup_snapshot, external_references.into(), ); startup_phase_end(_phase, "create_isolate (v8 snapshot deser)"); let isolate_ptr = unsafe { isolate.as_raw_isolate_ptr() }; // Register this isolate in the global platform registry so V8 background // threads can queue foreground tasks. The task queue Arc is already shared // with JsRuntimeState (created above) so the event loop can drain it // without touching the global map. setup::register_isolate( setup::isolate_ptr_to_key(isolate_ptr), waker.clone(), tokio::runtime::Handle::try_current().ok(), state_rc.foreground_tasks.clone(), ); // ...isolate is fully set up, we can forward its pointer to the ops to finish // their' setup... for op_ctx in op_ctxs.iter_mut() { op_ctx.isolate = isolate_ptr; } op_state.borrow_mut().put(isolate_ptr); // ...once ops and isolate are set up, we can create a `ContextState`... let context_state = Rc::new(ContextState::new( op_driver.clone(), isolate_ptr, op_ctxs, op_method_decls, methods_ctx_offset, op_state.borrow().external_ops_tracker.clone(), unrefed_ops, )); // TODO(bartlomieju): factor out // Add the task spawners to the OpState let spawner = context_state .task_spawner_factory .clone() .new_same_thread_spawner(); op_state.borrow_mut().put(spawner); let spawner = context_state .task_spawner_factory .clone() .new_cross_thread_spawner(); op_state.borrow_mut().put(spawner); // ...and with `ContextState` available we can set up V8 context... let _phase = startup_phase_begin(); let mut snapshotted_data = None; let main_context = { v8::scope!(let scope, &mut isolate); let cppgc_template = crate::cppgc::make_cppgc_template(scope); state_rc .cppgc_template .borrow_mut() .replace(v8::Global::new(scope, cppgc_template)); let _p_ctx = startup_phase_begin(); let context = create_context( scope, &global_template_middleware, &global_object_middlewares, has_snapshot, ); startup_phase_end(_p_ctx, " V8 Context::from_snapshot"); // Get module map data from the snapshot if let Some(raw_data) = sidecar_data { let _p_ld = startup_phase_begin(); snapshotted_data = Some(snapshot::load_snapshotted_data_from_snapshot( scope, context, raw_data, )); startup_phase_end(_p_ld, " load_data loop (get_context_data x N)"); } v8::Global::new(scope, context) }; startup_phase_end(_phase, "create_context+load_data"); let main_realm = { v8::scope_with_context!(context_scope, &mut isolate, &main_context); let scope = context_scope; let context = v8::Local::new(scope, &main_context); let callsite_prototype = crate::error::make_callsite_prototype(scope); state_rc .callsite_prototype .borrow_mut() .replace(v8::Global::new(scope, callsite_prototype)); // ...followed by creation of `Deno.core` namespace, as well as internal // infrastructure to provide JavaScript bindings for ops... let _phase = startup_phase_begin(); if init_mode == InitMode::New { bindings::initialize_deno_core_namespace(scope, context, init_mode); bindings::initialize_primordials_and_infra(scope)?; } // If we're creating a new runtime or there are new ops to register // set up JavaScript bindings for them. if init_mode.needs_ops_bindings() { bindings::initialize_deno_core_ops_bindings( scope, context, &context_state.op_ctxs, &context_state.op_method_decls, methods_ctx_offset, &mut state_rc.function_templates.borrow_mut(), will_snapshot, ); } else if !will_snapshot { // Snapshots built against V8 14.9+ bake the slow version of each op // function (see `op_ctx_template`); the fast-call overloads must be // re-attached at runtime. That pass (~0.9ms, ~1.6k V8 functions) only // helps ops accessed at runtime — baked modules already captured their // slow refs at snapshot eval — so we DEFER it until the first residual // ext-module load (`ensure_fast_ops_upgraded`). A program that loads no // residual module (e.g. an empty script) never pays for it. // // Capture the refs the deferred upgrade needs NOW, while `Deno.core` is // still on the global — it's scrubbed from the public `Deno` after // bootstrap, so the post-bootstrap deferred caller can't read them. let (ops_obj, stub_fn) = bindings::snapshotted_fast_op_refs(scope, context); *context_state.deferred_fast_ops.borrow_mut() = Some(( v8::Global::new(scope, ops_obj), v8::Global::new(scope, stub_fn), )); } // The deferred upgrade applies ONLY when loading a snapshot at runtime. // In the fresh-bind path the ops were just bound with fast calls, and in // the snapshot-build path we must keep the slow ops (fast-call functions // can't be serialized) and must NOT upgrade even though `op_load_ext_script` // runs during the build. Mark those paths done so `ensure_fast_ops_upgraded` // is a no-op for them. if init_mode.needs_ops_bindings() || will_snapshot { context_state.fast_ops_upgraded.set(true); } startup_phase_end(_phase, "ops_bindings (fast call upgrade)"); // SAFETY: Initialize the context state slot. unsafe { context.set_aligned_pointer_in_embedder_data( super::jsrealm::CONTEXT_STATE_SLOT_INDEX, Rc::into_raw(context_state.clone()) as *mut c_void, ); } let inspector = if options.inspector { Some(JsRuntimeInspector::new( isolate_ptr, scope, context, options.is_main, options.worker_id, )) } else { None }; // ...now that JavaScript bindings to ops are available we can deserialize // modules stored in the snapshot (because they depend on the ops and external // references must match properly) and recreate a module map... let exception_state = context_state.exception_state.clone(); let module_map = Rc::new(ModuleMap::new( loader, state_rc.source_mapper.clone(), exception_state.clone(), will_snapshot, )); let _phase = startup_phase_begin(); if let Some((snapshotted_data, mut data_store)) = snapshotted_data { *exception_state.js_handled_promise_rejection_cb.borrow_mut() = snapshotted_data .js_handled_promise_rejection_cb .map(|cb| data_store.get(scope, cb)); module_map.update_with_snapshotted_data( scope, &mut data_store, snapshotted_data.module_map_data, ); if let Some(index) = snapshotted_data.ext_import_meta_proto { *context_state.ext_import_meta_proto.borrow_mut() = Some(data_store.get(scope, index)); } state_rc .function_templates .borrow_mut() .update_with_snapshotted_data( scope, &mut data_store, snapshotted_data.function_templates_data, ); let mut mapper = state_rc.source_mapper.borrow_mut(); for (key, map) in snapshotted_data.ext_source_maps { mapper.add_ext_source_map(ModuleName::from_static(key), map.into()); } } startup_phase_end(_phase, "load module map from snapshot"); if context_state.ext_import_meta_proto.borrow().is_none() { let null = v8::null(scope); let obj = v8::Object::with_prototype_and_properties( scope, null.into(), &[], &[], ); *context_state.ext_import_meta_proto.borrow_mut() = Some(v8::Global::new(scope, obj)); } // SAFETY: Set the module map slot in the context unsafe { context.set_aligned_pointer_in_embedder_data( super::jsrealm::MODULE_MAP_SLOT_INDEX, Rc::into_raw(module_map.clone()) as *mut c_void, ); } // ...we are ready to create a "realm" for the context... let main_realm = { let main_realm = JsRealmInner::new( context_state, main_context, module_map.clone(), state_rc.function_templates.clone(), ); // TODO(bartlomieju): why is this done in here? Maybe we can hoist it out? state_rc.has_inspector.set(inspector.is_some()); *state_rc.inspector.borrow_mut() = inspector; main_realm }; let main_realm = JsRealm::new(main_realm); scope.set_data( STATE_DATA_OFFSET, Rc::into_raw(state_rc.clone()) as *mut c_void, ); main_realm }; // ...which allows us to create the `JsRuntime` instance... let mut js_runtime = JsRuntime { inner: InnerIsolateState { will_snapshot, op_count, extensions, source_count, addl_refs_count, main_realm: ManuallyDrop::new(main_realm), state: ManuallyDropRc(ManuallyDrop::new(state_rc)), v8_isolate: ManuallyDrop::new(isolate), }, allocations: isolate_allocations, files_loaded_from_fs_during_snapshot: vec![], is_main_runtime: options.is_main, }; // ...we're almost done with the setup, all that's left is to execute // internal JS and then execute code provided by extensions... { let realm = JsRealm::clone(&js_runtime.inner.main_realm); let context_global = realm.context(); let module_map = realm.0.module_map(); // TODO(bartlomieju): this is somewhat duplicated in `bindings::initialize_context`, // but for migration period we need to have ops available in both `Deno.core.ops` // as well as have them available in "virtual ops module" // if !matches!( // self.init_mode, // InitMode::FromSnapshot { // skip_op_registration: true // } // ) { if init_mode == InitMode::New { let _phase = startup_phase_begin(); js_runtime .execute_virtual_ops_module(context_global, module_map.clone()); startup_phase_end(_phase, "execute_virtual_ops_module"); } if init_mode == InitMode::New { let _phase = startup_phase_begin(); js_runtime.execute_builtin_sources( &realm, &module_map, &mut files_loaded, )?; startup_phase_end(_phase, "execute_builtin_sources"); } let _phase = startup_phase_begin(); js_runtime.store_js_callbacks(&realm, will_snapshot); startup_phase_end(_phase, "store_js_callbacks"); // Register residual `lazy_loaded_*` sources from the snapshot bundle. // These are the files that were declared on extensions but were not // consumed at snapshot build time, so the snapshot blob doesn't carry // them and `core.loadExtScript()` / lazy ESM imports need to find them // here. (When there is no snapshot, the same sources are loaded from // disk through `Extension.lazy_loaded_*_files` instead.) module_map.add_residual_lazy_loaded_sources( options.residual_lazy_js_sources, options.residual_lazy_esm_sources, ); let _phase = startup_phase_begin(); js_runtime.init_extension_js( &realm, &module_map, sources, options.extension_code_cache, )?; startup_phase_end(_phase, "init_extension_js"); } startup_phase_end(_phase_total, "TOTAL JsRuntime::new_inner"); if will_snapshot { js_runtime.files_loaded_from_fs_during_snapshot = files_loaded; } // Every runtime gets a uv loop for the libuv compat layer (timers, // I/O, setImmediate, etc.). Stored in both OpState (for ext/node ops // that need the raw pointer) and ContextState (for event loop phases). if !will_snapshot { // SAFETY: zeroed memory is valid for uv_loop_t before uv_loop_init. let mut uv_loop = Box::new(unsafe { std::mem::zeroed::<uv_compat::UvLoop>() }); // SAFETY: uv_loop points to valid zeroed memory. unsafe { uv_compat::uv_loop_init(&mut *uv_loop) }; let loop_ptr: *mut uv_compat::UvLoop = &mut *uv_loop; // SAFETY: loop_ptr is valid and initialized. unsafe { js_runtime.register_uv_loop(loop_ptr) }; js_runtime.inner.state.op_state.borrow_mut().put(uv_loop); js_runtime .inner .state .op_state .borrow_mut() .put(uv_compat::AsyncId::default()); } // ...and we've made it; `JsRuntime` is ready to execute user code. Ok(js_runtime) } /// If extensions were initialized with `lazy_init`, they need to be /// fully initialized with this method. pub fn lazy_init_extensions( &self, ext_args: Vec<ExtensionArguments>, ) -> Result<(), CoreError> { if ext_args.len() != self.inner.state.lazy_extensions.len() { return Err( CoreErrorKind::ExtensionLazyInitCountMismatch( ExtensionLazyInitCountMismatchError { lazy_init_extensions_len: self.inner.state.lazy_extensions.len(), arguments_len: ext_args.len(), }, ) .into_box(), ); } let mut state = self.inner.state.op_state.borrow_mut(); for (mut args, expected_name) in ext_args .into_iter() .zip(self.inner.state.lazy_extensions.iter()) { if args.name != *expected_name { return Err( CoreErrorKind::ExtensionLazyInitOrderMismatch( ExtensionLazyInitOrderMismatchError { expected: expected_name, actual: args.name, }, ) .into_box(), ); } let Some(f) = args.op_state_fn.take() else { continue; }; f(&mut state); } Ok(()) } pub fn set_eval_context_code_cache_cbs( &self, eval_context_code_cache_cbs: Option<( EvalContextGetCodeCacheCb, EvalContextCodeCacheReadyCb, )>, ) { let (eval_context_get_code_cache_cb, eval_context_set_code_cache_cb) = eval_context_code_cache_cbs .map(|cbs| (Some(cbs.0), Some(cbs.1))) .unwrap_or_default(); *self.inner.state.eval_context_get_code_cache_cb.borrow_mut() = eval_context_get_code_cache_cb; *self .inner .state .eval_context_code_cache_ready_cb .borrow_mut() = eval_context_set_code_cache_cb; } #[cfg(test)] #[inline] pub(crate) fn module_map(&self) -> Rc<ModuleMap> { self.inner.main_realm.0.module_map() } #[inline] pub fn main_context(&self) -> v8::Global<v8::Context> { self.inner.main_realm.0.context().clone() } #[cfg(test)] pub(crate) fn main_realm(&self) -> JsRealm { JsRealm::clone(&self.inner.main_realm) } #[inline] pub fn v8_isolate(&mut self) -> &mut v8::OwnedIsolate { &mut self.inner.v8_isolate } #[inline] fn v8_isolate_ptr(&mut self) -> v8::UnsafeRawIsolatePtr { unsafe { self.inner.v8_isolate.as_raw_isolate_ptr() } } #[inline] pub fn inspector(&self) -> Rc<JsRuntimeInspector> { self.inner.state.inspector() } #[inline] pub fn wait_for_inspector_disconnect(&self) { if let Some(callback) = self .inner .state .wait_for_inspector_disconnect_callback .as_ref() { callback(); } } pub fn runtime_activity_stats_factory(&self) -> RuntimeActivityStatsFactory { RuntimeActivityStatsFactory { context_state: self.inner.main_realm.0.context_state.clone(), op_state: self.inner.state.op_state.clone(), } } // TODO(bartlomieju): remove, instead `JsRuntimeForSnapshot::new` should return // a struct that contains this data. pub(crate) fn files_loaded_from_fs_during_snapshot(&self) -> &[&'static str] { &self.files_loaded_from_fs_during_snapshot } /// Returns the specifiers of every `lazy_loaded_esm` / `lazy_loaded_js` file /// whose source was compiled into the V8 snapshot during snapshot creation. pub(crate) fn consumed_lazy_specifiers(&self) -> Vec<String> { let module_map = self.inner.main_realm.0.module_map(); let data = module_map.get_data().borrow(); let mut set: Vec<String> = data .consumed_lazy_specifiers .borrow() .iter() .cloned() .collect(); set.sort(); set } /// Create a synthetic module - `ext:core/ops` - that exports all ops registered /// with the runtime. fn execute_virtual_ops_module( &mut self, context_global: &v8::Global<v8::Context>, module_map: Rc<ModuleMap>, ) { scope!(scope, self); let context_local = v8::Local::new(scope, context_global); let context_state = JsRealm::state_from_scope(scope); let global = context_local.global(scope); let synthetic_module_exports = create_exports_for_ops_virtual_module( &context_state.op_ctxs, &context_state.op_method_decls, context_state.methods_ctx_offset, scope, global, ); let mod_id = module_map.new_synthetic_module( scope, VIRTUAL_OPS_MODULE_NAME, crate::ModuleType::JavaScript, synthetic_module_exports, ); module_map.mod_evaluate_sync(scope, mod_id).unwrap(); } /// Executes built-in scripts and ES modules - this code is required for /// ops system to work properly, as well as providing necessary bindings /// on the `Deno.core` namespace. /// /// This is not done in [`bindings::initialize_primordials_and_infra`] because /// some of this code already relies on certain ops being available. fn execute_builtin_sources( &mut self, _realm: &JsRealm, module_map: &Rc<ModuleMap>, files_loaded: &mut Vec<&'static str>, ) -> Result<(), CoreError> { scope!(scope, self); for source_file in &BUILTIN_SOURCES { let name = source_file.specifier.v8_string(scope).unwrap(); let source = source_file.source.v8_string(scope).unwrap(); let origin = script_origin(scope, name, false, None); let script = v8::Script::compile(scope, source, Some(&origin)) .ok_or(CoreModuleParseError(source_file.specifier))?; script .run(scope) .ok_or(CoreModuleExecuteError(source_file.specifier))?; } for file_source in &BUILTIN_ES_MODULES { mark_as_loaded_from_fs_during_snapshot(files_loaded, &file_source.code); module_map.lazy_load_es_module_with_code( scope, file_source.specifier, file_source.load()?, None, )?; } Ok(()) } /// Initializes JS of provided Extensions in the given realm. async fn init_extension_js_inner( &mut self, realm: &JsRealm, module_map: &Rc<ModuleMap>, loaded_sources: LoadedSources, ext_code_cache: Option<Rc<dyn ExtCodeCache>>, ) -> Result<(), CoreError> { // First, add all the lazy ESM for source in loaded_sources.lazy_esm { module_map.add_lazy_loaded_esm_source(source.specifier, source.code); } // Add lazy-loaded scripts (loaded on demand via loadExtScript()) for source in loaded_sources.lazy_js { module_map.add_lazy_loaded_script_source(source.specifier, source.code); } // Register `synthetic_esm` mappings so imports of the declared module // specifiers resolve via the synthetic-module dispatch in // `resolve_callback` instead of going through normal ESM loading. for (module_spec, backing_spec) in loaded_sources.synthetic_esm { module_map.add_synthetic_esm_module(module_spec, backing_spec); } // Temporarily override the loader of the `ModuleMap` so we can load // extension code. // TODO(bartlomieju): maybe this should be a method on the `ModuleMap`, // instead of explicitly changing the `.loader` field? let loader = module_map.loader.borrow().clone(); let mut modules = Vec::with_capacity(loaded_sources.esm.len()); let mut sources = Vec::with_capacity(loaded_sources.esm.len()); for esm in loaded_sources.esm { modules.push(ModuleSpecifier::parse(&esm.specifier).unwrap()); sources.push((esm.specifier, esm.code)); } let ext_loader = Rc::new(ExtModuleLoader::new(sources, ext_code_cache.clone())); *module_map.loader.borrow_mut() = ext_loader.clone(); module_map.set_loading_internal_modules(true); // Next, load the extension modules as side modules (but do not execute them) for module in modules { // eprintln!("loading module: {module}"); realm .load_side_es_module_from_code(self.v8_isolate(), module.into(), None) .await?; } // Execute extension scripts for source in loaded_sources.js { match &ext_code_cache { Some(ext_code_cache) => { let specifier = ModuleSpecifier::parse(&source.specifier)?; realm.execute_script_with_cache( self.v8_isolate(), specifier, source.code, &|specifier, code| { ext_code_cache.get_code_cache_info(specifier, code, false) }, &|specifier, hash, code_cache| { ext_code_cache .code_cache_ready(specifier, hash, code_cache, false) }, )?; } _ => { realm.execute_script( self.v8_isolate(), source.specifier, source.code, )?; } } } // ...then execute all entry points for specifier in loaded_sources.esm_entry_points { let Some(mod_id) = module_map.get_id(&specifier, RequestedModuleType::None) else { return Err( CoreErrorKind::MissingFromModuleMap(specifier.to_string()).into_box(), ); }; let isolate = self.v8_isolate(); jsrealm::context_scope!(scope, realm, isolate); module_map.mod_evaluate_sync(scope, mod_id)?; let mut cx = Context::from_waker(Waker::noop()); // poll once so code cache is populated. the `ExtCodeCache` trait is sync, so // the `CodeCacheReady` futures will always finish on the first poll. let _ = module_map.poll_progress(&mut cx, scope); } #[cfg(debug_assertions)] { jsrealm::context_scope!(scope, realm, self.v8_isolate()); module_map.check_all_modules_evaluated(scope)?; } let module_map = realm.0.module_map(); module_map.set_loading_internal_modules(false); *module_map.loader.borrow_mut() = loader; ext_loader.finalize()?; Ok(()) } /// Initializes JS of provided Extensions in the given realm. fn init_extension_js( &mut self, realm: &JsRealm, module_map: &Rc<ModuleMap>, loaded_sources: LoadedSources, ext_code_cache: Option<Rc<dyn ExtCodeCache>>, ) -> Result<(), CoreError> { futures::executor::block_on(self.init_extension_js_inner( realm, module_map, loaded_sources, ext_code_cache, ))?; Ok(()) } pub fn eval<'s, 'i, T>( scope: &mut v8::PinScope<'s, 'i>, code: &str, ) -> Option<v8::Local<'s, T>> where v8::Local<'s, T>: TryFrom<v8::Local<'s, v8::Value>, Error = v8::DataError>, { v8::escapable_handle_scope!(let scope, scope); let source = v8::String::new(scope, code).unwrap(); let script = v8::Script::compile(scope, source, None).unwrap(); let v = script.run(scope)?; scope.escape(v).try_into().ok() } /// Grab and store JavaScript bindings to callbacks necessary for the /// JsRuntime to operate properly. fn store_js_callbacks(&mut self, realm: &JsRealm, will_snapshot: bool) { let ( event_loop_tick_cb, process_timers_cb, drain_next_tick_and_macrotasks_cb, handle_rejections_cb, build_custom_error_cb, error_constructors, run_immediate_callbacks_cb, wasm_instance_fn, wasm_instances_map, ) = { scope!(scope, self); let context = realm.context(); let context_local = v8::Local::new(scope, context); let global = context_local.global(scope); let deno_obj: v8::Local<v8::Object> = bindings::get(scope, global, DENO, "Deno"); let core_obj: v8::Local<v8::Object> = bindings::get(scope, deno_obj, CORE, "Deno.core"); let event_loop_tick_cb: v8::Local<v8::Function> = bindings::get( scope, core_obj, EVENT_LOOP_TICK, "Deno.core.__eventLoopTick", ); let process_timers_cb: v8::Local<v8::Function> = bindings::get( scope, core_obj, PROCESS_TIMERS, "Deno.core.__processTimers", ); let drain_next_tick_and_macrotasks_cb: v8::Local<v8::Function> = bindings::get( scope, core_obj, DRAIN_NEXT_TICK_AND_MACROTASKS, "Deno.core.__drainNextTickAndMacrotasks", ); let handle_rejections_cb: v8::Local<v8::Function> = bindings::get( scope, core_obj, HANDLE_REJECTIONS, "Deno.core.__handleRejections", ); let build_custom_error_cb: v8::Local<v8::Function> = bindings::get( scope, core_obj, BUILD_CUSTOM_ERROR, "Deno.core.buildCustomError", ); let error_constructors: v8::Local<v8::Object> = bindings::get( scope, core_obj, ERROR_CONSTRUCTORS, "Deno.core.errorConstructors", ); let run_immediate_callbacks_cb: v8::Local<v8::Function> = bindings::get( scope, core_obj, RUN_IMMEDIATE_CALLBACKS, "Deno.core.runImmediateCallbacks", ); let mut wasm_instance_fn = None; let mut wasm_instances_map = None; if !will_snapshot { let key = WEBASSEMBLY.v8_string(scope).unwrap(); if let Some(web_assembly_obj_value) = global.get(scope, key.into()) { let maybe_web_assembly_object = TryInto::<v8::Local<v8::Object>>::try_into(web_assembly_obj_value); if let Ok(web_assembly_object) = maybe_web_assembly_object { wasm_instance_fn = Some(bindings::get::<v8::Local<v8::Function>>( scope, web_assembly_object, INSTANCE, "WebAssembly.Instance", )); // Registry mapping a Wasm module's namespace to its instance // exports, shared by all synthetic `.wasm` modules in this realm // via `import.meta.wasmInstances`. See `ContextState` for details. let weak_map_fn = bindings::get::<v8::Local<v8::Function>>( scope, global, WEAK_MAP, "WeakMap", ); wasm_instances_map = weak_map_fn.new_instance(scope, &[]); } } } // SAFETY: The backing memory lives on ContextState which outlives // all JS. We pass a no-op destructor since ContextState owns // the memory. extern "C" fn _no_op_deleter( _data: *mut std::ffi::c_void, _len: usize, _deleter_data: *mut std::ffi::c_void, ) { } // Create a shared Uint8Array backed by ContextState::tick_info and // pass it to JS via __setTickInfo so JS can read/write // hasTickScheduled without crossing the JS-to-Rust boundary. { let state_rc = realm.0.state(); let tick_info_ptr = state_rc.tick_info.as_ptr() as *mut std::ffi::c_void; let backing_store = unsafe { v8::ArrayBuffer::new_backing_store_from_ptr( tick_info_ptr, 2, _no_op_deleter, std::ptr::null_mut(), ) }; let backing_store_shared = backing_store.make_shared(); let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared); let tick_info_array = v8::Uint8Array::new(scope, ab, 0, 2).unwrap(); let set_tick_info_fn: v8::Local<v8::Function> = bindings::get( scope, core_obj, SET_TICK_INFO, "Deno.core.__setTickInfo", ); let undefined: v8::Local<v8::Value> = v8::undefined(scope).into(); set_tick_info_fn.call(scope, undefined, &[tick_info_array.into()]); // Clean up the init-only setter from Deno.core let key = SET_TICK_INFO.v8_string(scope).unwrap(); core_obj.delete(scope, key.into()); } // Create a shared Uint32Array backed by ContextState::immediate_info // and pass it to JS via __setImmediateInfo. { let state_rc = realm.0.state(); let imm_info_ptr = state_rc.immediate_info.as_ptr() as *mut std::ffi::c_void; let imm_byte_len = 3 * std::mem::size_of::<u32>(); let backing_store = unsafe { v8::ArrayBuffer::new_backing_store_from_ptr( imm_info_ptr, imm_byte_len, _no_op_deleter, std::ptr::null_mut(), ) }; let backing_store_shared = backing_store.make_shared(); let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared); let imm_info_array = v8::Uint32Array::new(scope, ab, 0, 3).unwrap(); let set_imm_info_fn: v8::Local<v8::Function> = bindings::get( scope, core_obj, SET_IMMEDIATE_INFO, "Deno.core.__setImmediateInfo", ); let undefined: v8::Local<v8::Value> = v8::undefined(scope).into(); set_imm_info_fn.call(scope, undefined, &[imm_info_array.into()]); let key = SET_IMMEDIATE_INFO.v8_string(scope).unwrap(); core_obj.delete(scope, key.into()); } // Create a shared Int32Array backed by ContextState::timer_info // and pass it to JS via __setTimerInfo. { let state_rc = realm.0.state(); let timer_info_ptr = state_rc.timer_info.as_ptr() as *mut std::ffi::c_void; let timer_byte_len = std::mem::size_of::<i32>(); let backing_store = unsafe { v8::ArrayBuffer::new_backing_store_from_ptr( timer_info_ptr, timer_byte_len, _no_op_deleter, std::ptr::null_mut(), ) }; let backing_store_shared = backing_store.make_shared(); let ab = v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared); let timer_info_array = v8::Int32Array::new(scope, ab, 0, 1).unwrap(); let set_timer_info_fn: v8::Local<v8::Function> = bindings::get( scope, core_obj, SET_TIMER_INFO, "Deno.core.__setTimerInfo", ); let undefined: v8::Local<v8::Value> = v8::undefined(scope).into(); set_timer_info_fn.call(scope, undefined, &[timer_info_array.into()]); let key = SET_TIMER_INFO.v8_string(scope).unwrap(); core_obj.delete(scope, key.into()); } ( v8::Global::new(scope, event_loop_tick_cb), v8::Global::new(scope, process_timers_cb), v8::Global::new(scope, drain_next_tick_and_macrotasks_cb), v8::Global::new(scope, handle_rejections_cb), v8::Global::new(scope, build_custom_error_cb), v8::Global::new(scope, error_constructors), v8::Global::new(scope, run_immediate_callbacks_cb), wasm_instance_fn.map(|f| v8::Global::new(scope, f)), wasm_instances_map.map(|m| v8::Global::new(scope, m)), ) }; // Put global handles in the realm's ContextState let state_rc = realm.0.state(); state_rc .js_event_loop_tick_cb .borrow_mut() .replace(event_loop_tick_cb); state_rc .js_process_timers_cb .borrow_mut() .replace(process_timers_cb); state_rc .js_drain_next_tick_and_macrotasks_cb .borrow_mut() .replace(drain_next_tick_and_macrotasks_cb); state_rc .js_handle_rejections_cb .borrow_mut() .replace(handle_rejections_cb); state_rc .exception_state .js_build_custom_error_cb .borrow_mut() .replace(build_custom_error_cb); state_rc .exception_state .js_error_constructors .borrow_mut() .replace(error_constructors); state_rc .run_immediate_callbacks_cb .borrow_mut() .replace(run_immediate_callbacks_cb); if let Some(wasm_instance_fn) = wasm_instance_fn { state_rc .wasm_instance_fn .borrow_mut() .replace(wasm_instance_fn); } if let Some(wasm_instances_map) = wasm_instances_map { state_rc .wasm_instances_map .borrow_mut() .replace(wasm_instances_map); } } /// Returns the runtime's op state, which can be used to maintain ops /// and access resources between op calls. pub fn op_state(&self) -> Rc<RefCell<OpState>> { self.inner.state.op_state.clone() } /// Returns the raw `uv_loop_t` pointer registered with this runtime, /// or `None` if no loop is registered. pub fn uv_loop_ptr(&self) -> Option<*mut uv_compat::uv_loop_t> { self.inner.main_realm.0.context_state.uv_loop_ptr.get() } /// Returns the runtime's source mapper, which can be used to apply /// source maps to file locations. pub fn source_mapper(&self) -> Rc<RefCell<crate::source_map::SourceMapper>> { self.inner.state.source_mapper.clone() } /// Register a `uv_loop_t` with the runtime so that its event loop phases /// (timers, I/O, idle, prepare, check, close) are driven by /// `poll_event_loop`. /// /// A `v8::Global<v8::Context>` is created via `Global::into_raw()` and /// stored in `loop_.data` so that libuv-style callbacks can retrieve /// the context. The raw Global is dropped during realm cleanup. /// /// # Safety /// `loop_ptr` must be a valid, initialized `uv_loop_t` pointer that /// outlives the runtime. pub unsafe fn register_uv_loop( &mut self, loop_ptr: *mut uv_compat::uv_loop_t, ) { let realm = &self.inner.main_realm; let context_state = &realm.0.context_state; let inner_ptr = unsafe { uv_compat::uv_loop_get_inner_ptr(loop_ptr) }; let uv_inner = inner_ptr as *const uv_compat::UvLoopInner; context_state.uv_loop_inner.set(Some(uv_inner)); context_state.uv_loop_ptr.set(Some(loop_ptr)); let global_ctx = realm.0.context().clone(); let raw = global_ctx.into_raw(); // SAFETY: `loop_ptr` is a valid, initialized `uv_loop_t` guaranteed // by the caller. `raw` is a persistent-handle slot pointer from // `Global::into_raw()` — V8 keeps it updated across GC cycles. // The raw Global is reconstructed and dropped in `JsRealmInner::destroy`. unsafe { (*loop_ptr).data = raw.as_ptr() as *mut std::ffi::c_void; } // Create the check handle for setImmediate. // JS controls start/stop/ref/unref via op_immediate_check. // SAFETY: loop_ptr is valid per caller contract. let check_handle = unsafe { uv_compat::ImmediateCheckHandle::new(loop_ptr) }; *context_state.immediate_check_handle.borrow_mut() = Some(check_handle); } /// Returns the runtime's op names, ordered by OpId. pub fn op_names(&self) -> Vec<&'static str> { let state = &self.inner.main_realm.0.context_state; state.op_ctxs.iter().map(|o| o.decl.name).collect() } /// Executes traditional, non-ECMAScript-module JavaScript code, This code executes in /// the global scope by default, and it is possible to maintain local JS state and invoke /// this method multiple times. /// /// `name` may be any type that implements the internal [`IntoModuleName`] trait. /// It can be a filepath or any other string, but it is required to be 7-bit ASCII, eg. /// /// - "/some/file/path.js" /// - "<anon>" /// - "[native code]" /// /// The same `name` value can be used for multiple executions. /// /// The source may be any type that implements the internal [`IntoModuleCodeString`] trait, but /// it is highly recommended that embedders use the [`ascii_str!`] to generate the fastest version /// of strings for v8 to handle. If the strings are not static, you may also pass a [`String`] /// generated by the [`format!`] macro. pub fn execute_script( &mut self, name: impl IntoModuleName, source_code: impl IntoModuleCodeString, ) -> Result<v8::Global<v8::Value>, Box<JsError>> { let isolate = &mut self.inner.v8_isolate; self.inner.main_realm.execute_script( isolate, name.into_module_name(), source_code.into_module_code(), ) } /// Call a function and return a future resolving with the return value of the /// function. If the function returns a promise, the future will resolve only once the /// event loop resolves the underlying promise. If the future rejects, the future will /// resolve with the underlying error. /// /// The event loop must be polled seperately for this future to resolve. If the event loop /// is not polled, the future will never make progress. pub fn call( &mut self, function: &v8::Global<v8::Function>, ) -> impl Future<Output = Result<v8::Global<v8::Value>, Box<JsError>>> + use<> { self.call_with_args(function, &[]) } /// Call a function and returns a future resolving with the return value of the /// function. If the function returns a promise, the future will resolve only once the /// event loop resolves the underlying promise. If the future rejects, the future will /// resolve with the underlying error. /// /// The event loop must be polled seperately for this future to resolve. If the event loop /// is not polled, the future will never make progress. pub fn scoped_call( scope: &mut v8::PinScope, function: &v8::Global<v8::Function>, ) -> impl Future<Output = Result<v8::Global<v8::Value>, Box<JsError>>> + use<> { Self::scoped_call_with_args(scope, function, &[]) } /// Call a function and returns a future resolving with the return value of the /// function. If the function returns a promise, the future will resolve only once the /// event loop resolves the underlying promise. If the future rejects, the future will /// resolve with the underlying error. /// /// The event loop must be polled seperately for this future to resolve. If the event loop /// is not polled, the future will never make progress. pub fn call_with_args( &mut self, function: &v8::Global<v8::Function>, args: &[v8::Global<v8::Value>], ) -> impl Future<Output = Result<v8::Global<v8::Value>, Box<JsError>>> + use<> { scope!(scope, self); Self::scoped_call_with_args(scope, function, args) } /// Call a function and returns a future resolving with the return value of the /// function. If the function returns a promise, the future will resolve only once the /// event loop resolves the underlying promise. If the future rejects, the future will /// resolve with the underlying error. /// /// The event loop must be polled seperately for this future to resolve. If the event loop /// is not polled, the future will never make progress. pub fn scoped_call_with_args( scope: &mut v8::PinScope, function: &v8::Global<v8::Function>, args: &[v8::Global<v8::Value>], ) -> impl Future<Output = Result<v8::Global<v8::Value>, Box<JsError>>> + use<> { v8::tc_scope!(let scope, scope); let cb = function.open(scope); let this = v8::undefined(scope).into(); let promise = if args.is_empty() { cb.call(scope, this, &[]) } else { let mut local_args: SmallVec<[v8::Local<v8::Value>; 8]> = SmallVec::with_capacity(args.len()); for v in args { local_args.push(v8::Local::new(scope, v)); } cb.call(scope, this, &local_args) }; if promise.is_none() { if scope.is_execution_terminating() { let undefined = v8::undefined(scope).into(); return RcPromiseFuture::new(exception_to_err_result( scope, undefined, false, true, )); } let exception = scope.exception().unwrap(); return RcPromiseFuture::new(exception_to_err_result( scope, exception, false, true, )); } let promise = promise.unwrap(); if !promise.is_promise() { return RcPromiseFuture::new(Ok(v8::Global::new(scope, promise))); } let promise = v8::Local::<v8::Promise>::try_from(promise).unwrap(); Self::resolve_promise_inner(scope, promise) } /// Call a function. If it returns a promise, run the event loop until that /// promise is settled. If the promise rejects or there is an uncaught error /// in the event loop, return `Err(error)`. Or return `Ok(<await returned>)`. #[deprecated = "Use call"] pub async fn call_and_await( &mut self, function: &v8::Global<v8::Function>, ) -> Result<v8::Global<v8::Value>, CoreError> { let call = self.call(function); self .with_event_loop_promise(call, PollEventLoopOptions::default()) .await } /// Call a function with args. If it returns a promise, run the event loop until that /// promise is settled. If the promise rejects or there is an uncaught error /// in the event loop, return `Err(error)`. Or return `Ok(<await returned>)`. #[deprecated = "Use call_with_args"] pub async fn call_with_args_and_await( &mut self, function: &v8::Global<v8::Function>, args: &[v8::Global<v8::Value>], ) -> Result<v8::Global<v8::Value>, CoreError> { let call = self.call_with_args(function, args); self .with_event_loop_promise(call, PollEventLoopOptions::default()) .await } /// Returns the namespace object of a module. /// /// This is only available after module evaluation has completed. /// This function panics if module has not been instantiated. pub fn get_module_namespace( &mut self, module_id: ModuleId, ) -> Result<v8::Global<v8::Object>, CoreError> { let isolate = &mut self.inner.v8_isolate; self .inner .main_realm .get_module_namespace(isolate, module_id) } /// Registers a callback on the isolate when the memory limits are approached. /// Use this to prevent V8 from crashing the process when reaching the limit. /// /// Calls the closure with the current heap limit and the initial heap limit. /// The return value of the closure is set as the new limit. pub fn add_near_heap_limit_callback<C>(&mut self, cb: C) where C: FnMut(usize, usize) -> usize + 'static, { let boxed_cb = Box::new(RefCell::new(cb)); let data = boxed_cb.as_ptr() as *mut c_void; let prev = self .allocations .near_heap_limit_callback_data .replace((boxed_cb, near_heap_limit_callback::<C>)); if let Some((_, prev_cb)) = prev { self .v8_isolate() .remove_near_heap_limit_callback(prev_cb, 0); } self .v8_isolate() .add_near_heap_limit_callback(near_heap_limit_callback::<C>, data); } pub fn remove_near_heap_limit_callback(&mut self, heap_limit: usize) { if let Some((_, cb)) = self.allocations.near_heap_limit_callback_data.take() { self .v8_isolate() .remove_near_heap_limit_callback(cb, heap_limit); } } pub fn maybe_init_inspector(&mut self) { let inspector = &mut self.inner.state.inspector.borrow_mut(); if inspector.is_some() { return; } let context = self.main_context(); let isolate_ptr = unsafe { self.inner.v8_isolate.as_raw_isolate_ptr() }; v8::scope_with_context!( scope, self.inner.v8_isolate.as_mut(), context.clone(), ); let context = v8::Local::new(scope, context); self.inner.state.has_inspector.set(true); **inspector = Some(JsRuntimeInspector::new( isolate_ptr, scope, context, self.is_main_runtime, None, )); } /// Waits for the given value to resolve while polling the event loop. /// /// This future resolves when either the value is resolved or the event loop runs to /// completion. pub fn resolve( &mut self, promise: v8::Global<v8::Value>, ) -> impl Future<Output = Result<v8::Global<v8::Value>, Box<JsError>>> + use<> { scope!(scope, self); Self::scoped_resolve(scope, promise) } /// Waits for the given value to resolve while polling the event loop. /// /// This future resolves when either the value is resolved or the event loop runs to /// completion. pub fn scoped_resolve( scope: &mut v8::PinScope, promise: v8::Global<v8::Value>, ) -> impl Future<Output = Result<v8::Global<v8::Value>, Box<JsError>>> + use<> { let promise = v8::Local::new(scope, promise); if !promise.is_promise() { return RcPromiseFuture::new(Ok(v8::Global::new(scope, promise))); } let promise = v8::Local::<v8::Promise>::try_from(promise).unwrap(); Self::resolve_promise_inner(scope, promise) } /// Waits for the given value to resolve while polling the event loop. /// /// This future resolves when either the value is resolved or the event loop runs to /// completion. #[deprecated = "Use resolve"] pub async fn resolve_value( &mut self, global: v8::Global<v8::Value>, ) -> Result<v8::Global<v8::Value>, CoreError> { let resolve = self.resolve(global); self .with_event_loop_promise(resolve, PollEventLoopOptions::default()) .await } /// Given a promise, returns a future that resolves when it does. fn resolve_promise_inner<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, promise: v8::Local<'s, v8::Promise>, ) -> RcPromiseFuture { let future = RcPromiseFuture::default(); let f = future.clone(); watch_promise(scope, promise, move |scope, _rv, res| { let res = match res { Ok(l) => Ok(v8::Global::new(scope, l)), Err(e) => exception_to_err_result(scope, e, true, true), }; f.0.resolved.set(Some(res)); if let Some(waker) = f.0.waker.take() { waker.wake(); } }); future } /// Runs event loop to completion /// /// This future resolves when: /// - there are no more pending dynamic imports /// - there are no more pending ops /// - there are no more active inspector sessions (only if /// `PollEventLoopOptions.wait_for_inspector` is set to true) pub async fn run_event_loop( &mut self, poll_options: PollEventLoopOptions, ) -> Result<(), CoreError> { poll_fn(|cx| self.poll_event_loop(cx, poll_options)).await } /// A utility function that run provided future concurrently with the event loop. /// /// If the event loop resolves while polling the future, it return an error with the text /// `Promise resolution is still pending but the event loop has already resolved` pub async fn with_event_loop_promise<'fut, T, E>( &mut self, mut fut: impl Future<Output = Result<T, E>> + Unpin + 'fut, poll_options: PollEventLoopOptions, ) -> Result<T, CoreError> where CoreError: From<E>, { // Manually implement tokio::select poll_fn(|cx| { if let Poll::Ready(t) = fut.poll_unpin(cx) { return Poll::Ready(t.map_err(|e| e.into())); } if let Poll::Ready(t) = self.poll_event_loop(cx, poll_options) { t?; if let Poll::Ready(t) = fut.poll_unpin(cx) { return Poll::Ready(t.map_err(|e| e.into())); } return Poll::Ready(Err( CoreErrorKind::PendingPromiseResolution.into_box(), )); } Poll::Pending }) .await } /// A utility function that run provided future concurrently with the event loop. /// /// If the event loop resolves while polling the future, it will continue to be polled, /// regardless of whether it returned an error or success. /// /// Useful for interacting with local inspector session. pub async fn with_event_loop_future<'fut, T, E>( &mut self, mut fut: impl Future<Output = Result<T, E>> + Unpin + 'fut, poll_options: PollEventLoopOptions, ) -> Result<T, E> { // Manually implement tokio::select poll_fn(|cx| { if let Poll::Ready(t) = fut.poll_unpin(cx) { return Poll::Ready(t); } if let Poll::Ready(t) = self.poll_event_loop(cx, poll_options) { // TODO(mmastrac): We need to ignore this error for things like the repl to behave as // they did before, but this is definitely not correct. It's just something we're // relying on. :( _ = t; } Poll::Pending }) .await } /// Runs a single tick of event loop /// /// If `PollEventLoopOptions.wait_for_inspector` is set to true, the event /// loop will return `Poll::Pending` if there are active inspector sessions. pub fn poll_event_loop( &mut self, cx: &mut Context, poll_options: PollEventLoopOptions, ) -> Poll<Result<(), CoreError>> { let has_inspector = self.inner.state.has_inspector.get(); // SAFETY: We know this isolate is valid and non-null at this time let mut isolate = unsafe { v8::Isolate::from_raw_isolate_ptr(self.v8_isolate_ptr()) }; let result = { v8::scope!(let isolate_scope, &mut isolate); let context = v8::Local::new(isolate_scope, self.inner.main_realm.context()); let mut scope = v8::ContextScope::new(isolate_scope, context); self.poll_event_loop_inner(cx, &mut scope, poll_options) }; // Tell V8's CPU profiler whether we're about to go idle. When the event // loop has no immediate work it returns `Poll::Pending` and the task parks // until an external event (timer, I/O) wakes it. Marking the isolate idle // makes the profiler attribute that wait to the `(idle)` node instead of // `(program)`, which otherwise makes an idle process look like it is using // 100% CPU in DevTools (https://github.com/denoland/deno/issues/21620). // // This must happen *after* the `v8::ContextScope` above is dropped: tearing // the scope down calls `Isolate::Exit`, which restores the VM state that was // saved on entry and would clobber the idle state if we set it any earlier. if has_inspector { isolate.set_idle(result.is_pending()); } result } /// Phase-based event loop tick, loosely following libuv's architecture: /// /// 1. Timers -- fire expired libuv C timers + JS user timers /// 2. Pending work -- module progress, task spawner, async ops, /// nextTick/macrotask drain, rejections /// 3. Idle / Prepare -- libuv idle + prepare callbacks /// 4. I/O -- drive TCP read/write/accept via UvLoopInner /// 5. Check -- libuv check callbacks + immediates /// 6. Close -- close callbacks (Rust + libuv) /// /// Microtask checkpoints run between phases. fn poll_event_loop_inner( &self, cx: &mut Context, scope: &mut v8::PinScope, poll_options: PollEventLoopOptions, ) -> Poll<Result<(), CoreError>> { let has_inspector = self.inner.state.has_inspector.get(); self.inner.state.waker.register(cx.waker()); // Pre-phase: Inspector + drain foreground tasks + microtask checkpoint if has_inspector { self.inspector().poll_sessions_from_event_loop(cx); } { // Drain and run foreground tasks queued by the custom V8 platform. // Uses the local Arc shared with the registry — no global map lookup. let tasks = std::mem::take(&mut *self.inner.state.foreground_tasks.lock().unwrap()); for task in tasks { task.run(); } v8::tc_scope!(let tc_scope, scope); let context_state = JsRealm::state_from_scope(tc_scope); if !context_state.has_tick_scheduled() { tc_scope.perform_microtask_checkpoint(); } if let Some(exception) = tc_scope.exception() { return Poll::Ready(Err( exception_to_err_result::<()>(tc_scope, exception, false, true) .unwrap_err() .into(), )); } } let realm = &self.inner.main_realm; let modules = &realm.0.module_map; let context_state = &realm.0.context_state; let exception_state = &context_state.exception_state; // Tight I/O loop: when run_io does work, re-run I/O phases immediately // without returning to tokio. This avoids kqueue/kevent round-trip // latency between batches. let mut dispatched_ops = false; let mut did_work = false; let mut uv_did_io = false; // ===== Phase 1: Timers ===== // 1a. Fire expired libuv C timers if let Some(uv_inner_ptr) = context_state.uv_loop_inner.get() { // Update cached loop time at the start of each tick, matching libuv. unsafe { (*uv_inner_ptr).update_time() }; unsafe { (*uv_inner_ptr).run_timers() }; } // ===== Phase 2: Pending work ===== // Module progress polling (before ops, matching original ordering) modules.poll_progress(cx, scope)?; // 2a. V8 task spawner tasks dispatched_ops |= Self::dispatch_task_spawner(cx, scope, context_state); // 2b. Process expired JS timers first and immediately commit the // returned next-expiry state. Completed ops are resolved afterwards so // promise hooks that arm timers cannot be overwritten by an older timer // snapshot. let timer_ready = Self::dispatch_user_timers(cx, scope, context_state)?; did_work |= timer_ready; dispatched_ops |= Self::dispatch_event_loop_tick(cx, scope, context_state)?; // Microtask checkpoint after timer/op processing, but only when // no ticks are scheduled (matching original guard after timers). if !context_state.has_tick_scheduled() { scope.perform_microtask_checkpoint(); } // 2c. Dispatch "rejectionhandled" events before tick processing. // This ensures rejectionhandled fires before unhandledrejection for // later promises, since processTicksAndRejections drains unhandled // rejections via processPromiseRejections. Self::dispatch_handled_rejections(scope, exception_state); // 2d. nextTick drain + macrotask drain. // Only drain if there's actual work (ops dispatched, tick scheduled, // or timers fired). This prevents macrotask callbacks from running // on empty iterations. let has_tick_scheduled = context_state.has_tick_scheduled(); dispatched_ops |= has_tick_scheduled; if dispatched_ops || did_work || has_tick_scheduled { Self::drain_next_tick_and_macrotasks(scope, context_state)?; } // 2e. Handle promise rejections (after nextTick/macrotask, since // unhandledrejection handlers are run in macrotask callbacks). Self::dispatch_rejections(scope, context_state, exception_state)?; scope.perform_microtask_checkpoint(); // ===== Phase 3: Idle / Prepare ===== // In libuv: idle runs after pending callbacks, prepare runs right // before I/O polling. Both must precede I/O. let has_uv = context_state.uv_loop_inner.get().is_some(); if let Some(uv_inner_ptr) = context_state.uv_loop_inner.get() { unsafe { (*uv_inner_ptr).run_idle(); (*uv_inner_ptr).run_prepare(); }; // Idle/prepare callbacks may call into JS; flush microtasks. scope.perform_microtask_checkpoint(); } // ===== Phase 4: I/O ===== // Single run_io call per tick, matching libuv's uv_run (one // uv__io_poll per iteration, callbacks fire, then we return). // Spinning run_io in place until it returns `false` batches more // work per tick but under sustained inbound traffic never exits — // tokio keeps delivering readiness as we poll — and traps the // event loop processing hundreds of handles back-to-back, which // crushes tail latency (p99 30–200 ms vs ~1 ms with a single // call). if let Some(uv_inner_ptr) = context_state.uv_loop_inner.get() { unsafe { (*uv_inner_ptr).set_waker(cx.waker()); } let did_io = unsafe { (*uv_inner_ptr).run_io() }; if did_io { uv_did_io = true; // Flush microtasks from I/O callbacks, then drain ticks. // processTicksAndRejections runs op_run_microtasks internally, // so the trailing checkpoint is only needed when no ticks ran. scope.perform_microtask_checkpoint(); Self::drain_next_tick_and_macrotasks(scope, context_state)?; if !context_state.has_tick_scheduled() { scope.perform_microtask_checkpoint(); } } } // ===== Phase 5: Check ===== // In libuv: check runs right after I/O polling. // Immediates fire here, matching Node.js's setImmediate semantics // (libuv check phase, after I/O). if let Some(uv_inner_ptr) = context_state.uv_loop_inner.get() { unsafe { (*uv_inner_ptr).run_check() }; } // Drain immediates in the check phase, matching Node.js semantics: // - Refed immediates always fire (they keep the event loop alive). // - Unrefed immediates only fire if other work drove this iteration // (did_work, dispatched_ops, or uv I/O). This matches libuv's // behavior where unrefed handles participate in iterations driven // by other work, but don't start new iterations on their own. { let has_immediates = context_state.immediate_info[IMM_IDX_HAS_OUTSTANDING] != 0 || context_state.immediate_info[IMM_IDX_COUNT] > 0; let has_refed = context_state.immediate_info[IMM_IDX_REF_COUNT] > 0; if has_immediates && (has_refed || did_work || dispatched_ops || uv_did_io) { Self::do_js_run_immediate_callbacks(scope, context_state)?; // Drain ticks queued by immediate callbacks so they don't // require an extra full event loop iteration. if context_state.has_tick_scheduled() { Self::drain_next_tick_and_macrotasks(scope, context_state)?; } } } scope.perform_microtask_checkpoint(); // Drain nextTick queue before close phase, matching Node.js/libuv // where process.nextTick fires between each event loop phase. // This ensures nextTicks from I/O callbacks (Phase 4) fire before // close callbacks (Phase 6). if context_state.has_tick_scheduled() { Self::drain_next_tick_and_macrotasks(scope, context_state)?; } // ===== Phase 6: Close ===== exception_state.check_exception_condition(scope)?; { let mut phases = context_state.event_loop_phases.borrow_mut(); phases.run_close_callbacks(); } if let Some(uv_inner_ptr) = context_state.uv_loop_inner.get() { unsafe { (*uv_inner_ptr).run_close() }; } // Run V8 close callbacks (JS handle.close() callbacks). // These are deferred to Phase 6 to match libuv's uv_close behavior // where close callbacks fire at the END of the event loop iteration, // after all nextTick and microtask queues have drained. { let v8_cbs = context_state .event_loop_phases .borrow_mut() .drain_v8_close_callbacks(); for cb in v8_cbs { (cb.callback)(scope); } } // libuv close callbacks may call into JS; flush microtasks if present. if has_uv || !context_state .event_loop_phases .borrow() .v8_close_callbacks .is_empty() { scope.perform_microtask_checkpoint(); } // Evaluate pending state let pending_state = EventLoopPendingState::new(scope, context_state, modules); if !pending_state.is_pending() { if has_inspector { let inspector = self.inspector(); let sessions_state = inspector.sessions_state(); if poll_options.wait_for_inspector && sessions_state.has_active { if sessions_state.has_blocking { return Poll::Pending; } if sessions_state.has_nonblocking_wait_for_disconnect { let context = self.main_context(); inspector.context_destroyed(scope, context); self.wait_for_inspector_disconnect(); return Poll::Pending; } } } return Poll::Ready(Ok(())); } // Re-wake logic for next iteration #[allow( clippy::suspicious_else_formatting, clippy::if_same_then_else, reason = "intentional structure for clarity of re-wake conditions" )] { if pending_state.has_pending_background_tasks || pending_state.has_tick_scheduled || pending_state.has_outstanding_immediates || context_state.immediate_info[IMM_IDX_REF_COUNT] > 0 || pending_state.has_pending_promise_events || uv_did_io { self.inner.state.waker.wake(); } else // If ops were dispatched we may have progress on pending modules that we should re-check if (pending_state.has_pending_module_evaluation || pending_state.has_pending_dyn_module_evaluation) && dispatched_ops { self.inner.state.waker.wake(); } } if pending_state.has_pending_module_evaluation { if pending_state.has_pending_ops || pending_state.has_pending_dyn_imports || pending_state.has_pending_dyn_module_evaluation || pending_state.has_pending_background_tasks || pending_state.has_pending_external_ops || pending_state.has_tick_scheduled || pending_state.has_pending_timers || pending_state.has_uv_alive_handles { // pass, will be polled again } else { // Last-resort: try one more microtask checkpoint before reporting // a stalled TLA. Under Explicit microtask policy, V8's internal // async module evaluation state machine may require an additional // checkpoint to fully resolve the evaluation promise (e.g. when // TLA resumes trigger lazy module loads whose nested checkpoints // are no-ops due to V8's reentrancy guard). scope.perform_microtask_checkpoint(); let modules = &realm.0.module_map(); if !modules.has_pending_module_evaluation() { // The checkpoint resolved the evaluation -- keep going self.inner.state.tla_stall_retries.set(0); self.inner.state.waker.wake(); } else if let Some(js_error) = find_and_report_stalled_level_await_in_any_realm(scope, &realm.0) { self.inner.state.tla_stall_retries.set(0); return Poll::Ready(Err(CoreErrorKind::Js(js_error).into_box())); } else { // V8 reports no stalled TLA but the evaluation promise is still // pending. This can happen with large async module graphs under // Explicit microtask policy. Give the event loop a few more // iterations to make progress, but bail if we are stuck. const MAX_TLA_STALL_RETRIES: u32 = 10; let retries = self.inner.state.tla_stall_retries.get() + 1; self.inner.state.tla_stall_retries.set(retries); if retries > MAX_TLA_STALL_RETRIES { self.inner.state.tla_stall_retries.set(0); return Poll::Ready(Err( CoreErrorKind::ModuleEvaluationDeadlock.into_box(), )); } #[allow( clippy::print_stderr, reason = "intentional debug diagnostic for TLA stall recovery" )] { eprintln!( "warning: module evaluation pending but no stalled top-level \ await found, retrying event loop iteration ({retries}/{MAX_TLA_STALL_RETRIES})" ); } self.inner.state.waker.wake(); } } } if pending_state.has_pending_dyn_module_evaluation { if pending_state.has_pending_ops || pending_state.has_pending_dyn_imports || pending_state.has_pending_background_tasks || pending_state.has_pending_external_ops || pending_state.has_tick_scheduled || pending_state.has_pending_timers || pending_state.has_uv_alive_handles { // pass, will be polled again } else if realm.modules_idle() { if let Some(js_error) = find_and_report_stalled_level_await_in_any_realm(scope, &realm.0) { self.inner.state.tla_stall_retries.set(0); return Poll::Ready(Err(CoreErrorKind::Js(js_error).into_box())); } else { const MAX_TLA_STALL_RETRIES: u32 = 10; let retries = self.inner.state.tla_stall_retries.get() + 1; self.inner.state.tla_stall_retries.set(retries); if retries > MAX_TLA_STALL_RETRIES { self.inner.state.tla_stall_retries.set(0); return Poll::Ready(Err( CoreErrorKind::ModuleEvaluationDeadlock.into_box(), )); } #[allow( clippy::print_stderr, reason = "intentional debug diagnostic for TLA stall recovery" )] { eprintln!( "warning: dynamic module evaluation pending but no stalled \ top-level await found, retrying event loop iteration \ ({retries}/{MAX_TLA_STALL_RETRIES})" ); } self.inner.state.waker.wake(); } } else { realm.increment_modules_idle(); self.inner.state.waker.wake(); } } Poll::Pending } } fn find_and_report_stalled_level_await_in_any_realm( scope: &mut v8::PinScope, inner_realm: &JsRealmInner, ) -> Option<Box<JsError>> { let module_map = inner_realm.module_map(); let messages = module_map.find_stalled_top_level_await(scope); if !messages.is_empty() { // We are gonna print only a single message to provide a nice formatting // with source line of offending promise shown. Once user fixed it, then // they will get another error message for the next promise (but this // situation is gonna be very rare, if ever happening). let msg = v8::Local::new(scope, &messages[0]); let js_error = JsError::from_v8_message(scope, msg); return Some(js_error); } None } fn create_context<'s, 'i>( scope: &mut v8::PinScope<'s, 'i, ()>, global_template_middlewares: &[GlobalTemplateMiddlewareFn], global_object_middlewares: &[GlobalObjectMiddlewareFn], has_snapshot: bool, ) -> v8::Local<'s, v8::Context> { let context = if has_snapshot { // Try to load the 1st index first, embedder may have used 0th for something else (like node:vm). v8::Context::from_snapshot(scope, 1, Default::default()).unwrap_or_else( || v8::Context::from_snapshot(scope, 0, Default::default()).unwrap(), ) } else { // Set up the global object template and create context from it. let mut global_object_template = v8::ObjectTemplate::new(scope); for middleware in global_template_middlewares { global_object_template = middleware(scope, global_object_template); } global_object_template.set_internal_field_count(2); v8::Context::new( scope, v8::ContextOptions { global_template: Some(global_object_template), ..Default::default() }, ) }; let scope = &mut v8::ContextScope::new(scope, context); let global = context.global(scope); for middleware in global_object_middlewares { middleware(scope, global); } context } impl JsRuntimeForSnapshot { /// Create a new runtime, panicking if the process fails. pub fn new(options: RuntimeOptions) -> JsRuntimeForSnapshot { match Self::try_new(options) { Ok(runtime) => runtime, Err(err) => { panic!("Failed to initialize JsRuntime for snapshotting: {:?}", err); } } } /// Try to create a new runtime, returning an error if the process fails. pub fn try_new( mut options: RuntimeOptions, ) -> Result<JsRuntimeForSnapshot, CoreError> { setup::init_v8( options.v8_platform.take(), true, options.unsafe_expose_natives_and_gc(), ); let runtime = JsRuntime::new_inner(options, true)?; Ok(JsRuntimeForSnapshot(runtime)) } /// Takes a snapshot and consumes the runtime. /// /// `Error` can usually be downcast to `JsError`. pub fn snapshot(mut self) -> Box<[u8]> { // Ensure there are no live inspectors to prevent crashes. self.inner.prepare_for_cleanup(); let original_sources = std::mem::take(&mut self.0.allocations.original_sources); let lazy_source_specifiers = std::mem::take(&mut self.0.allocations.lazy_source_specifiers); // `lazy_loaded_*` sources are externalized for the snapshot (so consumed // sources bake in as clean external strings), but only the *consumed* ones // are actually referenced by snapshotted code. Non-consumed (residual) // sources are already shipped via the residual table, so persisting their // bytes here would duplicate them. Store empty bytes for those slots — the // external-reference index is preserved (nothing references it), avoiding // the duplication. let consumed: std::collections::HashSet<String> = self.consumed_lazy_specifiers().into_iter().collect(); let lazy_source_start = original_sources.len() - lazy_source_specifiers.len(); let external_strings = original_sources .iter() .enumerate() .map(|(i, s)| { if i >= lazy_source_start && !consumed .contains(lazy_source_specifiers[i - lazy_source_start].as_str()) { return &[][..]; } s.as_str().as_bytes() }) .collect(); let realm = JsRealm::clone(&self.inner.main_realm); // Set the context to be snapshot's default context { jsrealm::context_scope!(scope, realm, self.v8_isolate()); let default_context = v8::Context::new(scope, Default::default()); scope.set_default_context(default_context); let local_context = v8::Local::new(scope, realm.context()); scope.add_context(local_context); } // Borrow the source maps during the snapshot to avoid copies let source_maps = self .inner .state .source_mapper .borrow_mut() .take_ext_source_maps(); let mut ext_source_maps = HashMap::with_capacity(source_maps.len()); for (k, v) in &source_maps { ext_source_maps.insert(k.as_static_str().unwrap(), v.as_ref()); } // Serialize the module map and store its data in the snapshot. // TODO(mmastrac): This should deconstruct the realm into sidecar data rather than // extracting it from the realm and then tearing the realm down. IE, this should // probably be a method on `JsRealm` with a self-consuming parameter signature: // `fn into_sidecar_data(self) -> ...`. let sidecar_data = { let mut data_store = SnapshotStoreDataStore::default(); let module_map_data = { let module_map = realm.0.module_map(); module_map.serialize_for_snapshotting(&mut data_store) }; let function_templates_data = { let function_templates = realm.0.function_templates(); let f = std::mem::take(&mut *function_templates.borrow_mut()); f.serialize_for_snapshotting(&mut data_store) }; let maybe_js_handled_promise_rejection_cb = { let context_state = &realm.0.context_state; let exception_state = &context_state.exception_state; exception_state .js_handled_promise_rejection_cb .borrow() .clone() } .map(|cb| data_store.register(cb)); let ext_import_meta_proto = realm .0 .context_state .ext_import_meta_proto .borrow() .clone() .map(|p| data_store.register(p)); let snapshotted_data = SnapshottedData { module_map_data, function_templates_data, op_count: self.inner.op_count, addl_refs_count: self.inner.addl_refs_count, source_count: self.inner.source_count, extensions: self.inner.extensions.clone(), js_handled_promise_rejection_cb: maybe_js_handled_promise_rejection_cb, ext_import_meta_proto, ext_source_maps, external_strings, }; jsrealm::context_scope!(scope, realm, self.v8_isolate()); snapshot::store_snapshotted_data_for_snapshot( scope, realm.context().clone(), snapshotted_data, data_store, ) }; drop(realm); let v8_data = self .0 .inner .prepare_for_snapshot() .create_blob(v8::FunctionCodeHandling::Keep) .unwrap(); snapshot::serialize(v8_data, sidecar_data) } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) struct EventLoopPendingState { has_pending_ops: bool, has_pending_refed_ops: bool, has_pending_dyn_imports: bool, has_pending_dyn_module_evaluation: bool, has_pending_module_evaluation: bool, has_pending_background_tasks: bool, has_tick_scheduled: bool, has_pending_promise_events: bool, has_pending_external_ops: bool, has_outstanding_immediates: bool, has_pending_timers: bool, has_uv_alive_handles: bool, } impl EventLoopPendingState { /// Collect event loop state from all the sub-states. pub fn new( scope: &mut v8::PinScope<()>, state: &ContextState, modules: &ModuleMap, ) -> Self { let num_unrefed_ops = state.unrefed_ops.borrow().len(); let num_pending_ops = state.pending_ops.len(); let has_pending_tasks = state.task_spawner_factory.has_pending_tasks(); // User timers: JS manages these; the timer handle is refed when // there are refed timers (timer_info[0] > 0). let has_pending_refed_user_timers = state.user_timer.is_refed(); let has_pending_dyn_imports = modules.has_pending_dynamic_imports(); let has_pending_dyn_module_evaluation = modules.has_pending_dyn_module_evaluation(); let has_pending_module_evaluation = modules.has_pending_module_evaluation(); let has_pending_promise_events = !state .exception_state .pending_promise_rejections .borrow() .is_empty() || !state .exception_state .pending_handled_promise_rejections .borrow() .is_empty(); let has_pending_refed_ops = has_pending_tasks || has_pending_refed_user_timers || num_pending_ops > num_unrefed_ops; let has_outstanding_immediates = state.immediate_info[IMM_IDX_HAS_OUTSTANDING] != 0; let has_pending_timers = !state.active_timers.borrow().is_empty(); let has_uv_alive_handles = if let Some(uv_inner_ptr) = state.uv_loop_inner.get() { unsafe { (*uv_inner_ptr).has_alive_handles() } } else { false }; EventLoopPendingState { has_pending_ops: has_pending_refed_ops || (num_pending_ops > 0), has_pending_refed_ops, has_pending_dyn_imports, has_pending_dyn_module_evaluation, has_pending_module_evaluation, has_pending_background_tasks: scope.has_pending_background_tasks(), has_tick_scheduled: state.has_tick_scheduled(), has_pending_promise_events, has_pending_external_ops: state.external_ops_tracker.has_pending_ops(), has_outstanding_immediates, has_pending_timers, has_uv_alive_handles, } } /// Collect event loop state from all the states stored in the scope. pub fn new_from_scope(scope: &mut v8::PinScope) -> Self { let module_map = JsRealm::module_map_from(scope); let context_state = JsRealm::state_from_scope(scope); Self::new(scope, &context_state, &module_map) } pub fn is_pending(&self) -> bool { self.has_pending_refed_ops || self.has_pending_dyn_imports || self.has_pending_dyn_module_evaluation || self.has_pending_module_evaluation || self.has_pending_background_tasks || self.has_tick_scheduled || self.has_pending_promise_events || self.has_pending_external_ops || self.has_uv_alive_handles } } extern "C" fn near_heap_limit_callback<F>( data: *mut c_void, current_heap_limit: usize, initial_heap_limit: usize, ) -> usize where F: FnMut(usize, usize) -> usize, { // SAFETY: The data is a pointer to the Rust callback function. It is stored // in `JsRuntime::allocations` and thus is guaranteed to outlive the isolate. let callback = unsafe { &mut *(data as *mut F) }; callback(current_heap_limit, initial_heap_limit) } impl JsRuntimeState { pub(crate) fn inspector(&self) -> Rc<JsRuntimeInspector> { self.inspector.borrow().as_ref().unwrap().clone() } /// Called by `bindings::host_import_module_dynamically_callback` /// after initiating new dynamic import load. pub fn notify_new_dynamic_import(&self) { // Notify event loop to poll again soon. self.waker.wake(); } /// Performs an action with the inspector, if we have one pub(crate) fn with_inspector<T>( &self, mut f: impl FnMut(&JsRuntimeInspector) -> T, ) -> Option<T> { // Fast path if !self.has_inspector.get() { return None; } self .inspector .borrow() .as_ref() .map(|inspector| f(inspector)) } } // Related to module loading impl JsRuntime { #[cfg(test)] pub(crate) fn instantiate_module( &mut self, id: ModuleId, ) -> Result<(), v8::Global<v8::Value>> { let isolate = &mut *self.inner.v8_isolate; let realm = JsRealm::clone(&self.inner.main_realm); jsrealm::context_scope!(scope, realm, isolate); realm.instantiate_module(scope, id) } /// Evaluates an already instantiated ES module. /// /// Returns a future that resolves when module promise resolves. /// Implementors must manually call [`JsRuntime::run_event_loop`] to drive /// module evaluation future. /// /// Modules with top-level await are treated like promises, so a `throw` in the top-level /// block of a module is treated as an unhandled rejection. These rejections are provided to /// the unhandled promise rejection handler, which has the opportunity to pass them off to /// error-handling code. If those rejections are not handled (indicated by a `false` return /// from that unhandled promise rejection handler), then the runtime will terminate. /// /// The future provided by `mod_evaluate` will only return errors in the case where /// the runtime is shutdown and no longer available to provide unhandled rejection /// information. /// /// This function panics if module has not been instantiated. pub fn mod_evaluate( &mut self, id: ModuleId, ) -> impl Future<Output = Result<(), CoreError>> + use<> { let isolate = &mut *self.inner.v8_isolate; let realm = &self.inner.main_realm; jsrealm::context_scope!(scope, realm, isolate); self.inner.main_realm.0.module_map.mod_evaluate(scope, id) } /// Asynchronously load specified module and all of its dependencies. /// /// The module will be marked as "main", and because of that /// "import.meta.main" will return true when checked inside that module. /// /// The source may be any type that implements the internal [`IntoModuleCodeString`] trait, but /// it is highly recommended that embedders use the [`ascii_str!`] to generate the fastest version /// of strings for v8 to handle. If the strings are not static, you may also pass a [`String`] /// generated by the [`format!`] macro. /// /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. pub async fn load_main_es_module_from_code( &mut self, specifier: &ModuleSpecifier, code: impl IntoModuleCodeString, ) -> Result<ModuleId, CoreError> { self .drive_es_module_load(true, specifier, Some(code.into_module_code())) .await } /// Asynchronously load specified module and all of its dependencies, retrieving /// the module from the supplied [`ModuleLoader`]. /// /// The module will be marked as "main", and because of that /// "import.meta.main" will return true when checked inside that module. /// /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. pub async fn load_main_es_module( &mut self, specifier: &ModuleSpecifier, ) -> Result<ModuleId, CoreError> { self.drive_es_module_load(true, specifier, None).await } /// Asynchronously load specified ES module and all of its dependencies from the /// provided source. /// /// This method is meant to be used when loading some utility code that /// might be later imported by the main module (ie. an entry point module). /// /// The source may be any type that implements the internal [`IntoModuleCodeString`] trait, but /// it is highly recommended that embedders use the [`ascii_str!`] to generate the fastest version /// of strings for v8 to handle. If the strings are not static, you may also pass a [`String`] /// generated by the [`format!`] macro. /// /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. pub async fn load_side_es_module_from_code( &mut self, specifier: &ModuleSpecifier, code: impl IntoModuleCodeString, ) -> Result<ModuleId, CoreError> { self .drive_es_module_load(false, specifier, Some(code.into_module_code())) .await } /// Asynchronously load specified ES module and all of its dependencies, retrieving /// the module from the supplied [`ModuleLoader`]. /// /// This method is meant to be used when loading some utility code that /// might be later imported by the main module (ie. an entry point module). /// /// User must call [`JsRuntime::mod_evaluate`] with returned `ModuleId` /// manually after load is finished. pub async fn load_side_es_module( &mut self, specifier: &ModuleSpecifier, ) -> Result<ModuleId, CoreError> { self.drive_es_module_load(false, specifier, None).await } /// Drive a recursive ES module load to completion while concurrently /// pumping the event loop. The event loop pumping is required so that /// JavaScript-side `module.registerHooks()` load hooks (which run as an /// async polling task) can respond to load requests from the recursive /// load. Without it, static module loads that go through hook bridges /// would deadlock. async fn drive_es_module_load( &mut self, is_main: bool, specifier: &ModuleSpecifier, code: Option<ModuleCodeString>, ) -> Result<ModuleId, CoreError> { let realm = JsRealm::clone(&self.inner.main_realm); let module_map_rc = realm.0.module_map(); // Only pump the V8 event loop alongside the load when the loader resolves // modules through asynchronous JavaScript (e.g. Node // `module.registerHooks()` load hooks). For ordinary loads pumping is // unnecessary and would run unrelated event-loop work mid-load, so the // load is driven on its own instead. let pump_event_loop = module_map_rc.loader.borrow().pump_event_loop_during_load(); if let Some(code) = code { jsrealm::context_scope!(scope, &realm, self.v8_isolate()); module_map_rc .new_es_module( scope, is_main, specifier.to_string().into(), code, false, None, ) .map_err(|e| e.into_error(scope, false, false))?; } let mut load = if is_main { RecursiveModuleLoad::main(specifier.to_string(), module_map_rc.clone()) .await? } else { RecursiveModuleLoad::side( specifier.to_string(), module_map_rc.clone(), SideModuleKind::Async, None, ) .await? }; loop { // Drive the recursive load, optionally pumping the V8 event loop // concurrently. Pumping is what lets JavaScript-side module hooks // (`module.registerHooks()`) respond to load requests issued by the // recursive load via the async hook bridge; for ordinary loads it is // skipped so unrelated event-loop work doesn't run mid-load. let next_step = poll_fn(|cx| { if let Poll::Ready(t) = load.poll_next_unpin(cx) { return Poll::Ready(Ok::<_, CoreError>(t)); } if !pump_event_loop { return Poll::Pending; } match self.poll_event_loop(cx, PollEventLoopOptions::default()) { Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), Poll::Ready(Ok(())) | Poll::Pending => {} } // After polling the event loop, the recursive load may have new // results ready (e.g. a hook callback responded). Re-poll. match load.poll_next_unpin(cx) { Poll::Ready(t) => Poll::Ready(Ok(t)), Poll::Pending => Poll::Pending, } }) .await?; let Some(load_result) = next_step else { break }; let (request, source) = load_result?; jsrealm::context_scope!(scope, &realm, self.v8_isolate()); load .register_and_recurse(scope, &request, source) .map_err(|e| e.into_error(scope, false, false))?; } let root_id = load.root_module_id().expect("Root module should be loaded"); jsrealm::context_scope!(scope, &realm, self.v8_isolate()); realm.instantiate_module(scope, root_id).map_err(|e| { let exception = v8::Local::new(scope, e); CoreErrorKind::Js(crate::error::exception_to_err( scope, exception, false, false, )) .into_box() })?; Ok(root_id) } /// Load and evaluate an ES module provided the specifier and source code. /// /// The module should not have Top-Level Await (that is, it should be /// possible to evaluate it synchronously). /// /// It is caller's responsibility to ensure that not duplicate specifiers are /// passed to this method. pub fn lazy_load_es_module_with_code( &mut self, specifier: impl IntoModuleName, code: impl IntoModuleCodeString, ) -> Result<v8::Global<v8::Value>, CoreError> { let isolate = &mut self.inner.v8_isolate; self.inner.main_realm.lazy_load_es_module_with_code( isolate, specifier.into_module_name(), code.into_module_code(), ) } fn do_js_run_immediate_callbacks<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, context_state: &ContextState, ) -> Result<(), Box<JsError>> { v8::tc_scope!(let tc_scope, scope); let undefined = v8::undefined(tc_scope).into(); let run_immediate_callbacks_cb = context_state.run_immediate_callbacks_cb.borrow(); let run_immediate_callbacks_cb = run_immediate_callbacks_cb.as_ref().unwrap().open(tc_scope); run_immediate_callbacks_cb.call(tc_scope, undefined, &[]); if let Some(exception) = tc_scope.exception() { let e: Result<(), Box<JsError>> = exception_to_err_result(tc_scope, exception, false, true); return e; } Ok(()) } /// Schedule the next timer wake-up from the value returned by JS /// `__processTimers(now)`. /// /// The JS side returns: /// - positive: next expiry time (has refed timers) /// - negative: next expiry time negated (only unrefed timers) /// - 0.0: no timers remain fn schedule_timer_expiry(context_state: &ContextState, expiry_ms: f64) { if expiry_ms != 0.0 { let next_expiry = expiry_ms.abs(); let current_now = context_state.user_timer.now(); let delay_ms = if next_expiry > current_now { (next_expiry - current_now) as u64 } else { 0 }; context_state .user_timer .schedule(std::time::Duration::from_millis(if delay_ms > 0 { delay_ms } else { 1 })); if expiry_ms > 0.0 { context_state.user_timer.ref_timer(); } else { context_state.user_timer.unref_timer(); } } else { context_state.user_timer.clear(); context_state.user_timer.unref_timer(); } } /// Phase 2b: Poll the user timer and call JS `__processTimers(now)`. /// The returned value is committed before async op resolution runs. fn dispatch_user_timers<'s, 'i>( cx: &mut Context, scope: &mut v8::PinScope<'s, 'i>, context_state: &ContextState, ) -> Result<bool, Box<JsError>> { if context_state.user_timer.poll_ready(cx).is_pending() { return Ok(false); } // `now` is passed to JS raw, with no clamp. The old single-function // `__eventLoopTick(timerNow, ...)` protocol clamped `now <= 0.0` to // `f64::EPSILON` so the timer argument could never collide with the // negative "skip timers" sentinel it shared with op resolution. This // protocol has no such sentinel: `__processTimers(now)` is a dedicated // call made only when the timer is ready, and `processTimers` only // compares `list.expiry > now` (every real timer's expiry exceeds a 0.0 // startup `now`), so a raw 0.0 cannot mis-fire a timer. let now = context_state.user_timer.now(); v8::tc_scope!(let tc_scope, scope); let process_timers_cb = context_state.js_process_timers_cb.borrow(); let process_timers_fn = process_timers_cb.as_ref().unwrap().open(tc_scope); let now_val = v8::Number::new(tc_scope, now); let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into(); let result = process_timers_fn.call(tc_scope, undefined, &[now_val.into()]); if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false, true); } if tc_scope.has_terminated() || tc_scope.is_execution_terminating() { return Ok(false); } if let Some(result) = result { let expiry_ms = result.number_value(tc_scope).unwrap_or(0.0); Self::schedule_timer_expiry(context_state, expiry_ms); } Ok(true) } /// Phase 2a: Poll and dispatch V8 task spawner tasks. fn dispatch_task_spawner( cx: &mut Context, scope: &mut v8::PinScope, context_state: &ContextState, ) -> bool { let mut dispatched = false; let mut retries = 3; while let Poll::Ready(tasks) = context_state.task_spawner_factory.poll_inner(cx) { dispatched = true; for task in tasks { task(scope); } if !context_state.has_tick_scheduled() { scope.perform_microtask_checkpoint(); } retries -= 1; if retries == 0 { cx.waker().wake_by_ref(); break; } } dispatched } /// Phase 2c: Resolve completed async ops in a single Rust-to-JS call via /// `__eventLoopTick(promiseId, isOk, res, ...)`. /// /// If no ops are ready, returns Ok(false) and the /// caller should use drain_next_tick_and_macrotasks for any pending ticks. fn dispatch_event_loop_tick<'s, 'i>( cx: &mut Context, scope: &mut v8::PinScope<'s, 'i>, context_state: &ContextState, ) -> Result<bool, Box<JsError>> { const MAX_VEC_SIZE_FOR_OPS: usize = 1024; let mut args: SmallVec<[v8::Local<v8::Value>; 32]> = SmallVec::with_capacity(32); // Completed async ops as (promiseId, isOk, res) triplets. loop { // Ensure there is room for the next (promiseId, isOk, res) triplet if args.len() + 3 > MAX_VEC_SIZE_FOR_OPS { cx.waker().wake_by_ref(); break; } let Poll::Ready((promise_id, op_id, res)) = context_state.pending_ops.poll_ready(cx) else { break; }; let res = res.unwrap(scope); { let op_ctx = &context_state.op_ctxs[op_id as usize]; if op_ctx.metrics_enabled() { if res.is_ok() { dispatch_metrics_async(op_ctx, OpMetricsEvent::CompletedAsync); } else { dispatch_metrics_async(op_ctx, OpMetricsEvent::ErrorAsync); } } } context_state.unrefed_ops.borrow_mut().remove(&promise_id); context_state .activity_traces .complete(RuntimeActivityType::AsyncOp, promise_id as _); args.push(v8::Integer::new(scope, promise_id).into()); args.push(v8::Boolean::new(scope, res.is_ok()).into()); args.push(res.unwrap_or_else(std::convert::identity)); } // Skip the call if no ops completed. let has_ops = !args.is_empty(); if !has_ops { return Ok(false); } let undefined: v8::Local<v8::Value> = v8::undefined(scope).into(); v8::tc_scope!(let tc_scope, scope); let tick_cb = context_state.js_event_loop_tick_cb.borrow(); let tick_fn = tick_cb.as_ref().unwrap().open(tc_scope); tick_fn.call(tc_scope, undefined, args.as_slice()); if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false, true); } if tc_scope.has_terminated() || tc_scope.is_execution_terminating() { return Ok(false); } Ok(has_ops) } /// Dispatch "rejectionhandled" events for promises that were previously /// reported as unhandled but have since had handlers attached. /// This must run before unhandled rejection processing so that /// rejectionhandled fires before unhandledrejection for later promises. fn dispatch_handled_rejections<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, exception_state: &ExceptionState, ) { let undefined: v8::Local<v8::Value> = v8::undefined(scope).into(); while let Some((promise, result)) = exception_state .pending_handled_promise_rejections .borrow_mut() .pop_front() { if let Some(handler) = exception_state .js_handled_promise_rejection_cb .borrow() .as_ref() { let function = handler.open(scope); let args = [ v8::Local::new(scope, promise).into(), v8::Local::new(scope, result), ]; function.call(scope, undefined, &args); } } } /// Phase 2c: Handle promise rejections. fn dispatch_rejections<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, context_state: &ContextState, exception_state: &ExceptionState, ) -> Result<(), Box<JsError>> { // First handle "handled" rejections Self::dispatch_handled_rejections(scope, exception_state); // Then handle unhandled rejections if exception_state .pending_promise_rejections .borrow() .is_empty() { return Ok(()); } let mut pending_rejections = exception_state.pending_promise_rejections.borrow_mut(); let mut rejections = VecDeque::default(); std::mem::swap(&mut *pending_rejections, &mut rejections); drop(pending_rejections); let mut args: SmallVec<[v8::Local<v8::Value>; 16]> = SmallVec::with_capacity(rejections.len() * 3); for rejection in rejections.into_iter() { args.push(v8::Local::new(scope, rejection.0).into()); args.push(v8::Local::new(scope, rejection.1)); args.push(v8::Local::new(scope, rejection.2)); } v8::tc_scope!(let tc_scope, scope); let handle_rejections_cb = context_state.js_handle_rejections_cb.borrow(); let handle_rejections_fn = handle_rejections_cb.as_ref().unwrap().open(tc_scope); let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into(); handle_rejections_fn.call(tc_scope, undefined, args.as_slice()); if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false, true); } Ok(()) } /// Drain nextTick queue and macrotask queue (no op resolution). /// Used when ticks are pending but no async ops completed. /// /// This mirrors the JS `drainTicks()` (see `01_core.js`) exactly, but keeps /// the common path — no `process.nextTick` scheduled and no promise rejection /// to warn about, which is every pure-Deno workload — entirely on the Rust /// side. In that case `drainTicks` just runs `op_run_microtasks()` (a /// `perform_microtask_checkpoint`) and returns; doing it here avoids a /// Rust->JS call plus the JS->Rust op call it would otherwise make. Only when /// a tick or rejection is actually pending do we cross into JS to run /// `processTicksAndRejections`. The flag checks and checkpoint ordering are /// byte-for-byte identical to `drainTicks`, so the nextTick-before-`.then` /// and rejectionhandled-before-unhandledrejection invariants are preserved. fn drain_next_tick_and_macrotasks<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, context_state: &ContextState, ) -> Result<(), Box<JsError>> { if !context_state.has_tick_scheduled() && !context_state.has_rejection_to_warn() { scope.perform_microtask_checkpoint(); if !context_state.has_tick_scheduled() && !context_state.has_rejection_to_warn() { return Ok(()); } } // Slow path: a nextTick or rejection is pending. Cross into JS to run // `drainTicks`, which goes straight to `processTicksAndRejections` given // the flags above. let undefined: v8::Local<v8::Value> = v8::undefined(scope).into(); v8::tc_scope!(let tc_scope, scope); let drain_cb = context_state.js_drain_next_tick_and_macrotasks_cb.borrow(); let drain_fn = drain_cb.as_ref().unwrap().open(tc_scope); drain_fn.call(tc_scope, undefined, &[]); if let Some(exception) = tc_scope.exception() { return exception_to_err_result(tc_scope, exception, false, true); } Ok(()) } } fn mark_as_loaded_from_fs_during_snapshot( files_loaded: &mut Vec<&'static str>, source: &ExtensionFileSourceCode, ) { #[allow( deprecated, reason = "needed for compatibility with snapshot loading" )] if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) = source { files_loaded.push(path); } }