summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/build.rs13
-rw-r--r--core/bindings.rs23
-rw-r--r--core/error.rs6
-rw-r--r--core/examples/http_bench_json_ops/main.rs16
-rw-r--r--core/extensions.rs2
-rw-r--r--core/lib.rs1
-rw-r--r--core/modules.rs25
-rw-r--r--core/ops.rs8
-rw-r--r--core/ops_builtin_v8.rs24
-rw-r--r--core/realm.rs2
-rw-r--r--core/runtime.rs919
-rw-r--r--core/snapshot_util.rs83
-rw-r--r--runtime/build.rs5
13 files changed, 696 insertions, 431 deletions
diff --git a/cli/build.rs b/cli/build.rs
index 72b8e7818..5ff86fa20 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -262,7 +262,7 @@ mod ts {
)
.unwrap();
- create_snapshot(CreateSnapshotOptions {
+ let output = create_snapshot(CreateSnapshotOptions {
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
snapshot_path,
startup_snapshot: None,
@@ -289,6 +289,9 @@ mod ts {
})),
snapshot_module_load_cb: None,
});
+ for path in output.files_loaded_during_snapshot {
+ println!("cargo:rerun-if-changed={}", path.display());
+ }
}
pub(crate) fn version() -> String {
@@ -326,7 +329,8 @@ deno_core::extension!(
}
);
-fn create_cli_snapshot(snapshot_path: PathBuf) {
+#[must_use = "The files listed by create_cli_snapshot should be printed as 'cargo:rerun-if-changed' lines"]
+fn create_cli_snapshot(snapshot_path: PathBuf) -> CreateSnapshotOutput {
// NOTE(bartlomieju): ordering is important here, keep it in sync with
// `runtime/worker.rs`, `runtime/web_worker.rs` and `runtime/build.rs`!
let fs = Arc::new(deno_fs::RealFs);
@@ -481,7 +485,10 @@ fn main() {
ts::create_compiler_snapshot(compiler_snapshot_path, &c);
let cli_snapshot_path = o.join("CLI_SNAPSHOT.bin");
- create_cli_snapshot(cli_snapshot_path);
+ let output = create_cli_snapshot(cli_snapshot_path);
+ for path in output.files_loaded_during_snapshot {
+ println!("cargo:rerun-if-changed={}", path.display())
+ }
#[cfg(target_os = "windows")]
{
diff --git a/core/bindings.rs b/core/bindings.rs
index 1f35d1246..d91e4c309 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -15,10 +15,19 @@ use crate::modules::ImportAssertionsKind;
use crate::modules::ModuleMap;
use crate::modules::ResolutionKind;
use crate::ops::OpCtx;
-use crate::snapshot_util::SnapshotOptions;
use crate::JsRealm;
use crate::JsRuntime;
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub(crate) enum BindingsMode {
+ /// We have no snapshot -- this is a pristine context.
+ New,
+ /// We have initialized before, are reloading a snapshot, and will snapshot.
+ Loaded,
+ /// We have initialized before, are reloading a snapshot, and will not snapshot again.
+ LoadedFinal,
+}
+
pub(crate) fn external_references(ops: &[OpCtx]) -> v8::ExternalReferences {
// Overallocate a bit, it's better than having to resize the vector.
let mut references = Vec::with_capacity(4 + ops.len() * 4);
@@ -118,7 +127,7 @@ pub(crate) fn initialize_context<'s>(
scope: &mut v8::HandleScope<'s>,
context: v8::Local<'s, v8::Context>,
op_ctxs: &[OpCtx],
- snapshot_options: SnapshotOptions,
+ bindings_mode: BindingsMode,
) -> v8::Local<'s, v8::Context> {
let global = context.global(scope);
@@ -128,13 +137,13 @@ pub(crate) fn initialize_context<'s>(
codegen,
"Deno.__op__ = function(opFns, callConsole, console) {{"
);
- if !snapshot_options.loaded() {
+ if bindings_mode == BindingsMode::New {
_ = writeln!(codegen, "Deno.__op__console(callConsole, console);");
}
for op_ctx in op_ctxs {
if op_ctx.decl.enabled {
// If we're loading from a snapshot, we can skip registration for most ops
- if matches!(snapshot_options, SnapshotOptions::Load)
+ if bindings_mode == BindingsMode::LoadedFinal
&& !op_ctx.decl.force_registration
{
continue;
@@ -173,7 +182,7 @@ pub(crate) fn initialize_context<'s>(
let op_fn = op_ctx_function(scope, op_ctx);
op_fns.set_index(scope, op_ctx.id as u32, op_fn.into());
}
- if snapshot_options.loaded() {
+ if bindings_mode != BindingsMode::New {
op_fn.call(scope, recv.into(), &[op_fns.into()]);
} else {
// Bind functions to Deno.core.*
@@ -284,7 +293,7 @@ pub fn host_import_module_dynamically_callback<'s>(
let resolver_handle = v8::Global::new(scope, resolver);
{
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let module_map_rc = JsRuntime::module_map_from(scope);
debug!(
@@ -484,7 +493,7 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
};
if has_unhandled_rejection_handler {
- let state_rc = JsRuntime::state(tc_scope);
+ let state_rc = JsRuntime::state_from(tc_scope);
let mut state = state_rc.borrow_mut();
if let Some(pending_mod_evaluate) = state.pending_mod_evaluate.as_mut() {
if !pending_mod_evaluate.has_evaluated {
diff --git a/core/error.rs b/core/error.rs
index 3d0b20b0a..16f813b89 100644
--- a/core/error.rs
+++ b/core/error.rs
@@ -209,7 +209,7 @@ impl JsStackFrame {
let l = message.get_line_number(scope)? as i64;
// V8's column numbers are 0-based, we want 1-based.
let c = message.get_start_column() as i64 + 1;
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let (getter, cache) = {
let state = state_rc.borrow();
(
@@ -282,7 +282,7 @@ impl JsError {
frames = vec![stack_frame];
}
{
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let (getter, cache) = {
let state = state_rc.borrow();
(
@@ -414,7 +414,7 @@ impl JsError {
}
}
{
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let (getter, cache) = {
let state = state_rc.borrow();
(
diff --git a/core/examples/http_bench_json_ops/main.rs b/core/examples/http_bench_json_ops/main.rs
index 7c15f7bf2..36c0996c3 100644
--- a/core/examples/http_bench_json_ops/main.rs
+++ b/core/examples/http_bench_json_ops/main.rs
@@ -3,7 +3,7 @@ use deno_core::anyhow::Error;
use deno_core::op;
use deno_core::AsyncRefCell;
use deno_core::AsyncResult;
-use deno_core::JsRuntime;
+use deno_core::JsRuntimeForSnapshot;
use deno_core::OpState;
use deno_core::Resource;
use deno_core::ResourceId;
@@ -93,7 +93,7 @@ impl From<tokio::net::TcpStream> for TcpStream {
}
}
-fn create_js_runtime() -> JsRuntime {
+fn create_js_runtime() -> JsRuntimeForSnapshot {
let ext = deno_core::Extension::builder("my_ext")
.ops(vec![
op_listen::decl(),
@@ -103,11 +103,13 @@ fn create_js_runtime() -> JsRuntime {
])
.build();
- JsRuntime::new(deno_core::RuntimeOptions {
- extensions: vec![ext],
- will_snapshot: false,
- ..Default::default()
- })
+ JsRuntimeForSnapshot::new(
+ deno_core::RuntimeOptions {
+ extensions: vec![ext],
+ ..Default::default()
+ },
+ Default::default(),
+ )
}
#[op]
diff --git a/core/extensions.rs b/core/extensions.rs
index a8b52eb3b..ff86fec64 100644
--- a/core/extensions.rs
+++ b/core/extensions.rs
@@ -349,6 +349,7 @@ macro_rules! extension {
#[derive(Default)]
pub struct Extension {
+ pub(crate) name: &'static str,
js_files: Option<Vec<ExtensionFileSource>>,
esm_files: Option<Vec<ExtensionFileSource>>,
esm_entry_point: Option<&'static str>,
@@ -358,7 +359,6 @@ pub struct Extension {
event_loop_middleware: Option<Box<OpEventLoopFn>>,
initialized: bool,
enabled: bool,
- name: &'static str,
deps: Option<&'static [&'static str]>,
force_op_registration: bool,
pub(crate) is_core: bool,
diff --git a/core/lib.rs b/core/lib.rs
index 8edc8be18..336d9c2b9 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -113,6 +113,7 @@ pub use crate::runtime::CrossIsolateStore;
pub use crate::runtime::GetErrorClassFn;
pub use crate::runtime::JsErrorCreateFn;
pub use crate::runtime::JsRuntime;
+pub use crate::runtime::JsRuntimeForSnapshot;
pub use crate::runtime::RuntimeOptions;
pub use crate::runtime::SharedArrayBufferStore;
pub use crate::runtime::Snapshot;
diff --git a/core/modules.rs b/core/modules.rs
index 174dadd2d..353119660 100644
--- a/core/modules.rs
+++ b/core/modules.rs
@@ -1719,6 +1719,7 @@ mod tests {
use super::*;
use crate::ascii_str;
use crate::JsRuntime;
+ use crate::JsRuntimeForSnapshot;
use crate::RuntimeOptions;
use crate::Snapshot;
use deno_ops::op;
@@ -2889,11 +2890,13 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
);
let loader = MockLoader::new();
- let mut runtime = JsRuntime::new(RuntimeOptions {
- module_loader: Some(loader),
- will_snapshot: true,
- ..Default::default()
- });
+ let mut runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ module_loader: Some(loader),
+ ..Default::default()
+ },
+ Default::default(),
+ );
// In default resolution code should be empty.
// Instead we explicitly pass in our own code.
// The behavior should be very similar to /a.js.
@@ -2931,11 +2934,13 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
);
let loader = MockLoader::new();
- let mut runtime = JsRuntime::new(RuntimeOptions {
- module_loader: Some(loader),
- will_snapshot: true,
- ..Default::default()
- });
+ let mut runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ module_loader: Some(loader),
+ ..Default::default()
+ },
+ Default::default(),
+ );
// In default resolution code should be empty.
// Instead we explicitly pass in our own code.
// The behavior should be very similar to /a.js.
diff --git a/core/ops.rs b/core/ops.rs
index 2614c3a87..5f1bf67ef 100644
--- a/core/ops.rs
+++ b/core/ops.rs
@@ -183,7 +183,7 @@ pub struct OpState {
pub get_error_class_fn: GetErrorClassFn,
pub tracker: OpsTracker,
pub last_fast_op_error: Option<AnyError>,
- gotham_state: GothamState,
+ pub(crate) gotham_state: GothamState,
}
impl OpState {
@@ -196,6 +196,12 @@ impl OpState {
tracker: OpsTracker::new(ops_count),
}
}
+
+ /// Clear all user-provided resources and state.
+ pub(crate) fn clear(&mut self) {
+ std::mem::take(&mut self.gotham_state);
+ std::mem::take(&mut self.resource_table);
+ }
}
impl Deref for OpState {
diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs
index 8987a56b6..8416546cb 100644
--- a/core/ops_builtin_v8.rs
+++ b/core/ops_builtin_v8.rs
@@ -72,14 +72,14 @@ fn op_run_microtasks(scope: &mut v8::HandleScope) {
#[op(v8)]
fn op_has_tick_scheduled(scope: &mut v8::HandleScope) -> bool {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow();
state.has_tick_scheduled
}
#[op(v8)]
fn op_set_has_tick_scheduled(scope: &mut v8::HandleScope, v: bool) {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
state_rc.borrow_mut().has_tick_scheduled = v;
}
@@ -242,7 +242,7 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> {
if self.for_storage {
return None;
}
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow_mut();
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store {
let backing_store = shared_array_buffer.get_backing_store();
@@ -263,7 +263,7 @@ impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> {
self.throw_data_clone_error(scope, message);
return None;
}
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow_mut();
if let Some(compiled_wasm_module_store) = &state.compiled_wasm_module_store
{
@@ -305,7 +305,7 @@ impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> {
if self.for_storage {
return None;
}
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow_mut();
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store {
let backing_store = shared_array_buffer_store.take(transfer_id)?;
@@ -325,7 +325,7 @@ impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> {
if self.for_storage {
return None;
}
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow_mut();
if let Some(compiled_wasm_module_store) = &state.compiled_wasm_module_store
{
@@ -409,7 +409,7 @@ fn op_serialize(
value_serializer.write_header();
if let Some(transferred_array_buffers) = transferred_array_buffers {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow_mut();
for index in 0..transferred_array_buffers.length() {
let i = v8::Number::new(scope, index as f64).into();
@@ -494,7 +494,7 @@ fn op_deserialize<'a>(
}
if let Some(transferred_array_buffers) = transferred_array_buffers {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow_mut();
if let Some(shared_array_buffer_store) = &state.shared_array_buffer_store {
for i in 0..transferred_array_buffers.length() {
@@ -724,7 +724,7 @@ fn op_set_wasm_streaming_callback(
.as_ref()
.unwrap()
.clone();
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let streaming_rid = state_rc
.borrow()
.op_state
@@ -751,7 +751,7 @@ fn op_abort_wasm_streaming(
error: serde_v8::Value,
) -> Result<(), Error> {
let wasm_streaming = {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow();
let wsr = state
.op_state
@@ -790,7 +790,7 @@ fn op_dispatch_exception(
scope: &mut v8::HandleScope,
exception: serde_v8::Value,
) {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let mut state = state_rc.borrow_mut();
if let Some(inspector) = &state.inspector {
let inspector = inspector.borrow();
@@ -828,7 +828,7 @@ fn op_apply_source_map(
scope: &mut v8::HandleScope,
location: Location,
) -> Result<Location, Error> {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let (getter, cache) = {
let state = state_rc.borrow();
(
diff --git a/core/realm.rs b/core/realm.rs
index fc0ff4b4b..94ce77464 100644
--- a/core/realm.rs
+++ b/core/realm.rs
@@ -162,7 +162,7 @@ impl JsRealmInner {
};
let exception = v8::Local::new(scope, handle);
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow();
if let Some(inspector) = &state.inspector {
let inspector = inspector.borrow();
diff --git a/core/runtime.rs b/core/runtime.rs
index b4d271f66..b4876f0af 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use crate::bindings;
+use crate::bindings::BindingsMode;
use crate::error::generic_error;
use crate::error::to_v8_type_error;
use crate::error::JsError;
@@ -23,6 +24,8 @@ use crate::realm::ContextState;
use crate::realm::JsRealm;
use crate::realm::JsRealmInner;
use crate::snapshot_util;
+use crate::snapshot_util::SnapshotOptions;
+use crate::snapshot_util::SnapshottedData;
use crate::source_map::SourceMapCache;
use crate::source_map::SourceMapGetter;
use crate::Extension;
@@ -47,16 +50,23 @@ use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::c_void;
+use std::mem::ManuallyDrop;
+use std::ops::Deref;
+use std::ops::DerefMut;
use std::option::Option;
use std::pin::Pin;
use std::rc::Rc;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Once;
use std::task::Context;
use std::task::Poll;
use v8::CreateParams;
-use v8::OwnedIsolate;
+
+const STATE_DATA_OFFSET: u32 = 0;
+const MODULE_MAP_DATA_OFFSET: u32 = 1;
pub enum Snapshot {
Static(&'static [u8]),
@@ -75,31 +85,166 @@ struct IsolateAllocations {
Option<(Box<RefCell<dyn Any>>, v8::NearHeapLimitCallback)>,
}
+/// ManuallyDrop<Rc<...>> is clone, but it returns a ManuallyDrop<Rc<...>> which is a massive
+/// memory-leak footgun.
+struct ManuallyDropRc<T>(ManuallyDrop<Rc<T>>);
+
+impl<T> ManuallyDropRc<T> {
+ 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.
+struct InnerIsolateState {
+ snapshotting: bool,
+ state: ManuallyDropRc<RefCell<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) {
+ let mut state = self.state.borrow_mut();
+ let inspector = state.inspector.take();
+ 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) {
+ self.prepare_for_cleanup();
+
+ 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 RefCell<JsRuntimeState>) };
+
+ let module_map_ptr = self.v8_isolate.get_data(MODULE_MAP_DATA_OFFSET);
+ // SAFETY: We are sure that it's a valid pointer for whole lifetime of
+ // the runtime.
+ _ = unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) };
+
+ self.state.borrow_mut().destroy_all_realms();
+
+ 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
+ let (state, isolate) = unsafe {
+ (
+ ManuallyDrop::take(&mut self.state.0),
+ ManuallyDrop::take(&mut self.v8_isolate),
+ )
+ };
+ std::mem::forget(self);
+ drop(state);
+ 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.snapshotting {
+ // Create the snapshot and just drop it.
+ eprintln!("WARNING: v8::OwnedIsolate for snapshot was leaked");
+ } else {
+ ManuallyDrop::drop(&mut self.v8_isolate);
+ }
+ }
+ }
+}
+
/// A single execution context of JavaScript. Corresponds roughly to the "Web
-/// Worker" concept in the DOM. A JsRuntime is a Future that can be used with
-/// an event loop (Tokio, async_std).
+/// Worker" concept in the DOM.
////
-/// The JsRuntime future completes when there is an error or when all
+/// The JsRuntimeImpl future completes when there is an error or when all
/// pending ops have completed.
///
-/// Pending ops are created in JavaScript by calling Deno.core.opAsync(), and in Rust
-/// by implementing an async function that takes a serde::Deserialize "control argument"
-/// and an optional zero copy buffer, each async Op is tied to a Promise in JavaScript.
-pub struct JsRuntime {
- state: Rc<RefCell<JsRuntimeState>>,
+/// API consumers will want to use either the [`JsRuntime`] or [`JsRuntimeForSnapshot`]
+/// type aliases.
+pub struct JsRuntimeImpl<const FOR_SNAPSHOT: bool = false> {
+ inner: InnerIsolateState,
module_map: Option<Rc<RefCell<ModuleMap>>>,
- // This is an Option<OwnedIsolate> instead of just OwnedIsolate to workaround
- // a safety issue with SnapshotCreator. See JsRuntime::drop.
- v8_isolate: Option<v8::OwnedIsolate>,
- snapshot_options: snapshot_util::SnapshotOptions,
- snapshot_module_load_cb: Option<Rc<ExtModuleLoaderCb>>,
allocations: IsolateAllocations,
- extensions: Rc<RefCell<Vec<Extension>>>,
+ extensions: Vec<Extension>,
event_loop_middlewares: Vec<Box<OpEventLoopFn>>,
+ bindings_mode: BindingsMode,
// Marks if this is considered the top-level runtime. Used only be inspector.
is_main: bool,
}
+/// The runtime type that most users will use when not creating a snapshot.
+pub struct JsRuntime(JsRuntimeImpl<false>);
+
+impl Deref for JsRuntime {
+ type Target = JsRuntimeImpl<false>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for JsRuntime {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+/// The runtime type used for snapshot creation.
+pub struct JsRuntimeForSnapshot(JsRuntimeImpl<true>);
+
+impl Deref for JsRuntimeForSnapshot {
+ type Target = JsRuntimeImpl<true>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for JsRuntimeForSnapshot {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
pub(crate) struct DynImportModEvaluate {
load_id: ModuleLoadId,
module_id: ModuleId,
@@ -156,7 +301,7 @@ pub type SharedArrayBufferStore =
pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
-/// Internal state for JsRuntime which is stored in one of v8::Isolate's
+/// Internal state for JsRuntimeImpl which is stored in one of v8::Isolate's
/// embedder slots.
pub struct JsRuntimeState {
global_realm: Option<JsRealm>,
@@ -253,7 +398,7 @@ pub struct RuntimeOptions {
/// executed tries to load modules.
pub module_loader: Option<Rc<dyn ModuleLoader>>,
- /// JsRuntime extensions, not to be confused with ES modules.
+ /// JsRuntimeImpl 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`.
@@ -265,15 +410,6 @@ pub struct RuntimeOptions {
/// V8 snapshot that should be loaded on startup.
pub startup_snapshot: Option<Snapshot>,
- /// Prepare runtime to take snapshot of loaded code.
- /// The snapshot is deterministic and uses predictable random numbers.
- pub will_snapshot: bool,
-
- /// An optional callback that will be called for each module that is loaded
- /// during snapshotting. This callback can be used to transpile source on the
- /// fly, during snapshotting, eg. to transpile TypeScript to JavaScript.
- pub snapshot_module_load_cb: Option<ExtModuleLoaderCb>,
-
/// Isolate creation parameters.
pub create_params: Option<v8::CreateParams>,
@@ -304,50 +440,59 @@ pub struct RuntimeOptions {
pub is_main: bool,
}
-impl Drop for JsRuntime {
- fn drop(&mut self) {
- // Forcibly destroy all outstanding realms
- self.state.borrow_mut().destroy_all_realms();
- if let Some(v8_isolate) = self.v8_isolate.as_mut() {
- Self::drop_state_and_module_map(v8_isolate);
- }
- // Ensure that we've correctly dropped all references
- debug_assert_eq!(Rc::strong_count(&self.state), 1);
- }
+#[derive(Default)]
+pub struct RuntimeSnapshotOptions {
+ /// An optional callback that will be called for each module that is loaded
+ /// during snapshotting. This callback can be used to transpile source on the
+ /// fly, during snapshotting, eg. to transpile TypeScript to JavaScript.
+ pub snapshot_module_load_cb: Option<ExtModuleLoaderCb>,
}
-impl JsRuntime {
- const STATE_DATA_OFFSET: u32 = 0;
- const MODULE_MAP_DATA_OFFSET: u32 = 1;
-
- /// Only constructor, configuration is done through `options`.
- pub fn new(mut options: RuntimeOptions) -> Self {
- let v8_platform = options.v8_platform.take();
+trait JsRuntimeInternalTrait {
+ fn create_raw_isolate(
+ refs: &'static v8::ExternalReferences,
+ params: Option<CreateParams>,
+ snapshot: SnapshotOptions,
+ ) -> v8::OwnedIsolate;
+}
+impl<const FOR_SNAPSHOT: bool> JsRuntimeImpl<FOR_SNAPSHOT> {
+ fn init_v8(v8_platform: Option<v8::SharedRef<v8::Platform>>) {
static DENO_INIT: Once = Once::new();
- DENO_INIT.call_once(move || v8_init(v8_platform, options.will_snapshot));
-
- // Add builtins extension
- // TODO(bartlomieju): remove this in favor of `SnapshotOptions`.
- let has_startup_snapshot = options.startup_snapshot.is_some();
- if !has_startup_snapshot {
- options
- .extensions
- .insert(0, crate::ops_builtin::core::init_ops_and_esm());
- } else {
- options
- .extensions
- .insert(0, crate::ops_builtin::core::init_ops());
+ static DENO_PREDICTABLE: AtomicBool = AtomicBool::new(false);
+ static DENO_PREDICTABLE_SET: AtomicBool = AtomicBool::new(false);
+
+ let predictable = FOR_SNAPSHOT || cfg!(test);
+ if DENO_PREDICTABLE_SET.load(Ordering::SeqCst) {
+ let current = DENO_PREDICTABLE.load(Ordering::SeqCst);
+ assert_eq!(current, predictable, "V8 may only be initialized once in either snapshotting or non-snapshotting mode. Either snapshotting or non-snapshotting mode may be used in a single process, not both.");
+ DENO_PREDICTABLE_SET.store(true, Ordering::SeqCst);
+ DENO_PREDICTABLE.store(predictable, Ordering::SeqCst);
}
- let ops = Self::collect_ops(&mut options.extensions);
- let mut op_state = OpState::new(ops.len());
+ DENO_INIT.call_once(move || v8_init(v8_platform, predictable));
+ }
- if let Some(get_error_class_fn) = options.get_error_class_fn {
- op_state.get_error_class_fn = get_error_class_fn;
- }
+ fn new_runtime(
+ mut options: RuntimeOptions,
+ snapshot_options: SnapshotOptions,
+ maybe_load_callback: Option<ExtModuleLoaderCb>,
+ ) -> JsRuntimeImpl<FOR_SNAPSHOT>
+ where
+ JsRuntimeImpl<FOR_SNAPSHOT>: JsRuntimeInternalTrait,
+ {
+ let (op_state, ops) = Self::create_opstate(&mut options, &snapshot_options);
let op_state = Rc::new(RefCell::new(op_state));
+ // Collect event-loop middleware
+ let mut event_loop_middlewares =
+ Vec::with_capacity(options.extensions.len());
+ for extension in &mut options.extensions {
+ if let Some(middleware) = extension.init_event_loop_middleware() {
+ event_loop_middlewares.push(middleware);
+ }
+ }
+
let align = std::mem::align_of::<usize>();
let layout = std::alloc::Layout::from_size_align(
std::mem::size_of::<*mut v8::OwnedIsolate>(),
@@ -397,20 +542,22 @@ impl JsRuntime {
context_state.borrow_mut().op_ctxs = op_ctxs;
context_state.borrow_mut().isolate = Some(isolate_ptr);
- let snapshot_options = snapshot_util::SnapshotOptions::from_bools(
- options.startup_snapshot.is_some(),
- options.will_snapshot,
- );
let refs = bindings::external_references(&context_state.borrow().op_ctxs);
// V8 takes ownership of external_references.
let refs: &'static v8::ExternalReferences = Box::leak(Box::new(refs));
- let mut maybe_snapshotted_data = None;
- let (mut isolate, global_context) = Self::create_isolate(
- snapshot_options.will_snapshot(),
+ let bindings_mode = match snapshot_options {
+ SnapshotOptions::None => bindings::BindingsMode::New,
+ SnapshotOptions::Create => bindings::BindingsMode::New,
+ SnapshotOptions::Load(_) => bindings::BindingsMode::LoadedFinal,
+ SnapshotOptions::CreateFromExisting(_) => bindings::BindingsMode::Loaded,
+ };
+ let snapshotting = snapshot_options.will_snapshot();
+
+ let (mut isolate, global_context, snapshotted_data) = Self::create_isolate(
refs,
options.create_params.take(),
- options.startup_snapshot.take(),
+ snapshot_options,
);
// SAFETY: this is first use of `isolate_ptr` so we are sure we're
// not overwriting an existing pointer.
@@ -419,7 +566,7 @@ impl JsRuntime {
isolate_ptr.read()
};
- let mut context_scope =
+ let mut context_scope: v8::HandleScope =
v8::HandleScope::with_context(&mut isolate, global_context.clone());
let scope = &mut context_scope;
let context = v8::Local::new(scope, global_context.clone());
@@ -428,15 +575,9 @@ impl JsRuntime {
scope,
context,
&context_state.borrow().op_ctxs,
- snapshot_options,
+ bindings_mode,
);
- // Get module map data from the snapshot
- if has_startup_snapshot {
- maybe_snapshotted_data =
- Some(snapshot_util::get_snapshotted_data(scope, context));
- }
-
context.set_slot(scope, context_state.clone());
op_state.borrow_mut().put(isolate_ptr);
@@ -449,23 +590,6 @@ impl JsRuntime {
let loader = options
.module_loader
.unwrap_or_else(|| Rc::new(NoopModuleLoader));
- #[cfg(feature = "include_js_files_for_snapshotting")]
- if snapshot_options.will_snapshot() {
- for source in options
- .extensions
- .iter()
- .flat_map(|e| vec![e.get_esm_sources(), e.get_js_sources()])
- .flatten()
- .flatten()
- {
- use crate::ExtensionFileSourceCode;
- if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
- &source.code
- {
- println!("cargo:rerun-if-changed={}", path.display())
- }
- }
- }
{
let global_realm = JsRealmInner::new(
@@ -480,70 +604,58 @@ impl JsRuntime {
state.known_realms.push(global_realm);
}
scope.set_data(
- Self::STATE_DATA_OFFSET,
+ STATE_DATA_OFFSET,
Rc::into_raw(state_rc.clone()) as *mut c_void,
);
let module_map_rc = Rc::new(RefCell::new(ModuleMap::new(loader, op_state)));
- if let Some(snapshotted_data) = maybe_snapshotted_data {
+ if let Some(snapshotted_data) = snapshotted_data {
let mut module_map = module_map_rc.borrow_mut();
module_map.update_with_snapshotted_data(scope, snapshotted_data);
}
scope.set_data(
- Self::MODULE_MAP_DATA_OFFSET,
+ MODULE_MAP_DATA_OFFSET,
Rc::into_raw(module_map_rc.clone()) as *mut c_void,
);
drop(context_scope);
- let mut js_runtime = Self {
- v8_isolate: Some(isolate),
- snapshot_options,
- snapshot_module_load_cb: options.snapshot_module_load_cb.map(Rc::new),
+ let mut js_runtime = JsRuntimeImpl {
+ inner: InnerIsolateState {
+ snapshotting,
+ state: ManuallyDropRc(ManuallyDrop::new(state_rc)),
+ v8_isolate: ManuallyDrop::new(isolate),
+ },
+ bindings_mode,
allocations: IsolateAllocations::default(),
- event_loop_middlewares: Vec::with_capacity(options.extensions.len()),
- extensions: Rc::new(RefCell::new(options.extensions)),
- state: state_rc,
+ event_loop_middlewares,
+ extensions: options.extensions,
module_map: Some(module_map_rc),
is_main: options.is_main,
};
- // Init resources and ops before extensions to make sure they are
- // available during the initialization process.
- js_runtime.init_extension_ops().unwrap();
let realm = js_runtime.global_realm();
- js_runtime.init_extension_js(&realm).unwrap();
-
+ // TODO(mmastrac): We should thread errors back out of the runtime
+ js_runtime
+ .init_extension_js(&realm, maybe_load_callback)
+ .unwrap();
js_runtime
}
/// Create a new [`v8::OwnedIsolate`] and its global [`v8::Context`] from optional parameters and snapshot.
fn create_isolate(
- will_snapshot: bool,
refs: &'static v8::ExternalReferences,
params: Option<CreateParams>,
- snapshot: Option<Snapshot>,
- ) -> (v8::OwnedIsolate, v8::Global<v8::Context>) {
- let mut isolate = if will_snapshot {
- snapshot_util::create_snapshot_creator(refs, snapshot)
- } else {
- let mut params = params
- .unwrap_or_default()
- .embedder_wrapper_type_info_offsets(
- V8_WRAPPER_TYPE_INDEX,
- V8_WRAPPER_OBJECT_INDEX,
- )
- .external_references(&**refs);
-
- if let Some(snapshot) = snapshot {
- params = match snapshot {
- Snapshot::Static(data) => params.snapshot_blob(data),
- Snapshot::JustCreated(data) => params.snapshot_blob(data),
- Snapshot::Boxed(data) => params.snapshot_blob(data),
- };
- }
-
- v8::Isolate::new(params)
- };
+ snapshot: SnapshotOptions,
+ ) -> (
+ v8::OwnedIsolate,
+ v8::Global<v8::Context>,
+ Option<SnapshottedData>,
+ )
+ where
+ JsRuntimeImpl<FOR_SNAPSHOT>: JsRuntimeInternalTrait,
+ {
+ let has_snapshot = snapshot.loaded();
+ let mut isolate = Self::create_raw_isolate(refs, params, snapshot);
isolate.set_capture_stack_trace_for_uncaught_exceptions(true, 10);
isolate.set_promise_reject_callback(bindings::promise_reject_callback);
@@ -557,38 +669,32 @@ impl JsRuntime {
bindings::wasm_async_resolve_promise_callback,
);
- let context = {
+ let (context, snapshotted_data) = {
let scope = &mut v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(scope);
- v8::Global::new(scope, context)
- };
- (isolate, context)
- }
- fn drop_state_and_module_map(v8_isolate: &mut OwnedIsolate) {
- let state_ptr = v8_isolate.get_data(Self::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 RefCell<JsRuntimeState>) };
- drop(state_rc);
+ // Get module map data from the snapshot
+ let snapshotted_data = if has_snapshot {
+ Some(snapshot_util::get_snapshotted_data(scope, context))
+ } else {
+ None
+ };
- let module_map_ptr = v8_isolate.get_data(Self::MODULE_MAP_DATA_OFFSET);
- let module_map_rc =
- // SAFETY: We are sure that it's a valid pointer for whole lifetime of
- // the runtime.
- unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) };
- drop(module_map_rc);
+ (v8::Global::new(scope, context), snapshotted_data)
+ };
+
+ (isolate, context, snapshotted_data)
}
#[inline]
- pub(crate) fn module_map(&mut self) -> &Rc<RefCell<ModuleMap>> {
+ pub(crate) fn module_map(&self) -> &Rc<RefCell<ModuleMap>> {
self.module_map.as_ref().unwrap()
}
#[inline]
pub fn global_context(&self) -> v8::Global<v8::Context> {
self
+ .inner
.state
.borrow()
.known_realms
@@ -600,23 +706,28 @@ impl JsRuntime {
#[inline]
pub fn v8_isolate(&mut self) -> &mut v8::OwnedIsolate {
- self.v8_isolate.as_mut().unwrap()
+ &mut self.inner.v8_isolate
}
#[inline]
pub fn inspector(&mut self) -> Rc<RefCell<JsRuntimeInspector>> {
- self.state.borrow().inspector()
+ self.inner.state.borrow().inspector()
}
#[inline]
pub fn global_realm(&mut self) -> JsRealm {
- let state = self.state.borrow();
+ let state = self.inner.state.borrow();
state.global_realm.clone().unwrap()
}
+ /// Returns the extensions that this runtime is using (including internal ones).
+ pub fn extensions(&self) -> &Vec<Extension> {
+ &self.extensions
+ }
+
/// Creates a new realm (V8 context) in this JS execution context,
/// pre-initialized with all of the extensions that were passed in
- /// [`RuntimeOptions::extensions`] when the [`JsRuntime`] was
+ /// [`RuntimeOptions::extensions`] when the [`JsRuntimeImpl`] was
/// constructed.
pub fn create_realm(&mut self) -> Result<JsRealm, Error> {
let realm = {
@@ -656,21 +767,21 @@ impl JsRuntime {
scope,
context,
&context_state.borrow().op_ctxs,
- self.snapshot_options,
+ self.bindings_mode,
);
context.set_slot(scope, context_state.clone());
let realm = JsRealmInner::new(
context_state,
v8::Global::new(scope, context),
- self.state.clone(),
+ self.inner.state.clone(),
false,
);
- let mut state = self.state.borrow_mut();
+ let mut state = self.inner.state.borrow_mut();
state.known_realms.push(realm.clone());
JsRealm::new(realm)
};
- self.init_extension_js(&realm)?;
+ self.init_extension_js(&realm, None)?;
Ok(realm)
}
@@ -679,65 +790,38 @@ impl JsRuntime {
self.global_realm().handle_scope(self.v8_isolate())
}
- pub(crate) fn state(isolate: &v8::Isolate) -> Rc<RefCell<JsRuntimeState>> {
- let state_ptr = isolate.get_data(Self::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 RefCell<JsRuntimeState>) };
- let state = state_rc.clone();
- Rc::into_raw(state_rc);
- state
- }
-
- pub(crate) fn module_map_from(
- isolate: &v8::Isolate,
- ) -> Rc<RefCell<ModuleMap>> {
- let module_map_ptr = isolate.get_data(Self::MODULE_MAP_DATA_OFFSET);
- let module_map_rc =
- // SAFETY: We are sure that it's a valid pointer for whole lifetime of
- // the runtime.
- unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) };
- let module_map = module_map_rc.clone();
- Rc::into_raw(module_map_rc);
- module_map
- }
-
/// Initializes JS of provided Extensions in the given realm.
- fn init_extension_js(&mut self, realm: &JsRealm) -> Result<(), Error> {
- // Initalization of JS happens in phases:
+ fn init_extension_js(
+ &mut self,
+ realm: &JsRealm,
+ maybe_load_callback: Option<ExtModuleLoaderCb>,
+ ) -> Result<(), Error> {
+ // Initialization of JS happens in phases:
// 1. Iterate through all extensions:
// a. Execute all extension "script" JS files
// b. Load all extension "module" JS files (but do not execute them yet)
// 2. Iterate through all extensions:
// a. If an extension has a `esm_entry_point`, execute it.
+ // Take extensions temporarily so we can avoid have a mutable reference to self
+ let extensions = std::mem::take(&mut self.extensions);
+
// TODO(nayeemrmn): Module maps should be per-realm.
let module_map = self.module_map.as_ref().unwrap();
let loader = module_map.borrow().loader.clone();
let ext_loader = Rc::new(ExtModuleLoader::new(
- &self.extensions.borrow(),
- self.snapshot_module_load_cb.clone(),
+ &extensions,
+ maybe_load_callback.map(Rc::new),
));
module_map.borrow_mut().loader = ext_loader;
let mut esm_entrypoints = vec![];
- // Take extensions to avoid double-borrow
- let extensions = std::mem::take(&mut self.extensions);
-
futures::executor::block_on(async {
- let num_of_extensions = extensions.borrow().len();
- for i in 0..num_of_extensions {
- let (maybe_esm_files, maybe_esm_entry_point) = {
- let exts = extensions.borrow();
- (
- exts[i].get_esm_sources().map(|e| e.to_owned()),
- exts[i].get_esm_entry_point(),
- )
- };
+ for extension in &extensions {
+ let maybe_esm_entry_point = extension.get_esm_entry_point();
- if let Some(esm_files) = maybe_esm_files {
+ if let Some(esm_files) = extension.get_esm_sources() {
for file_source in esm_files {
self
.load_side_module(
@@ -752,10 +836,7 @@ impl JsRuntime {
esm_entrypoints.push(entry_point);
}
- let exts = extensions.borrow();
- let ext = &exts[i];
-
- if let Some(js_files) = ext.get_js_sources() {
+ if let Some(js_files) = extension.get_js_sources() {
for file_source in js_files {
realm.execute_script(
self.v8_isolate(),
@@ -765,7 +846,7 @@ impl JsRuntime {
}
}
- if ext.is_core {
+ if extension.is_core {
self.init_cbs(realm);
}
}
@@ -799,9 +880,7 @@ impl JsRuntime {
Ok::<_, anyhow::Error>(())
})?;
- // Restore extensions
self.extensions = extensions;
-
self.module_map.as_ref().unwrap().borrow_mut().loader = loader;
Ok(())
}
@@ -866,19 +945,36 @@ impl JsRuntime {
}
/// Initializes ops of provided Extensions
- fn init_extension_ops(&mut self) -> Result<(), Error> {
- let op_state = self.op_state();
+ fn create_opstate(
+ options: &mut RuntimeOptions,
+ snapshot_options: &SnapshotOptions,
+ ) -> (OpState, Vec<OpDecl>) {
+ // Add built-in extension
+ if snapshot_options.loaded() {
+ options
+ .extensions
+ .insert(0, crate::ops_builtin::core::init_ops());
+ } else {
+ options
+ .extensions
+ .insert(0, crate::ops_builtin::core::init_ops_and_esm());
+ }
+
+ let ops = Self::collect_ops(&mut options.extensions);
+
+ let mut op_state = OpState::new(ops.len());
+
+ if let Some(get_error_class_fn) = options.get_error_class_fn {
+ op_state.get_error_class_fn = get_error_class_fn;
+ }
+
// Setup state
- for e in self.extensions.borrow_mut().iter_mut() {
+ for e in &mut options.extensions {
// ops are already registered during in bindings::initialize_context();
- e.init_state(&mut op_state.borrow_mut());
-
- // Setup event-loop middleware
- if let Some(middleware) = e.init_event_loop_middleware() {
- self.event_loop_middlewares.push(middleware);
- }
+ e.init_state(&mut op_state);
}
- Ok(())
+
+ (op_state, ops)
}
pub fn eval<'s, T>(
@@ -954,7 +1050,7 @@ impl JsRuntime {
/// Returns the runtime's op state, which can be used to maintain ops
/// and access resources between op calls.
pub fn op_state(&mut self) -> Rc<RefCell<OpState>> {
- let state = self.state.borrow();
+ let state = self.inner.state.borrow();
state.op_state.clone()
}
@@ -1029,56 +1125,6 @@ impl JsRuntime {
self.resolve_value(promise).await
}
- /// Takes a snapshot. The isolate should have been created with will_snapshot
- /// set to true.
- ///
- /// `Error` can usually be downcast to `JsError`.
- pub fn snapshot(mut self) -> v8::StartupData {
- self.state.borrow_mut().inspector.take();
-
- // Set the context to be snapshot's default context
- {
- let context = self.global_context();
- let mut scope = self.handle_scope();
- let local_context = v8::Local::new(&mut scope, context);
- scope.set_default_context(local_context);
- }
-
- // Serialize the module map and store its data in the snapshot.
- {
- let snapshotted_data = {
- let module_map_rc = self.module_map.take().unwrap();
- let module_map = module_map_rc.borrow();
- module_map.serialize_for_snapshotting(&mut self.handle_scope())
- };
-
- let context = self.global_context();
- let mut scope = self.handle_scope();
- snapshot_util::set_snapshotted_data(
- &mut scope,
- context,
- snapshotted_data,
- );
- }
-
- // Drop existing ModuleMap to drop v8::Global handles
- {
- let v8_isolate = self.v8_isolate();
- Self::drop_state_and_module_map(v8_isolate);
- }
-
- // Drop other v8::Global handles before snapshotting
- {
- let state = self.state.clone();
- state.borrow_mut().destroy_all_realms();
- }
-
- let snapshot_creator = self.v8_isolate.take().unwrap();
- snapshot_creator
- .create_blob(v8::FunctionCodeHandling::Keep)
- .unwrap()
- }
-
/// Returns the namespace object of a module.
///
/// This is only available after module evaluation has completed.
@@ -1170,18 +1216,18 @@ impl JsRuntime {
}
pub fn maybe_init_inspector(&mut self) {
- if self.state.borrow().inspector.is_some() {
+ if self.inner.state.borrow().inspector.is_some() {
return;
}
let context = self.global_context();
let scope = &mut v8::HandleScope::with_context(
- self.v8_isolate.as_mut().unwrap(),
+ self.inner.v8_isolate.as_mut(),
context.clone(),
);
let context = v8::Local::new(scope, context);
- let mut state = self.state.borrow_mut();
+ let mut state = self.inner.state.borrow_mut();
state.inspector =
Some(JsRuntimeInspector::new(scope, context, self.is_main));
}
@@ -1258,7 +1304,7 @@ impl JsRuntime {
let has_inspector: bool;
{
- let state = self.state.borrow();
+ let state = self.inner.state.borrow();
has_inspector = state.inspector.is_some();
state.waker.register(cx.waker());
}
@@ -1306,7 +1352,7 @@ impl JsRuntime {
// Event loop middlewares
let mut maybe_scheduling = false;
{
- let op_state = self.state.borrow().op_state.clone();
+ let op_state = self.inner.state.borrow().op_state.clone();
for f in &self.event_loop_middlewares {
if f(op_state.clone(), cx) {
maybe_scheduling = true;
@@ -1342,7 +1388,7 @@ impl JsRuntime {
return Poll::Ready(Ok(()));
}
- let state = self.state.borrow();
+ let state = self.inner.state.borrow();
// Check if more async ops have been dispatched
// during this turn of event loop.
@@ -1391,7 +1437,8 @@ impl JsRuntime {
|| pending_state.has_tick_scheduled
{
// pass, will be polled again
- } else if self.state.borrow().dyn_module_evaluate_idle_counter >= 1 {
+ } else if self.inner.state.borrow().dyn_module_evaluate_idle_counter >= 1
+ {
let scope = &mut self.handle_scope();
let messages = find_stalled_top_level_await(scope);
// We are gonna print only a single message to provide a nice formatting
@@ -1403,7 +1450,7 @@ impl JsRuntime {
let js_error = JsError::from_v8_message(scope, msg);
return Poll::Ready(Err(js_error.into()));
} else {
- let mut state = self.state.borrow_mut();
+ let mut state = self.inner.state.borrow_mut();
// Delay the above error by one spin of the event loop. A dynamic import
// evaluation may complete during this, in which case the counter will
// reset.
@@ -1416,20 +1463,63 @@ impl JsRuntime {
}
fn event_loop_pending_state(&mut self) -> EventLoopPendingState {
- let isolate = self.v8_isolate.as_mut().unwrap();
- let mut scope = v8::HandleScope::new(isolate);
+ let mut scope = v8::HandleScope::new(self.inner.v8_isolate.as_mut());
EventLoopPendingState::new(
&mut scope,
- &mut self.state.borrow_mut(),
+ &mut self.inner.state.borrow_mut(),
&self.module_map.as_ref().unwrap().borrow(),
)
}
+}
+
+impl JsRuntime {
+ /// Only constructor, configuration is done through `options`.
+ pub fn new(mut options: RuntimeOptions) -> JsRuntime {
+ JsRuntimeImpl::<false>::init_v8(options.v8_platform.take());
+
+ let snapshot_options = snapshot_util::SnapshotOptions::new_from(
+ options.startup_snapshot.take(),
+ false,
+ );
+
+ JsRuntime(JsRuntimeImpl::<false>::new_runtime(
+ options,
+ snapshot_options,
+ None,
+ ))
+ }
+
+ pub(crate) fn state_from(
+ isolate: &v8::Isolate,
+ ) -> Rc<RefCell<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 RefCell<JsRuntimeState>) };
+ let state = state_rc.clone();
+ std::mem::forget(state_rc);
+ state
+ }
+
+ pub(crate) fn module_map_from(
+ isolate: &v8::Isolate,
+ ) -> Rc<RefCell<ModuleMap>> {
+ let module_map_ptr = isolate.get_data(MODULE_MAP_DATA_OFFSET);
+ let module_map_rc =
+ // SAFETY: We are sure that it's a valid pointer for whole lifetime of
+ // the runtime.
+ unsafe { Rc::from_raw(module_map_ptr as *const RefCell<ModuleMap>) };
+ let module_map = module_map_rc.clone();
+ std::mem::forget(module_map_rc);
+ module_map
+ }
pub(crate) fn event_loop_pending_state_from_scope(
scope: &mut v8::HandleScope,
) -> EventLoopPendingState {
- let state = Self::state(scope);
- let module_map = Self::module_map_from(scope);
+ let state = JsRuntime::state_from(scope);
+ let module_map = JsRuntime::module_map_from(scope);
let state = EventLoopPendingState::new(
scope,
&mut state.borrow_mut(),
@@ -1439,6 +1529,102 @@ impl JsRuntime {
}
}
+impl JsRuntimeForSnapshot {
+ pub fn new(
+ mut options: RuntimeOptions,
+ runtime_snapshot_options: RuntimeSnapshotOptions,
+ ) -> JsRuntimeForSnapshot {
+ JsRuntimeImpl::<true>::init_v8(options.v8_platform.take());
+
+ let snapshot_options = snapshot_util::SnapshotOptions::new_from(
+ options.startup_snapshot.take(),
+ true,
+ );
+
+ JsRuntimeForSnapshot(JsRuntimeImpl::<true>::new_runtime(
+ options,
+ snapshot_options,
+ runtime_snapshot_options.snapshot_module_load_cb,
+ ))
+ }
+
+ /// Takes a snapshot and consumes the runtime.
+ ///
+ /// `Error` can usually be downcast to `JsError`.
+ pub fn snapshot(mut self) -> v8::StartupData {
+ // Ensure there are no live inspectors to prevent crashes.
+ self.inner.prepare_for_cleanup();
+
+ // Set the context to be snapshot's default context
+ {
+ let context = self.global_context();
+ let mut scope = self.handle_scope();
+ let local_context = v8::Local::new(&mut scope, context);
+ scope.set_default_context(local_context);
+ }
+
+ // Serialize the module map and store its data in the snapshot.
+ {
+ let snapshotted_data = {
+ let module_map_rc = self.module_map.take().unwrap();
+ let module_map = module_map_rc.borrow();
+ module_map.serialize_for_snapshotting(&mut self.handle_scope())
+ };
+
+ let context = self.global_context();
+ let mut scope = self.handle_scope();
+ snapshot_util::set_snapshotted_data(
+ &mut scope,
+ context,
+ snapshotted_data,
+ );
+ }
+
+ self
+ .0
+ .inner
+ .prepare_for_snapshot()
+ .create_blob(v8::FunctionCodeHandling::Keep)
+ .unwrap()
+ }
+}
+
+impl JsRuntimeInternalTrait for JsRuntimeImpl<true> {
+ fn create_raw_isolate(
+ refs: &'static v8::ExternalReferences,
+ _params: Option<CreateParams>,
+ snapshot: SnapshotOptions,
+ ) -> v8::OwnedIsolate {
+ snapshot_util::create_snapshot_creator(refs, snapshot)
+ }
+}
+
+impl JsRuntimeInternalTrait for JsRuntimeImpl<false> {
+ fn create_raw_isolate(
+ refs: &'static v8::ExternalReferences,
+ params: Option<CreateParams>,
+ snapshot: SnapshotOptions,
+ ) -> v8::OwnedIsolate {
+ let mut params = params
+ .unwrap_or_default()
+ .embedder_wrapper_type_info_offsets(
+ V8_WRAPPER_TYPE_INDEX,
+ V8_WRAPPER_OBJECT_INDEX,
+ )
+ .external_references(&**refs);
+
+ if let Some(snapshot) = snapshot.snapshot() {
+ params = match snapshot {
+ Snapshot::Static(data) => params.snapshot_blob(data),
+ Snapshot::JustCreated(data) => params.snapshot_blob(data),
+ Snapshot::Boxed(data) => params.snapshot_blob(data),
+ };
+ }
+
+ v8::Isolate::new(params)
+ }
+}
+
fn get_stalled_top_level_await_message_for_module(
scope: &mut v8::HandleScope,
module_id: ModuleId,
@@ -1544,7 +1730,7 @@ 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.
+ // in `JsRuntimeImpl::allocations` and thus is guaranteed to outlive the isolate.
let callback = unsafe { &mut *(data as *mut F) };
callback(current_heap_limit, initial_heap_limit)
}
@@ -1567,7 +1753,7 @@ pub(crate) fn exception_to_err_result<T>(
exception: v8::Local<v8::Value>,
in_promise: bool,
) -> Result<T, Error> {
- let state_rc = JsRuntime::state(scope);
+ let state_rc = JsRuntime::state_from(scope);
let was_terminating_execution = scope.is_execution_terminating();
// Disable running microtasks for a moment. When upgrading to V8 v11.4
@@ -1613,7 +1799,7 @@ pub(crate) fn exception_to_err_result<T>(
}
// Related to module loading
-impl JsRuntime {
+impl<const FOR_SNAPSHOT: bool> JsRuntimeImpl<FOR_SNAPSHOT> {
pub(crate) fn instantiate_module(
&mut self,
id: ModuleId,
@@ -1680,9 +1866,9 @@ impl JsRuntime {
// For more details see:
// https://github.com/denoland/deno/issues/4908
// https://v8.dev/features/top-level-await#module-execution-order
- let global_realm = self.state.borrow_mut().global_realm.clone().unwrap();
- let scope =
- &mut global_realm.handle_scope(self.v8_isolate.as_mut().unwrap());
+ let global_realm =
+ self.inner.state.borrow_mut().global_realm.clone().unwrap();
+ let scope = &mut global_realm.handle_scope(&mut self.inner.v8_isolate);
let tc_scope = &mut v8::TryCatch::new(scope);
let module = v8::Local::new(tc_scope, &module_handle);
let maybe_value = module.evaluate(tc_scope);
@@ -1710,6 +1896,7 @@ impl JsRuntime {
};
self
+ .inner
.state
.borrow_mut()
.pending_dyn_mod_evaluate
@@ -1729,11 +1916,11 @@ impl JsRuntime {
/// Evaluates an already instantiated ES module.
///
/// Returns a receiver handle that resolves when module promise resolves.
- /// Implementors must manually call [`JsRuntime::run_event_loop`] to drive
+ /// Implementors must manually call [`JsRuntimeImpl::run_event_loop`] to drive
/// module evaluation future.
///
/// `Error` can usually be downcast to `JsError` and should be awaited and
- /// checked after [`JsRuntime::run_event_loop`] completion.
+ /// checked after [`JsRuntimeImpl::run_event_loop`] completion.
///
/// This function panics if module has not been instantiated.
pub fn mod_evaluate(
@@ -1741,7 +1928,7 @@ impl JsRuntime {
id: ModuleId,
) -> oneshot::Receiver<Result<(), Error>> {
let global_realm = self.global_realm();
- let state_rc = self.state.clone();
+ let state_rc = self.inner.state.clone();
let module_map_rc = self.module_map().clone();
let scope = &mut self.handle_scope();
let tc_scope = &mut v8::TryCatch::new(scope);
@@ -1766,7 +1953,7 @@ impl JsRuntime {
// Because that promise is created internally by V8, when error occurs during
// module evaluation the promise is rejected, and since the promise has no rejection
// handler it will result in call to `bindings::promise_reject_callback` adding
- // the promise to pending promise rejection table - meaning JsRuntime will return
+ // the promise to pending promise rejection table - meaning JsRuntimeImpl will return
// error on next poll().
//
// This situation is not desirable as we want to manually return error at the
@@ -1903,7 +2090,7 @@ impl JsRuntime {
}
fn dynamic_import_resolve(&mut self, id: ModuleLoadId, mod_id: ModuleId) {
- let state_rc = self.state.clone();
+ let state_rc = self.inner.state.clone();
let module_map_rc = self.module_map().clone();
let scope = &mut self.handle_scope();
@@ -2078,14 +2265,14 @@ impl JsRuntime {
/// then another turn of event loop must be performed.
fn evaluate_pending_module(&mut self) {
let maybe_module_evaluation =
- self.state.borrow_mut().pending_mod_evaluate.take();
+ self.inner.state.borrow_mut().pending_mod_evaluate.take();
if maybe_module_evaluation.is_none() {
return;
}
let mut module_evaluation = maybe_module_evaluation.unwrap();
- let state_rc = self.state.clone();
+ let state_rc = self.inner.state.clone();
let scope = &mut self.handle_scope();
let promise_global = module_evaluation.promise.clone().unwrap();
@@ -2126,8 +2313,9 @@ impl JsRuntime {
// Returns true if some dynamic import was resolved.
fn evaluate_dyn_imports(&mut self) -> bool {
- let pending =
- std::mem::take(&mut self.state.borrow_mut().pending_dyn_mod_evaluate);
+ let pending = std::mem::take(
+ &mut self.inner.state.borrow_mut().pending_dyn_mod_evaluate,
+ );
if pending.is_empty() {
return false;
}
@@ -2170,7 +2358,7 @@ impl JsRuntime {
}
}
}
- self.state.borrow_mut().pending_dyn_mod_evaluate = still_pending;
+ self.inner.state.borrow_mut().pending_dyn_mod_evaluate = still_pending;
resolved_any
}
@@ -2179,7 +2367,7 @@ impl JsRuntime {
/// 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`
+ /// User must call [`JsRuntimeImpl::mod_evaluate`] with returned `ModuleId`
/// manually after load is finished.
pub async fn load_main_module(
&mut self,
@@ -2234,7 +2422,7 @@ impl JsRuntime {
/// 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`
+ /// User must call [`JsRuntimeImpl::mod_evaluate`] with returned `ModuleId`
/// manually after load is finished.
pub async fn load_side_module(
&mut self,
@@ -2285,7 +2473,7 @@ impl JsRuntime {
}
fn check_promise_rejections(&mut self) -> Result<(), Error> {
- let state = self.state.clone();
+ let state = self.inner.state.clone();
let scope = &mut self.handle_scope();
let state = state.borrow();
for realm in &state.known_realms {
@@ -2298,21 +2486,16 @@ impl JsRuntime {
fn do_js_event_loop_tick(&mut self, cx: &mut Context) -> Result<(), Error> {
// Now handle actual ops.
{
- let mut state = self.state.borrow_mut();
+ let mut state = self.inner.state.borrow_mut();
state.have_unpolled_ops = false;
}
// Handle responses for each realm.
- let isolate = self.v8_isolate.as_mut().unwrap();
- let realm_count = self.state.clone().borrow().known_realms.len();
+ let state = self.inner.state.clone();
+ let isolate = &mut self.inner.v8_isolate;
+ let realm_count = state.borrow().known_realms.len();
for realm_idx in 0..realm_count {
- let realm = self
- .state
- .borrow()
- .known_realms
- .get(realm_idx)
- .unwrap()
- .clone();
+ let realm = state.borrow().known_realms.get(realm_idx).unwrap().clone();
let context_state = realm.state();
let mut context_state = context_state.borrow_mut();
let scope = &mut realm.handle_scope(isolate);
@@ -2334,8 +2517,7 @@ impl JsRuntime {
context_state.pending_ops.poll_next_unpin(cx)
{
let (promise_id, op_id, mut resp) = item;
- self
- .state
+ state
.borrow()
.op_state
.borrow()
@@ -2352,7 +2534,7 @@ impl JsRuntime {
}
let has_tick_scheduled =
- v8::Boolean::new(scope, self.state.borrow().has_tick_scheduled);
+ v8::Boolean::new(scope, self.inner.state.borrow().has_tick_scheduled);
args.push(has_tick_scheduled.into());
let js_event_loop_tick_cb_handle =
@@ -2386,7 +2568,7 @@ pub fn queue_fast_async_op<R: serde::Serialize + 'static>(
) {
let runtime_state = match ctx.runtime_state.upgrade() {
Some(rc_state) => rc_state,
- // atleast 1 Rc is held by the JsRuntime.
+ // at least 1 Rc is held by the JsRuntimeImpl.
None => unreachable!(),
};
let get_class = {
@@ -2484,7 +2666,7 @@ pub fn queue_async_op<'s>(
) -> Option<v8::Local<'s, v8::Value>> {
let runtime_state = match ctx.runtime_state.upgrade() {
Some(rc_state) => rc_state,
- // atleast 1 Rc is held by the JsRuntime.
+ // at least 1 Rc is held by the JsRuntimeImpl.
None => unreachable!(),
};
@@ -3053,13 +3235,24 @@ pub mod tests {
.await;
}
+ /// Ensure that putting the inspector into OpState doesn't cause crashes. The only valid place we currently allow
+ /// the inspector to be stashed without cleanup is the OpState, and this should not actually cause crashes.
+ #[test]
+ fn inspector() {
+ let mut runtime = JsRuntime::new(RuntimeOptions {
+ inspector: true,
+ ..Default::default()
+ });
+ // This was causing a crash
+ runtime.op_state().borrow_mut().put(runtime.inspector());
+ runtime.execute_script_static("check.js", "null").unwrap();
+ }
+
#[test]
fn will_snapshot() {
let snapshot = {
- let mut runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- ..Default::default()
- });
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
runtime.execute_script_static("a.js", "a = 1 + 2").unwrap();
runtime.snapshot()
};
@@ -3077,10 +3270,8 @@ pub mod tests {
#[test]
fn will_snapshot2() {
let startup_data = {
- let mut runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- ..Default::default()
- });
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
runtime
.execute_script_static("a.js", "let a = 1 + 2")
.unwrap();
@@ -3088,11 +3279,13 @@ pub mod tests {
};
let snapshot = Snapshot::JustCreated(startup_data);
- let mut runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- startup_snapshot: Some(snapshot),
- ..Default::default()
- });
+ let mut runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ startup_snapshot: Some(snapshot),
+ ..Default::default()
+ },
+ Default::default(),
+ );
let startup_data = {
runtime
@@ -3120,10 +3313,8 @@ pub mod tests {
#[test]
fn test_snapshot_callbacks() {
let snapshot = {
- let mut runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- ..Default::default()
- });
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
runtime
.execute_script_static(
"a.js",
@@ -3157,10 +3348,8 @@ pub mod tests {
#[test]
fn test_from_boxed_snapshot() {
let snapshot = {
- let mut runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- ..Default::default()
- });
+ let mut runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
runtime.execute_script_static("a.js", "a = 1 + 2").unwrap();
let snap: &[u8] = &runtime.snapshot();
Vec::from(snap).into_boxed_slice()
@@ -3375,8 +3564,8 @@ pub mod tests {
}
}
- fn create_module(
- runtime: &mut JsRuntime,
+ fn create_module<const FOR_SNAPSHOT: bool>(
+ runtime: &mut JsRuntimeImpl<FOR_SNAPSHOT>,
i: usize,
main: bool,
) -> ModuleInfo {
@@ -3419,7 +3608,10 @@ pub mod tests {
}
}
- fn assert_module_map(runtime: &mut JsRuntime, modules: &Vec<ModuleInfo>) {
+ fn assert_module_map<const FOR_SNAPSHOT: bool>(
+ runtime: &mut JsRuntimeImpl<FOR_SNAPSHOT>,
+ modules: &Vec<ModuleInfo>,
+ ) {
let module_map_rc = runtime.module_map();
let module_map = module_map_rc.borrow();
assert_eq!(module_map.handles.len(), modules.len());
@@ -3453,14 +3645,16 @@ pub mod tests {
}
let loader = Rc::new(ModsLoader::default());
- let mut runtime = JsRuntime::new(RuntimeOptions {
- module_loader: Some(loader.clone()),
- will_snapshot: true,
- extensions: vec![Extension::builder("text_ext")
- .ops(vec![op_test::decl()])
- .build()],
- ..Default::default()
- });
+ let mut runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ module_loader: Some(loader.clone()),
+ extensions: vec![Extension::builder("text_ext")
+ .ops(vec![op_test::decl()])
+ .build()],
+ ..Default::default()
+ },
+ Default::default(),
+ );
let specifier = crate::resolve_url("file:///0.js").unwrap();
let source_code =
@@ -3489,15 +3683,17 @@ pub mod tests {
let snapshot = runtime.snapshot();
- let mut runtime2 = JsRuntime::new(RuntimeOptions {
- module_loader: Some(loader.clone()),
- will_snapshot: true,
- startup_snapshot: Some(Snapshot::JustCreated(snapshot)),
- extensions: vec![Extension::builder("text_ext")
- .ops(vec![op_test::decl()])
- .build()],
- ..Default::default()
- });
+ let mut runtime2 = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ module_loader: Some(loader.clone()),
+ startup_snapshot: Some(Snapshot::JustCreated(snapshot)),
+ extensions: vec![Extension::builder("text_ext")
+ .ops(vec![op_test::decl()])
+ .build()],
+ ..Default::default()
+ },
+ Default::default(),
+ );
assert_module_map(&mut runtime2, &modules);
@@ -3997,8 +4193,7 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1);
- let state_rc = JsRuntime::state(runtime.v8_isolate());
- state_rc.borrow_mut().has_tick_scheduled = false;
+ runtime.inner.state.borrow_mut().has_tick_scheduled = false;
assert!(matches!(
runtime.poll_event_loop(cx, false),
Poll::Ready(Ok(()))
@@ -4479,10 +4674,8 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
#[test]
fn js_realm_init_snapshot() {
let snapshot = {
- let runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- ..Default::default()
- });
+ let runtime =
+ JsRuntimeForSnapshot::new(Default::default(), Default::default());
let snap: &[u8] = &runtime.snapshot();
Vec::from(snap).into_boxed_slice()
};
diff --git a/core/snapshot_util.rs b/core/snapshot_util.rs
index 20019f5cc..05a196f50 100644
--- a/core/snapshot_util.rs
+++ b/core/snapshot_util.rs
@@ -4,9 +4,10 @@ use std::path::Path;
use std::path::PathBuf;
use std::time::Instant;
+use crate::runtime::RuntimeSnapshotOptions;
use crate::ExtModuleLoaderCb;
use crate::Extension;
-use crate::JsRuntime;
+use crate::JsRuntimeForSnapshot;
use crate::RuntimeOptions;
use crate::Snapshot;
@@ -21,16 +22,28 @@ pub struct CreateSnapshotOptions {
pub snapshot_module_load_cb: Option<ExtModuleLoaderCb>,
}
-pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) {
+pub struct CreateSnapshotOutput {
+ /// Any files marked as LoadedFromFsDuringSnapshot are collected here and should be
+ /// printed as 'cargo:rerun-if-changed' lines from your build script.
+ pub files_loaded_during_snapshot: Vec<PathBuf>,
+}
+
+#[must_use = "The files listed by create_snapshot should be printed as 'cargo:rerun-if-changed' lines"]
+pub fn create_snapshot(
+ create_snapshot_options: CreateSnapshotOptions,
+) -> CreateSnapshotOutput {
let mut mark = Instant::now();
- let js_runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- startup_snapshot: create_snapshot_options.startup_snapshot,
- extensions: create_snapshot_options.extensions,
- snapshot_module_load_cb: create_snapshot_options.snapshot_module_load_cb,
- ..Default::default()
- });
+ let js_runtime = JsRuntimeForSnapshot::new(
+ RuntimeOptions {
+ startup_snapshot: create_snapshot_options.startup_snapshot,
+ extensions: create_snapshot_options.extensions,
+ ..Default::default()
+ },
+ RuntimeSnapshotOptions {
+ snapshot_module_load_cb: create_snapshot_options.snapshot_module_load_cb,
+ },
+ );
println!(
"JsRuntime for snapshot prepared, took {:#?} ({})",
Instant::now().saturating_duration_since(mark),
@@ -38,6 +51,22 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) {
);
mark = Instant::now();
+ let mut files_loaded_during_snapshot = vec![];
+ for source in js_runtime
+ .extensions()
+ .iter()
+ .flat_map(|e| vec![e.get_esm_sources(), e.get_js_sources()])
+ .flatten()
+ .flatten()
+ {
+ use crate::ExtensionFileSourceCode;
+ if let ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) =
+ &source.code
+ {
+ files_loaded_during_snapshot.push(path.clone());
+ }
+ }
+
let snapshot = js_runtime.snapshot();
let snapshot_slice: &[u8] = &snapshot;
println!(
@@ -83,6 +112,9 @@ pub fn create_snapshot(create_snapshot_options: CreateSnapshotOptions) {
Instant::now().saturating_duration_since(mark),
create_snapshot_options.snapshot_path.display(),
);
+ CreateSnapshotOutput {
+ files_loaded_during_snapshot,
+ }
}
pub type FilterFn = Box<dyn Fn(&PathBuf) -> bool>;
@@ -121,29 +153,36 @@ fn data_error_to_panic(err: v8::DataError) -> ! {
}
}
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum SnapshotOptions {
- Load,
- CreateFromExisting,
+ Load(Snapshot),
+ CreateFromExisting(Snapshot),
Create,
None,
}
impl SnapshotOptions {
+ pub fn new_from(snapshot: Option<Snapshot>, will_snapshot: bool) -> Self {
+ match (snapshot, will_snapshot) {
+ (Some(snapshot), true) => Self::CreateFromExisting(snapshot),
+ (None, true) => Self::Create,
+ (Some(snapshot), false) => Self::Load(snapshot),
+ (None, false) => Self::None,
+ }
+ }
+
pub fn loaded(&self) -> bool {
- matches!(self, Self::Load | Self::CreateFromExisting)
+ matches!(self, Self::Load(_) | Self::CreateFromExisting(_))
}
pub fn will_snapshot(&self) -> bool {
- matches!(self, Self::Create | Self::CreateFromExisting)
+ matches!(self, Self::Create | Self::CreateFromExisting(_))
}
- pub fn from_bools(snapshot_loaded: bool, will_snapshot: bool) -> Self {
- match (snapshot_loaded, will_snapshot) {
- (true, true) => Self::CreateFromExisting,
- (false, true) => Self::Create,
- (true, false) => Self::Load,
- (false, false) => Self::None,
+ pub fn snapshot(self) -> Option<Snapshot> {
+ match self {
+ Self::CreateFromExisting(snapshot) => Some(snapshot),
+ Self::Load(snapshot) => Some(snapshot),
+ _ => None,
}
}
}
@@ -218,9 +257,9 @@ pub(crate) fn set_snapshotted_data(
/// Returns an isolate set up for snapshotting.
pub(crate) fn create_snapshot_creator(
external_refs: &'static v8::ExternalReferences,
- maybe_startup_snapshot: Option<Snapshot>,
+ maybe_startup_snapshot: SnapshotOptions,
) -> v8::OwnedIsolate {
- if let Some(snapshot) = maybe_startup_snapshot {
+ if let Some(snapshot) = maybe_startup_snapshot.snapshot() {
match snapshot {
Snapshot::Static(data) => {
v8::Isolate::snapshot_creator_from_existing_snapshot(
diff --git a/runtime/build.rs b/runtime/build.rs
index 334c3b11a..f656682a1 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -337,7 +337,7 @@ mod startup_snapshot {
runtime_main::init_ops_and_esm(),
];
- create_snapshot(CreateSnapshotOptions {
+ let output = create_snapshot(CreateSnapshotOptions {
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
snapshot_path,
startup_snapshot: None,
@@ -345,6 +345,9 @@ mod startup_snapshot {
compression_cb: None,
snapshot_module_load_cb: Some(Box::new(transpile_ts_for_snapshotting)),
});
+ for path in output.files_loaded_during_snapshot {
+ println!("cargo:rerun-if-changed={}", path.display());
+ }
}
}