summaryrefslogtreecommitdiff
path: root/core/runtime.rs
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2021-05-19 20:53:43 +0200
committerGitHub <noreply@github.com>2021-05-19 20:53:43 +0200
commit0768f8d369bf446ab7003e93819bebaf410ab6d4 (patch)
treea895bb37ed2e26e738d1b1bb6b2f3f0e8999c8e4 /core/runtime.rs
parenteb56186e4499c7584d2cfffdca4fde7ad3da16ee (diff)
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).
Diffstat (limited to 'core/runtime.rs')
-rw-r--r--core/runtime.rs691
1 files changed, 114 insertions, 577 deletions
diff --git a/core/runtime.rs b/core/runtime.rs
index 96281ef9f..0dbca0ae3 100644
--- a/core/runtime.rs
+++ b/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};
@@ -1929,312 +1772,6 @@ pub mod tests {
}
#[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)]
struct ModsLoader;