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:
parent
eb56186e44
commit
0768f8d369
4 changed files with 696 additions and 633 deletions
|
@ -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!(
|
||||
|
|
|
@ -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;
|
||||
|
|
582
core/modules.rs
582
core/modules.rs
|
@ -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();
|
||||
|
|
691
core/runtime.rs
691
core/runtime.rs
|
@ -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)]
|
||||
|
|
Loading…
Add table
Reference in a new issue