0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

refactor(core): move ModuleMap to separate RefCell (#10656)

This commit moves bulk of the logic related to module loading
from "JsRuntime" to "ModuleMap".

Next steps are to rewrite the actual loading logic (represented by
"RecursiveModuleLoad") to be a part of "ModuleMap" as well --
that way we will be able to track multiple module loads from within
the map which should help me solve the problem of concurrent
loads (since all info about currently loading/loaded modules will
be contained in the ModuleMap, so we'll be able to know if actually
all required modules have been loaded).
This commit is contained in:
Bartek Iwańczuk 2021-05-19 20:53:43 +02:00 committed by GitHub
parent eb56186e44
commit 0768f8d369
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 696 additions and 633 deletions

View file

@ -8,6 +8,7 @@ use crate::OpPayload;
use crate::OpTable;
use crate::PromiseId;
use crate::ZeroCopyBuf;
use log::debug;
use rusty_v8 as v8;
use serde::Serialize;
use serde_v8::to_v8;
@ -179,8 +180,18 @@ pub extern "C" fn host_import_module_dynamically_callback(
let resolver_handle = v8::Global::new(scope, resolver);
{
let state_rc = JsRuntime::state(scope);
let mut state = state_rc.borrow_mut();
state.dyn_import_cb(resolver_handle, &specifier_str, &referrer_name_str);
let module_map_rc = JsRuntime::module_map(scope);
debug!(
"dyn_import specifier {} referrer {} ",
specifier_str, referrer_name_str
);
module_map_rc.borrow_mut().load_dynamic_import(
&specifier_str,
&referrer_name_str,
resolver_handle,
);
state_rc.borrow_mut().notify_new_dynamic_import();
}
// Map errors from module resolution (not JS errors from module execution) to
@ -218,12 +229,11 @@ pub extern "C" fn host_initialize_import_meta_object_callback(
meta: v8::Local<v8::Object>,
) {
let scope = &mut unsafe { v8::CallbackScope::new(context) };
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let module_map_rc = JsRuntime::module_map(scope);
let module_map = module_map_rc.borrow();
let module_global = v8::Global::new(scope, module);
let info = state
.module_map
let info = module_map
.get_info(&module_global)
.expect("Module not found");
@ -573,7 +583,11 @@ fn queue_microtask(
};
}
// Called by V8 during `Isolate::mod_instantiate`.
/// Called by V8 during `JsRuntime::instantiate_module`.
///
/// This function borrows `ModuleMap` from the isolate slot,
/// so it is crucial to ensure there are no existing borrows
/// of `ModuleMap` when `JsRuntime::instantiate_module` is called.
pub fn module_resolve_callback<'s>(
context: v8::Local<'s, v8::Context>,
specifier: v8::Local<'s, v8::String>,
@ -582,32 +596,22 @@ pub fn module_resolve_callback<'s>(
) -> Option<v8::Local<'s, v8::Module>> {
let scope = &mut unsafe { v8::CallbackScope::new(context) };
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let module_map_rc = JsRuntime::module_map(scope);
let module_map = module_map_rc.borrow();
let referrer_global = v8::Global::new(scope, referrer);
let referrer_info = state
.module_map
let referrer_info = module_map
.get_info(&referrer_global)
.expect("ModuleInfo not found");
let referrer_name = referrer_info.name.to_string();
let specifier_str = specifier.to_rust_string_lossy(scope);
let resolved_specifier = state
.loader
.resolve(
state.op_state.clone(),
&specifier_str,
&referrer_name,
false,
)
.expect("Module should have been already resolved");
if let Some(id) = state.module_map.get_id(resolved_specifier.as_str()) {
if let Some(handle) = state.module_map.get_handle(id) {
return Some(v8::Local::new(scope, handle));
}
let maybe_module =
module_map.resolve_callback(scope, &specifier_str, &referrer_name);
if let Some(module) = maybe_module {
return Some(module);
}
let msg = format!(

View file

@ -51,6 +51,8 @@ pub use crate::modules::ModuleLoader;
pub use crate::modules::ModuleSource;
pub use crate::modules::ModuleSourceFuture;
pub use crate::modules::NoopModuleLoader;
// TODO(bartlomieju): this struct should be implementation
// detail nad not be public
pub use crate::modules::RecursiveModuleLoad;
pub use crate::normalize_path::normalize_path;
pub use crate::ops::serialize_op_result;

View file

@ -2,17 +2,22 @@
use rusty_v8 as v8;
use crate::bindings;
use crate::error::generic_error;
use crate::error::AnyError;
use crate::module_specifier::ModuleSpecifier;
use crate::runtime::exception_to_err_result;
use crate::OpState;
use futures::future::FutureExt;
use futures::stream::FuturesUnordered;
use futures::stream::Stream;
use futures::stream::StreamFuture;
use futures::stream::TryStreamExt;
use log::debug;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
@ -336,6 +341,28 @@ impl RecursiveModuleLoad {
Ok(())
}
pub fn is_currently_loading_main_module(&self) -> bool {
!self.is_dynamic_import() && self.state == LoadState::LoadingRoot
}
pub fn module_registered(&mut self, module_id: ModuleId) {
// If we just finished loading the root module, store the root module id.
if self.state == LoadState::LoadingRoot {
self.root_module_id = Some(module_id);
self.state = LoadState::LoadingImports;
}
if self.pending.is_empty() {
self.state = LoadState::Done;
}
}
/// Return root `ModuleId`; this function panics
/// if load is not finished yet.
pub fn expect_finished(&self) -> ModuleId {
self.root_module_id.expect("Root module id empty")
}
pub fn add_import(
&mut self,
specifier: ModuleSpecifier,
@ -383,6 +410,7 @@ impl Stream for RecursiveModuleLoad {
pub struct ModuleInfo {
pub id: ModuleId,
// Used in "bindings.rs" for "import.meta.main" property value.
pub main: bool,
pub name: String,
pub import_specifiers: Vec<ModuleSpecifier>,
@ -399,23 +427,41 @@ enum SymbolicModule {
}
/// A collection of JS modules.
#[derive(Default)]
pub struct ModuleMap {
// Handling of specifiers and v8 objects
ids_by_handle: HashMap<v8::Global<v8::Module>, ModuleId>,
handles_by_id: HashMap<ModuleId, v8::Global<v8::Module>>,
info: HashMap<ModuleId, ModuleInfo>,
by_name: HashMap<String, SymbolicModule>,
next_module_id: ModuleId,
// Handling of futures for loading module sources
pub loader: Rc<dyn ModuleLoader>,
op_state: Rc<RefCell<OpState>>,
pub(crate) dynamic_import_map:
HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>,
pub(crate) preparing_dynamic_imports:
FuturesUnordered<Pin<Box<PrepareLoadFuture>>>,
pub(crate) pending_dynamic_imports:
FuturesUnordered<StreamFuture<RecursiveModuleLoad>>,
}
impl ModuleMap {
pub fn new() -> ModuleMap {
pub fn new(
loader: Rc<dyn ModuleLoader>,
op_state: Rc<RefCell<OpState>>,
) -> ModuleMap {
Self {
handles_by_id: HashMap::new(),
ids_by_handle: HashMap::new(),
handles_by_id: HashMap::new(),
info: HashMap::new(),
by_name: HashMap::new(),
next_module_id: 1,
loader,
op_state,
dynamic_import_map: HashMap::new(),
preparing_dynamic_imports: FuturesUnordered::new(),
pending_dynamic_imports: FuturesUnordered::new(),
}
}
@ -434,22 +480,52 @@ impl ModuleMap {
}
}
pub fn get_children(&self, id: ModuleId) -> Option<&Vec<ModuleSpecifier>> {
self.info.get(&id).map(|i| &i.import_specifiers)
}
pub fn is_registered(&self, specifier: &ModuleSpecifier) -> bool {
self.get_id(&specifier.to_string()).is_some()
}
pub fn register(
// Create and compile an ES module.
pub(crate) fn new_module(
&mut self,
name: &str,
scope: &mut v8::HandleScope,
main: bool,
handle: v8::Global<v8::Module>,
import_specifiers: Vec<ModuleSpecifier>,
) -> ModuleId {
let name = String::from(name);
name: &str,
source: &str,
) -> Result<ModuleId, AnyError> {
let name_str = v8::String::new(scope, name).unwrap();
let source_str = v8::String::new(scope, source).unwrap();
let origin = bindings::module_origin(scope, name_str);
let source = v8::script_compiler::Source::new(source_str, Some(&origin));
let tc_scope = &mut v8::TryCatch::new(scope);
let maybe_module = v8::script_compiler::compile_module(tc_scope, source);
if tc_scope.has_caught() {
assert!(maybe_module.is_none());
let e = tc_scope.exception().unwrap();
return exception_to_err_result(tc_scope, e, false);
}
let module = maybe_module.unwrap();
let mut import_specifiers: Vec<ModuleSpecifier> = vec![];
let module_requests = module.get_module_requests();
for i in 0..module_requests.length() {
let module_request = v8::Local::<v8::ModuleRequest>::try_from(
module_requests.get(tc_scope, i).unwrap(),
)
.unwrap();
let import_specifier = module_request
.get_specifier()
.to_rust_string_lossy(tc_scope);
let module_specifier = self.loader.resolve(
self.op_state.clone(),
&import_specifier,
name,
false,
)?;
import_specifiers.push(module_specifier);
}
let handle = v8::Global::<v8::Module>::new(tc_scope, module);
let id = self.next_module_id;
self.next_module_id += 1;
self
@ -462,11 +538,83 @@ impl ModuleMap {
ModuleInfo {
id,
main,
name,
name: name.to_string(),
import_specifiers,
},
);
id
Ok(id)
}
pub fn register_during_load(
&mut self,
scope: &mut v8::HandleScope,
module_source: ModuleSource,
load: &mut RecursiveModuleLoad,
) -> Result<(), AnyError> {
let referrer_specifier =
crate::resolve_url(&module_source.module_url_found).unwrap();
// #A There are 3 cases to handle at this moment:
// 1. Source code resolved result have the same module name as requested
// and is not yet registered
// -> register
// 2. Source code resolved result have a different name as requested:
// 2a. The module with resolved module name has been registered
// -> alias
// 2b. The module with resolved module name has not yet been registered
// -> register & alias
// If necessary, register an alias.
if module_source.module_url_specified != module_source.module_url_found {
self.alias(
&module_source.module_url_specified,
&module_source.module_url_found,
);
}
let maybe_mod_id = self.get_id(&module_source.module_url_found);
let module_id = match maybe_mod_id {
Some(id) => {
// Module has already been registered.
debug!(
"Already-registered module fetched again: {}",
module_source.module_url_found
);
id
}
// Module not registered yet, do it now.
None => self.new_module(
scope,
load.is_currently_loading_main_module(),
&module_source.module_url_found,
&module_source.code,
)?,
};
// Now we must iterate over all imports of the module and load them.
let imports = self.get_children(module_id).unwrap().clone();
for module_specifier in imports {
let is_registered = self.is_registered(&module_specifier);
if !is_registered {
load
.add_import(module_specifier.to_owned(), referrer_specifier.clone());
}
}
load.module_registered(module_id);
Ok(())
}
pub fn get_children(&self, id: ModuleId) -> Option<&Vec<ModuleSpecifier>> {
self.info.get(&id).map(|i| &i.import_specifiers)
}
pub fn is_registered(&self, specifier: &ModuleSpecifier) -> bool {
self.get_id(specifier.as_str()).is_some()
}
pub fn alias(&mut self, name: &str, target: &str) {
@ -499,17 +647,79 @@ impl ModuleMap {
pub fn get_info_by_id(&self, id: &ModuleId) -> Option<&ModuleInfo> {
self.info.get(id)
}
pub fn load_main(
&self,
specifier: &str,
code: Option<String>,
) -> RecursiveModuleLoad {
RecursiveModuleLoad::main(
self.op_state.clone(),
specifier,
code,
self.loader.clone(),
)
}
// Initiate loading of a module graph imported using `import()`.
pub fn load_dynamic_import(
&mut self,
specifier: &str,
referrer: &str,
resolver_handle: v8::Global<v8::PromiseResolver>,
) {
let load = RecursiveModuleLoad::dynamic_import(
self.op_state.clone(),
specifier,
referrer,
self.loader.clone(),
);
self.dynamic_import_map.insert(load.id, resolver_handle);
let fut = load.prepare().boxed_local();
self.preparing_dynamic_imports.push(fut);
}
pub fn has_pending_dynamic_imports(&self) -> bool {
!(self.preparing_dynamic_imports.is_empty()
&& self.pending_dynamic_imports.is_empty())
}
/// Called by `module_resolve_callback` during module instantiation.
pub fn resolve_callback<'s>(
&self,
scope: &mut v8::HandleScope<'s>,
specifier: &str,
referrer: &str,
) -> Option<v8::Local<'s, v8::Module>> {
let resolved_specifier = self
.loader
.resolve(self.op_state.clone(), specifier, referrer, false)
.expect("Module should have been already resolved");
if let Some(id) = self.get_id(resolved_specifier.as_str()) {
if let Some(handle) = self.get_handle(id) {
return Some(v8::Local::new(scope, handle));
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serialize_op_result;
use crate::JsRuntime;
use crate::Op;
use crate::OpPayload;
use crate::RuntimeOptions;
use futures::future::FutureExt;
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
@ -704,9 +914,9 @@ mod tests {
]
);
let state_rc = JsRuntime::state(runtime.v8_isolate());
let state = state_rc.borrow();
let modules = &state.module_map;
let module_map_rc = JsRuntime::module_map(runtime.v8_isolate());
let modules = module_map_rc.borrow();
assert_eq!(modules.get_id("file:///a.js"), Some(a_id));
let b_id = modules.get_id("file:///b.js").unwrap();
let c_id = modules.get_id("file:///c.js").unwrap();
@ -745,6 +955,319 @@ mod tests {
Deno.core.print("circular3");
"#;
#[test]
fn test_mods() {
#[derive(Default)]
struct ModsLoader {
pub count: Arc<AtomicUsize>,
}
impl ModuleLoader for ModsLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
self.count.fetch_add(1, Ordering::Relaxed);
assert_eq!(specifier, "./b.js");
assert_eq!(referrer, "file:///a.js");
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}
fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
_module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
unreachable!()
}
}
let loader = Rc::new(ModsLoader::default());
let resolve_count = loader.count.clone();
let dispatch_count = Arc::new(AtomicUsize::new(0));
let dispatch_count_ = dispatch_count.clone();
let dispatcher = move |state, payload: OpPayload| -> Op {
dispatch_count_.fetch_add(1, Ordering::Relaxed);
let (control, _): (u8, ()) = payload.deserialize().unwrap();
assert_eq!(control, 42);
let resp = (0, serialize_op_result(Ok(43), state));
Op::Async(Box::pin(futures::future::ready(resp)))
};
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
runtime.register_op("op_test", dispatcher);
runtime.sync_ops_cache();
runtime
.execute(
"setup.js",
r#"
function assert(cond) {
if (!cond) {
throw Error("assert");
}
}
"#,
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
let module_map_rc = JsRuntime::module_map(runtime.v8_isolate());
let (mod_a, mod_b) = {
let scope = &mut runtime.handle_scope();
let mut module_map = module_map_rc.borrow_mut();
let specifier_a = "file:///a.js".to_string();
let mod_a = module_map
.new_module(
scope,
true,
&specifier_a,
r#"
import { b } from './b.js'
if (b() != 'b') throw Error();
let control = 42;
Deno.core.opAsync("op_test", control);
"#,
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
let imports = module_map.get_children(mod_a);
assert_eq!(
imports,
Some(&vec![crate::resolve_url("file:///b.js").unwrap()])
);
let mod_b = module_map
.new_module(
scope,
false,
"file:///b.js",
"export function b() { return 'b' }",
)
.unwrap();
let imports = module_map.get_children(mod_b).unwrap();
assert_eq!(imports.len(), 0);
(mod_a, mod_b)
};
runtime.instantiate_module(mod_b).unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
assert_eq!(resolve_count.load(Ordering::SeqCst), 1);
runtime.instantiate_module(mod_a).unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
runtime.mod_evaluate(mod_a);
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
}
#[test]
fn dyn_import_err() {
#[derive(Clone, Default)]
struct DynImportErrLoader {
pub count: Arc<AtomicUsize>,
}
impl ModuleLoader for DynImportErrLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
self.count.fetch_add(1, Ordering::Relaxed);
assert_eq!(specifier, "/foo.js");
assert_eq!(referrer, "file:///dyn_import2.js");
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}
fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
_module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
async { Err(io::Error::from(io::ErrorKind::NotFound).into()) }.boxed()
}
}
// Test an erroneous dynamic import where the specified module isn't found.
run_in_task(|cx| {
let loader = Rc::new(DynImportErrLoader::default());
let count = loader.count.clone();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
runtime
.execute(
"file:///dyn_import2.js",
r#"
(async () => {
await import("/foo.js");
})();
"#,
)
.unwrap();
assert_eq!(count.load(Ordering::Relaxed), 0);
// We should get an error here.
let result = runtime.poll_event_loop(cx);
if let Poll::Ready(Ok(_)) = result {
unreachable!();
}
assert_eq!(count.load(Ordering::Relaxed), 2);
})
}
#[derive(Clone, Default)]
struct DynImportOkLoader {
pub prepare_load_count: Arc<AtomicUsize>,
pub resolve_count: Arc<AtomicUsize>,
pub load_count: Arc<AtomicUsize>,
}
impl ModuleLoader for DynImportOkLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
let c = self.resolve_count.fetch_add(1, Ordering::Relaxed);
assert!(c < 4);
assert_eq!(specifier, "./b.js");
assert_eq!(referrer, "file:///dyn_import3.js");
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}
fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
self.load_count.fetch_add(1, Ordering::Relaxed);
let info = ModuleSource {
module_url_specified: specifier.to_string(),
module_url_found: specifier.to_string(),
code: "export function b() { return 'b' }".to_owned(),
};
async move { Ok(info) }.boxed()
}
fn prepare_load(
&self,
_op_state: Rc<RefCell<OpState>>,
_load_id: ModuleLoadId,
_module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<String>,
_is_dyn_import: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
self.prepare_load_count.fetch_add(1, Ordering::Relaxed);
async { Ok(()) }.boxed_local()
}
}
#[test]
fn dyn_import_ok() {
run_in_task(|cx| {
let loader = Rc::new(DynImportOkLoader::default());
let prepare_load_count = loader.prepare_load_count.clone();
let resolve_count = loader.resolve_count.clone();
let load_count = loader.load_count.clone();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
// Dynamically import mod_b
runtime
.execute(
"file:///dyn_import3.js",
r#"
(async () => {
let mod = await import("./b.js");
if (mod.b() !== 'b') {
throw Error("bad1");
}
// And again!
mod = await import("./b.js");
if (mod.b() !== 'b') {
throw Error("bad2");
}
})();
"#,
)
.unwrap();
// First poll runs `prepare_load` hook.
assert!(matches!(runtime.poll_event_loop(cx), Poll::Pending));
assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1);
// Second poll actually loads modules into the isolate.
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(resolve_count.load(Ordering::Relaxed), 4);
assert_eq!(load_count.load(Ordering::Relaxed), 2);
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(resolve_count.load(Ordering::Relaxed), 4);
assert_eq!(load_count.load(Ordering::Relaxed), 2);
})
}
#[test]
fn dyn_import_borrow_mut_error() {
// https://github.com/denoland/deno/issues/6054
run_in_task(|cx| {
let loader = Rc::new(DynImportOkLoader::default());
let prepare_load_count = loader.prepare_load_count.clone();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
runtime.sync_ops_cache();
runtime
.execute(
"file:///dyn_import3.js",
r#"
(async () => {
let mod = await import("./b.js");
if (mod.b() !== 'b') {
throw Error("bad");
}
})();
"#,
)
.unwrap();
// First poll runs `prepare_load` hook.
let _ = runtime.poll_event_loop(cx);
assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1);
// Second poll triggers error
let _ = runtime.poll_event_loop(cx);
})
}
#[test]
fn test_circular_load() {
let loader = MockLoader::new();
@ -772,9 +1295,8 @@ mod tests {
]
);
let state_rc = JsRuntime::state(runtime.v8_isolate());
let state = state_rc.borrow();
let modules = &state.module_map;
let module_map_rc = JsRuntime::module_map(runtime.v8_isolate());
let modules = module_map_rc.borrow();
assert_eq!(modules.get_id("file:///circular1.js"), Some(circular1_id));
let circular2_id = modules.get_id("file:///circular2.js").unwrap();
@ -845,9 +1367,8 @@ mod tests {
]
);
let state_rc = JsRuntime::state(runtime.v8_isolate());
let state = state_rc.borrow();
let modules = &state.module_map;
let module_map_rc = JsRuntime::module_map(runtime.v8_isolate());
let modules = module_map_rc.borrow();
assert_eq!(modules.get_id("file:///redirect1.js"), Some(redirect1_id));
@ -992,9 +1513,8 @@ mod tests {
vec!["file:///b.js", "file:///c.js", "file:///d.js"]
);
let state_rc = JsRuntime::state(runtime.v8_isolate());
let state = state_rc.borrow();
let modules = &state.module_map;
let module_map_rc = JsRuntime::module_map(runtime.v8_isolate());
let modules = module_map_rc.borrow();
assert_eq!(modules.get_id("file:///main_with_code.js"), Some(main_id));
let b_id = modules.get_id("file:///b.js").unwrap();

View file

@ -8,17 +8,12 @@ use crate::error::generic_error;
use crate::error::AnyError;
use crate::error::ErrWithV8Handle;
use crate::error::JsError;
use crate::futures::FutureExt;
use crate::module_specifier::ModuleSpecifier;
use crate::modules::LoadState;
use crate::modules::ModuleId;
use crate::modules::ModuleLoadId;
use crate::modules::ModuleLoader;
use crate::modules::ModuleMap;
use crate::modules::ModuleSource;
use crate::modules::NoopModuleLoader;
use crate::modules::PrepareLoadFuture;
use crate::modules::RecursiveModuleLoad;
use crate::ops::*;
use crate::Extension;
use crate::OpMiddlewareFn;
@ -30,10 +25,8 @@ use futures::channel::mpsc;
use futures::future::poll_fn;
use futures::stream::FuturesUnordered;
use futures::stream::StreamExt;
use futures::stream::StreamFuture;
use futures::task::AtomicWaker;
use futures::Future;
use log::debug;
use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
@ -115,12 +108,6 @@ pub(crate) struct JsRuntimeState {
pub(crate) pending_unref_ops: FuturesUnordered<PendingOpFuture>,
pub(crate) have_unpolled_ops: bool,
pub(crate) op_state: Rc<RefCell<OpState>>,
pub loader: Rc<dyn ModuleLoader>,
pub module_map: ModuleMap,
pub(crate) dyn_import_map:
HashMap<ModuleLoadId, v8::Global<v8::PromiseResolver>>,
preparing_dyn_imports: FuturesUnordered<Pin<Box<PrepareLoadFuture>>>,
pending_dyn_imports: FuturesUnordered<StreamFuture<RecursiveModuleLoad>>,
waker: AtomicWaker,
}
@ -284,6 +271,8 @@ impl JsRuntime {
op_state.get_error_class_fn = get_error_class_fn;
}
let op_state = Rc::new(RefCell::new(op_state));
isolate.set_slot(Rc::new(RefCell::new(JsRuntimeState {
global_context: Some(global_context),
pending_promise_exceptions: HashMap::new(),
@ -294,16 +283,14 @@ impl JsRuntime {
js_error_create_fn,
pending_ops: FuturesUnordered::new(),
pending_unref_ops: FuturesUnordered::new(),
op_state: Rc::new(RefCell::new(op_state)),
op_state: op_state.clone(),
have_unpolled_ops: false,
module_map: ModuleMap::new(),
loader,
dyn_import_map: HashMap::new(),
preparing_dyn_imports: FuturesUnordered::new(),
pending_dyn_imports: FuturesUnordered::new(),
waker: AtomicWaker::new(),
})));
let module_map = ModuleMap::new(loader, op_state);
isolate.set_slot(Rc::new(RefCell::new(module_map)));
// Add builtins extension
options
.extensions
@ -363,6 +350,11 @@ impl JsRuntime {
s.clone()
}
pub(crate) fn module_map(isolate: &v8::Isolate) -> Rc<RefCell<ModuleMap>> {
let module_map = isolate.get_slot::<Rc<RefCell<ModuleMap>>>().unwrap();
module_map.clone()
}
/// Initializes JS of provided Extensions
fn init_extension_js(&mut self) -> Result<(), AnyError> {
// Take extensions to avoid double-borrow
@ -495,8 +487,14 @@ impl JsRuntime {
// TODO(piscisaureus): The rusty_v8 type system should enforce this.
state.borrow_mut().global_context.take();
// Drop v8::Global handles before snapshotting
std::mem::take(&mut state.borrow_mut().module_map);
// Overwrite existing ModuleMap to drop v8::Global handles
self
.v8_isolate()
.set_slot(Rc::new(RefCell::new(ModuleMap::new(
Rc::new(NoopModuleLoader),
state.borrow().op_state.clone(),
))));
// Drop other v8::Global handles before snapshotting
std::mem::take(&mut state.borrow_mut().js_recv_cb);
let snapshot_creator = self.snapshot_creator.as_mut().unwrap();
@ -580,6 +578,7 @@ impl JsRuntime {
cx: &mut Context,
) -> Poll<Result<(), AnyError>> {
let state_rc = Self::state(self.v8_isolate());
let module_map_rc = Self::module_map(self.v8_isolate());
{
let state = state_rc.borrow();
state.waker.register(cx.waker());
@ -610,12 +609,11 @@ impl JsRuntime {
self.evaluate_pending_module();
let state = state_rc.borrow();
let module_map = module_map_rc.borrow();
let has_pending_ops = !state.pending_ops.is_empty();
let has_pending_dyn_imports = !{
state.preparing_dyn_imports.is_empty()
&& state.pending_dyn_imports.is_empty()
};
let has_pending_dyn_imports = module_map.has_pending_dynamic_imports();
let has_pending_dyn_module_evaluation =
!state.pending_dyn_mod_evaluate.is_empty();
let has_pending_module_evaluation = state.pending_mod_evaluate.is_some();
@ -653,8 +651,7 @@ impl JsRuntime {
let mut msg = "Dynamically imported module evaluation is still pending but there are no pending ops. This situation is often caused by unresolved promise.
Pending dynamic modules:\n".to_string();
for pending_evaluate in &state.pending_dyn_mod_evaluate {
let module_info = state
.module_map
let module_info = module_map
.get_info_by_id(&pending_evaluate.module_id)
.unwrap();
msg.push_str(&format!("- {}", module_info.name.as_str()));
@ -680,25 +677,11 @@ where
}
impl JsRuntimeState {
// Called by V8 during `Isolate::mod_instantiate`.
pub fn dyn_import_cb(
&mut self,
resolver_handle: v8::Global<v8::PromiseResolver>,
specifier: &str,
referrer: &str,
) {
debug!("dyn_import specifier {} referrer {} ", specifier, referrer);
let load = RecursiveModuleLoad::dynamic_import(
self.op_state.clone(),
specifier,
referrer,
self.loader.clone(),
);
self.dyn_import_map.insert(load.id, resolver_handle);
/// Called by `bindings::host_import_module_dynamically_callback`
/// after initiating new dynamic import load.
pub fn notify_new_dynamic_import(&mut self) {
// Notify event loop to poll again soon.
self.waker.wake();
let fut = load.prepare().boxed_local();
self.preparing_dyn_imports.push(fut);
}
}
@ -744,116 +727,53 @@ pub(crate) fn exception_to_err_result<'s, T>(
// Related to module loading
impl JsRuntime {
/// Low-level module creation.
///
/// Called during module loading or dynamic import loading.
fn mod_new(
pub(crate) fn instantiate_module(
&mut self,
main: bool,
name: &str,
source: &str,
) -> Result<ModuleId, AnyError> {
let state_rc = Self::state(self.v8_isolate());
let scope = &mut self.handle_scope();
let name_str = v8::String::new(scope, name).unwrap();
let source_str = v8::String::new(scope, source).unwrap();
let origin = bindings::module_origin(scope, name_str);
let source = v8::script_compiler::Source::new(source_str, Some(&origin));
let tc_scope = &mut v8::TryCatch::new(scope);
let maybe_module = v8::script_compiler::compile_module(tc_scope, source);
if tc_scope.has_caught() {
assert!(maybe_module.is_none());
let e = tc_scope.exception().unwrap();
return exception_to_err_result(tc_scope, e, false);
}
let module = maybe_module.unwrap();
let mut import_specifiers: Vec<ModuleSpecifier> = vec![];
let module_requests = module.get_module_requests();
for i in 0..module_requests.length() {
let module_request = v8::Local::<v8::ModuleRequest>::try_from(
module_requests.get(tc_scope, i).unwrap(),
)
.unwrap();
let import_specifier = module_request
.get_specifier()
.to_rust_string_lossy(tc_scope);
let state = state_rc.borrow();
let module_specifier = state.loader.resolve(
state.op_state.clone(),
&import_specifier,
name,
false,
)?;
import_specifiers.push(module_specifier);
}
let id = state_rc.borrow_mut().module_map.register(
name,
main,
v8::Global::<v8::Module>::new(tc_scope, module),
import_specifiers,
);
Ok(id)
}
/// Instantiates a ES module
///
/// `AnyError` can be downcast to a type that exposes additional information
/// about the V8 exception. By default this type is `JsError`, however it may
/// be a different type if `RuntimeOptions::js_error_create_fn` has been set.
fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), AnyError> {
let state_rc = Self::state(self.v8_isolate());
id: ModuleId,
) -> Result<(), AnyError> {
let module_map_rc = Self::module_map(self.v8_isolate());
let scope = &mut self.handle_scope();
let tc_scope = &mut v8::TryCatch::new(scope);
let module = state_rc
let module = module_map_rc
.borrow()
.module_map
.get_handle(id)
.map(|handle| v8::Local::new(tc_scope, handle))
.expect("ModuleInfo not found");
if module.get_status() == v8::ModuleStatus::Errored {
let exception = module.get_exception();
exception_to_err_result(tc_scope, exception, false)
.map_err(|err| attach_handle_to_error(tc_scope, err, exception))
} else {
let instantiate_result =
module.instantiate_module(tc_scope, bindings::module_resolve_callback);
match instantiate_result {
Some(_) => Ok(()),
None => {
let exception = tc_scope.exception().unwrap();
exception_to_err_result(tc_scope, exception, false)
.map_err(|err| attach_handle_to_error(tc_scope, err, exception))
}
}
let err = exception_to_err_result(tc_scope, exception, false)
.map_err(|err| attach_handle_to_error(tc_scope, err, exception));
return err;
}
// IMPORTANT: No borrows to `ModuleMap` can be held at this point because
// `module_resolve_callback` will be calling into `ModuleMap` from within
// the isolate.
let instantiate_result =
module.instantiate_module(tc_scope, bindings::module_resolve_callback);
if instantiate_result.is_none() {
let exception = tc_scope.exception().unwrap();
let err = exception_to_err_result(tc_scope, exception, false)
.map_err(|err| attach_handle_to_error(tc_scope, err, exception));
return err;
}
Ok(())
}
/// Evaluates an already instantiated ES module.
///
/// `AnyError` can be downcast to a type that exposes additional information
/// about the V8 exception. By default this type is `JsError`, however it may
/// be a different type if `RuntimeOptions::js_error_create_fn` has been set.
pub fn dyn_mod_evaluate(
fn dynamic_import_module_evaluate(
&mut self,
load_id: ModuleLoadId,
id: ModuleId,
) -> Result<(), AnyError> {
let state_rc = Self::state(self.v8_isolate());
let module_map_rc = Self::module_map(self.v8_isolate());
let module_handle = state_rc
let module_handle = module_map_rc
.borrow()
.module_map
.get_handle(id)
.expect("ModuleInfo not found");
@ -937,11 +857,11 @@ impl JsRuntime {
id: ModuleId,
) -> mpsc::Receiver<Result<(), AnyError>> {
let state_rc = Self::state(self.v8_isolate());
let module_map_rc = Self::module_map(self.v8_isolate());
let scope = &mut self.handle_scope();
let module = state_rc
let module = module_map_rc
.borrow()
.module_map
.get_handle(id)
.map(|handle| v8::Local::new(scope, handle))
.expect("ModuleInfo not found");
@ -999,15 +919,15 @@ impl JsRuntime {
receiver
}
fn dyn_import_error(&mut self, id: ModuleLoadId, err: AnyError) {
let state_rc = Self::state(self.v8_isolate());
fn dynamic_import_reject(&mut self, id: ModuleLoadId, err: AnyError) {
let module_map_rc = Self::module_map(self.v8_isolate());
let scope = &mut self.handle_scope();
let resolver_handle = state_rc
let resolver_handle = module_map_rc
.borrow_mut()
.dyn_import_map
.dynamic_import_map
.remove(&id)
.expect("Invalid dyn import id");
.expect("Invalid dynamic import id");
let resolver = resolver_handle.get(scope);
let exception = err
@ -1019,25 +939,28 @@ impl JsRuntime {
v8::Exception::type_error(scope, message)
});
// IMPORTANT: No borrows to `ModuleMap` can be held at this point because
// rejecting the promise might initiate another `import()` which will
// in turn call `bindings::host_import_module_dynamically_callback` which
// will reach into `ModuleMap` from within the isolate.
resolver.reject(scope, exception).unwrap();
scope.perform_microtask_checkpoint();
}
fn dyn_import_done(&mut self, id: ModuleLoadId, mod_id: ModuleId) {
let state_rc = Self::state(self.v8_isolate());
fn dynamic_import_resolve(&mut self, id: ModuleLoadId, mod_id: ModuleId) {
let module_map_rc = Self::module_map(self.v8_isolate());
let scope = &mut self.handle_scope();
let resolver_handle = state_rc
let resolver_handle = module_map_rc
.borrow_mut()
.dyn_import_map
.dynamic_import_map
.remove(&id)
.expect("Invalid dyn import id");
.expect("Invalid dynamic import id");
let resolver = resolver_handle.get(scope);
let module = {
let state = state_rc.borrow();
state
.module_map
module_map_rc
.borrow()
.get_handle(mod_id)
.map(|handle| v8::Local::new(scope, handle))
.expect("Dyn import module info not found")
@ -1045,6 +968,10 @@ impl JsRuntime {
// Resolution success
assert_eq!(module.get_status(), v8::ModuleStatus::Evaluated);
// IMPORTANT: No borrows to `ModuleMap` can be held at this point because
// resolving the promise might initiate another `import()` which will
// in turn call `bindings::host_import_module_dynamically_callback` which
// will reach into `ModuleMap` from within the isolate.
let module_namespace = module.get_module_namespace();
resolver.resolve(scope, module_namespace).unwrap();
scope.perform_microtask_checkpoint();
@ -1054,16 +981,16 @@ impl JsRuntime {
&mut self,
cx: &mut Context,
) -> Poll<Result<(), AnyError>> {
let state_rc = Self::state(self.v8_isolate());
let module_map_rc = Self::module_map(self.v8_isolate());
if state_rc.borrow().preparing_dyn_imports.is_empty() {
if module_map_rc.borrow().preparing_dynamic_imports.is_empty() {
return Poll::Ready(Ok(()));
}
loop {
let poll_result = state_rc
let poll_result = module_map_rc
.borrow_mut()
.preparing_dyn_imports
.preparing_dynamic_imports
.poll_next_unpin(cx);
if let Poll::Ready(Some(prepare_poll)) = poll_result {
@ -1072,13 +999,13 @@ impl JsRuntime {
match prepare_result {
Ok(load) => {
state_rc
module_map_rc
.borrow_mut()
.pending_dyn_imports
.pending_dynamic_imports
.push(load.into_future());
}
Err(err) => {
self.dyn_import_error(dyn_import_id, err);
self.dynamic_import_reject(dyn_import_id, err);
}
}
// Continue polling for more prepared dynamic imports.
@ -1094,16 +1021,16 @@ impl JsRuntime {
&mut self,
cx: &mut Context,
) -> Poll<Result<(), AnyError>> {
let state_rc = Self::state(self.v8_isolate());
let module_map_rc = Self::module_map(self.v8_isolate());
if state_rc.borrow().pending_dyn_imports.is_empty() {
if module_map_rc.borrow().pending_dynamic_imports.is_empty() {
return Poll::Ready(Ok(()));
}
loop {
let poll_result = state_rc
let poll_result = module_map_rc
.borrow_mut()
.pending_dyn_imports
.pending_dynamic_imports
.poll_next_unpin(cx);
if let Poll::Ready(Some(load_stream_poll)) = poll_result {
@ -1117,33 +1044,40 @@ impl JsRuntime {
// A module (not necessarily the one dynamically imported) has been
// fetched. Create and register it, and if successful, poll for the
// next recursive-load event related to this dynamic import.
match self.register_during_load(info, &mut load) {
let register_result =
module_map_rc.borrow_mut().register_during_load(
&mut self.handle_scope(),
info,
&mut load,
);
match register_result {
Ok(()) => {
// Keep importing until it's fully drained
state_rc
module_map_rc
.borrow_mut()
.pending_dyn_imports
.pending_dynamic_imports
.push(load.into_future());
}
Err(err) => self.dyn_import_error(dyn_import_id, err),
Err(err) => self.dynamic_import_reject(dyn_import_id, err),
}
}
Err(err) => {
// A non-javascript error occurred; this could be due to a an invalid
// module specifier, or a problem with the source map, or a failure
// to fetch the module source code.
self.dyn_import_error(dyn_import_id, err)
self.dynamic_import_reject(dyn_import_id, err)
}
}
} else {
// The top-level module from a dynamic import has been instantiated.
// Load is done.
let module_id = load.root_module_id.unwrap();
let result = self.mod_instantiate(module_id);
let module_id = load.expect_finished();
let result = self.instantiate_module(module_id);
if let Err(err) = result {
self.dyn_import_error(dyn_import_id, err);
self.dynamic_import_reject(dyn_import_id, err);
}
self.dyn_mod_evaluate(dyn_import_id, module_id)?;
self.dynamic_import_module_evaluate(dyn_import_id, module_id)?;
}
// Continue polling for more ready dynamic imports.
@ -1252,10 +1186,10 @@ impl JsRuntime {
if let Some(result) = maybe_result {
match result {
Ok((dyn_import_id, module_id)) => {
self.dyn_import_done(dyn_import_id, module_id);
self.dynamic_import_resolve(dyn_import_id, module_id);
}
Err((dyn_import_id, err1)) => {
self.dyn_import_error(dyn_import_id, err1);
self.dynamic_import_reject(dyn_import_id, err1);
}
}
} else {
@ -1264,90 +1198,6 @@ impl JsRuntime {
}
}
fn register_during_load(
&mut self,
info: ModuleSource,
load: &mut RecursiveModuleLoad,
) -> Result<(), AnyError> {
let ModuleSource {
code,
module_url_specified,
module_url_found,
} = info;
let is_main =
load.state == LoadState::LoadingRoot && !load.is_dynamic_import();
let referrer_specifier = crate::resolve_url(&module_url_found).unwrap();
let state_rc = Self::state(self.v8_isolate());
// #A There are 3 cases to handle at this moment:
// 1. Source code resolved result have the same module name as requested
// and is not yet registered
// -> register
// 2. Source code resolved result have a different name as requested:
// 2a. The module with resolved module name has been registered
// -> alias
// 2b. The module with resolved module name has not yet been registered
// -> register & alias
// If necessary, register an alias.
if module_url_specified != module_url_found {
let mut state = state_rc.borrow_mut();
state
.module_map
.alias(&module_url_specified, &module_url_found);
}
let maybe_mod_id = {
let state = state_rc.borrow();
state.module_map.get_id(&module_url_found)
};
let module_id = match maybe_mod_id {
Some(id) => {
// Module has already been registered.
debug!(
"Already-registered module fetched again: {}",
module_url_found
);
id
}
// Module not registered yet, do it now.
None => self.mod_new(is_main, &module_url_found, &code)?,
};
// Now we must iterate over all imports of the module and load them.
let imports = {
let state_rc = Self::state(self.v8_isolate());
let state = state_rc.borrow();
state.module_map.get_children(module_id).unwrap().clone()
};
for module_specifier in imports {
let is_registered = {
let state_rc = Self::state(self.v8_isolate());
let state = state_rc.borrow();
state.module_map.is_registered(&module_specifier)
};
if !is_registered {
load
.add_import(module_specifier.to_owned(), referrer_specifier.clone());
}
}
// If we just finished loading the root module, store the root module id.
if load.state == LoadState::LoadingRoot {
load.root_module_id = Some(module_id);
load.state = LoadState::LoadingImports;
}
if load.pending.is_empty() {
load.state = LoadState::Done;
}
Ok(())
}
/// Asynchronously load specified module and all of its dependencies
///
/// User must call `JsRuntime::mod_evaluate` with returned `ModuleId`
@ -1357,29 +1207,24 @@ impl JsRuntime {
specifier: &ModuleSpecifier,
code: Option<String>,
) -> Result<ModuleId, AnyError> {
let loader = {
let state_rc = Self::state(self.v8_isolate());
let state = state_rc.borrow();
state.loader.clone()
};
let module_map_rc = Self::module_map(self.v8_isolate());
let load = module_map_rc.borrow().load_main(specifier.as_str(), code);
let load = RecursiveModuleLoad::main(
self.op_state(),
&specifier.to_string(),
code,
loader,
);
let (_load_id, prepare_result) = load.prepare().await;
let mut load = prepare_result?;
while let Some(info_result) = load.next().await {
let info = info_result?;
self.register_during_load(info, &mut load)?;
let scope = &mut self.handle_scope();
module_map_rc
.borrow_mut()
.register_during_load(scope, info, &mut load)?;
}
let root_id = load.root_module_id.expect("Root module id empty");
self.mod_instantiate(root_id).map(|_| root_id)
let root_id = load.expect_finished();
self.instantiate_module(root_id).map(|_| root_id)
}
fn poll_pending_ops(
@ -1525,8 +1370,6 @@ pub mod tests {
use crate::op_sync;
use crate::ZeroCopyBuf;
use futures::future::lazy;
use futures::FutureExt;
use std::io;
use std::ops::FnOnce;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
@ -1928,312 +1771,6 @@ pub mod tests {
assert!(callback_invoke_count_second.load(Ordering::SeqCst) > 0);
}
#[test]
fn test_mods() {
#[derive(Default)]
struct ModsLoader {
pub count: Arc<AtomicUsize>,
}
impl ModuleLoader for ModsLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
self.count.fetch_add(1, Ordering::Relaxed);
assert_eq!(specifier, "./b.js");
assert_eq!(referrer, "file:///a.js");
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}
fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
_module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
unreachable!()
}
}
let loader = Rc::new(ModsLoader::default());
let resolve_count = loader.count.clone();
let dispatch_count = Arc::new(AtomicUsize::new(0));
let dispatch_count_ = dispatch_count.clone();
let dispatcher = move |state, payload: OpPayload| -> Op {
dispatch_count_.fetch_add(1, Ordering::Relaxed);
let (control, _): (u8, ()) = payload.deserialize().unwrap();
assert_eq!(control, 42);
let resp = (0, serialize_op_result(Ok(43), state));
Op::Async(Box::pin(futures::future::ready(resp)))
};
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
runtime.register_op("op_test", dispatcher);
runtime.sync_ops_cache();
runtime
.execute(
"setup.js",
r#"
function assert(cond) {
if (!cond) {
throw Error("assert");
}
}
"#,
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
let specifier_a = "file:///a.js".to_string();
let mod_a = runtime
.mod_new(
true,
&specifier_a,
r#"
import { b } from './b.js'
if (b() != 'b') throw Error();
let control = 42;
Deno.core.opAsync("op_test", control);
"#,
)
.unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
let state_rc = JsRuntime::state(runtime.v8_isolate());
{
let state = state_rc.borrow();
let imports = state.module_map.get_children(mod_a);
assert_eq!(
imports,
Some(&vec![crate::resolve_url("file:///b.js").unwrap()])
);
}
let mod_b = runtime
.mod_new(false, "file:///b.js", "export function b() { return 'b' }")
.unwrap();
{
let state = state_rc.borrow();
let imports = state.module_map.get_children(mod_b).unwrap();
assert_eq!(imports.len(), 0);
}
runtime.mod_instantiate(mod_b).unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
assert_eq!(resolve_count.load(Ordering::SeqCst), 1);
runtime.mod_instantiate(mod_a).unwrap();
assert_eq!(dispatch_count.load(Ordering::Relaxed), 0);
runtime.mod_evaluate(mod_a);
assert_eq!(dispatch_count.load(Ordering::Relaxed), 1);
}
#[test]
fn dyn_import_err() {
#[derive(Clone, Default)]
struct DynImportErrLoader {
pub count: Arc<AtomicUsize>,
}
impl ModuleLoader for DynImportErrLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
self.count.fetch_add(1, Ordering::Relaxed);
assert_eq!(specifier, "/foo.js");
assert_eq!(referrer, "file:///dyn_import2.js");
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}
fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
_module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
async { Err(io::Error::from(io::ErrorKind::NotFound).into()) }.boxed()
}
}
// Test an erroneous dynamic import where the specified module isn't found.
run_in_task(|cx| {
let loader = Rc::new(DynImportErrLoader::default());
let count = loader.count.clone();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
runtime
.execute(
"file:///dyn_import2.js",
r#"
(async () => {
await import("/foo.js");
})();
"#,
)
.unwrap();
assert_eq!(count.load(Ordering::Relaxed), 0);
// We should get an error here.
let result = runtime.poll_event_loop(cx);
if let Poll::Ready(Ok(_)) = result {
unreachable!();
}
assert_eq!(count.load(Ordering::Relaxed), 2);
})
}
#[derive(Clone, Default)]
struct DynImportOkLoader {
pub prepare_load_count: Arc<AtomicUsize>,
pub resolve_count: Arc<AtomicUsize>,
pub load_count: Arc<AtomicUsize>,
}
impl ModuleLoader for DynImportOkLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
let c = self.resolve_count.fetch_add(1, Ordering::Relaxed);
assert!(c < 4);
assert_eq!(specifier, "./b.js");
assert_eq!(referrer, "file:///dyn_import3.js");
let s = crate::resolve_import(specifier, referrer).unwrap();
Ok(s)
}
fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
self.load_count.fetch_add(1, Ordering::Relaxed);
let info = ModuleSource {
module_url_specified: specifier.to_string(),
module_url_found: specifier.to_string(),
code: "export function b() { return 'b' }".to_owned(),
};
async move { Ok(info) }.boxed()
}
fn prepare_load(
&self,
_op_state: Rc<RefCell<OpState>>,
_load_id: ModuleLoadId,
_module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<String>,
_is_dyn_import: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
self.prepare_load_count.fetch_add(1, Ordering::Relaxed);
async { Ok(()) }.boxed_local()
}
}
#[test]
fn dyn_import_ok() {
run_in_task(|cx| {
let loader = Rc::new(DynImportOkLoader::default());
let prepare_load_count = loader.prepare_load_count.clone();
let resolve_count = loader.resolve_count.clone();
let load_count = loader.load_count.clone();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
// Dynamically import mod_b
runtime
.execute(
"file:///dyn_import3.js",
r#"
(async () => {
let mod = await import("./b.js");
if (mod.b() !== 'b') {
throw Error("bad1");
}
// And again!
mod = await import("./b.js");
if (mod.b() !== 'b') {
throw Error("bad2");
}
})();
"#,
)
.unwrap();
// First poll runs `prepare_load` hook.
assert!(matches!(runtime.poll_event_loop(cx), Poll::Pending));
assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1);
// Second poll actually loads modules into the isolate.
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(resolve_count.load(Ordering::Relaxed), 4);
assert_eq!(load_count.load(Ordering::Relaxed), 2);
assert!(matches!(runtime.poll_event_loop(cx), Poll::Ready(Ok(_))));
assert_eq!(resolve_count.load(Ordering::Relaxed), 4);
assert_eq!(load_count.load(Ordering::Relaxed), 2);
})
}
#[test]
fn dyn_import_borrow_mut_error() {
// https://github.com/denoland/deno/issues/6054
run_in_task(|cx| {
let loader = Rc::new(DynImportOkLoader::default());
let prepare_load_count = loader.prepare_load_count.clone();
let mut runtime = JsRuntime::new(RuntimeOptions {
module_loader: Some(loader),
..Default::default()
});
runtime.sync_ops_cache();
runtime
.execute(
"file:///dyn_import3.js",
r#"
(async () => {
let mod = await import("./b.js");
if (mod.b() !== 'b') {
throw Error("bad");
}
})();
"#,
)
.unwrap();
// First poll runs `prepare_load` hook.
let _ = runtime.poll_event_loop(cx);
assert_eq!(prepare_load_count.load(Ordering::Relaxed), 1);
// Second poll triggers error
let _ = runtime.poll_event_loop(cx);
})
}
#[test]
fn es_snapshot() {
#[derive(Default)]