diff options
| -rw-r--r-- | cli/ops/worker_host.rs | 10 | ||||
| -rw-r--r-- | cli/tests/integration_tests.rs | 22 | ||||
| -rw-r--r-- | cli/tests/tla/a.js | 3 | ||||
| -rw-r--r-- | cli/tests/tla/b.js | 7 | ||||
| -rw-r--r-- | cli/tests/tla/c.js | 3 | ||||
| -rw-r--r-- | cli/tests/tla/d.js | 6 | ||||
| -rw-r--r-- | cli/tests/tla/order.js | 1 | ||||
| -rw-r--r-- | cli/tests/tla/parent.js | 9 | ||||
| -rw-r--r-- | cli/tests/tla2/a.js | 5 | ||||
| -rw-r--r-- | cli/tests/tla2/b.js | 5 | ||||
| -rw-r--r-- | cli/tests/tla3/b.js | 7 | ||||
| -rw-r--r-- | cli/tests/tla3/timeout_loop.js | 23 | ||||
| -rw-r--r-- | cli/tests/top_level_await_circular.js | 8 | ||||
| -rw-r--r-- | cli/tests/top_level_await_circular.out | 7 | ||||
| -rw-r--r-- | cli/tests/top_level_await_loop.js | 18 | ||||
| -rw-r--r-- | cli/tests/top_level_await_loop.out | 5 | ||||
| -rw-r--r-- | cli/tests/top_level_await_order.js | 21 | ||||
| -rw-r--r-- | cli/tests/top_level_await_order.out | 2 | ||||
| -rw-r--r-- | cli/tests/top_level_await_unresolved.js | 1 | ||||
| -rw-r--r-- | cli/tests/top_level_await_unresolved.out | 1 | ||||
| -rw-r--r-- | cli/worker.rs | 2 | ||||
| -rw-r--r-- | core/modules.rs | 8 | ||||
| -rw-r--r-- | core/runtime.rs | 333 |
23 files changed, 473 insertions, 34 deletions
diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs index 8ebf8b9e6..ad915f7f7 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -153,9 +153,15 @@ fn run_worker_thread( rt.block_on(load_future) }; - if let Err(e) = result { - let mut sender = worker.internal_channels.sender.clone(); + let mut sender = worker.internal_channels.sender.clone(); + // If sender is closed it means that worker has already been closed from + // within using "globalThis.close()" + if sender.is_closed() { + return; + } + + if let Err(e) = result { sender .try_send(WorkerEvent::TerminalError(e)) .expect("Failed to post message to host"); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 90dc5a4a8..66a83a655 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -2421,6 +2421,28 @@ itest!(wasm_unreachable { exit_code: 1, }); +itest!(top_level_await_order { + args: "run --allow-read top_level_await_order.js", + output: "top_level_await_order.out", +}); + +itest!(top_level_await_loop { + args: "run --allow-read top_level_await_loop.js", + output: "top_level_await_loop.out", +}); + +itest!(top_level_await_circular { + args: "run --allow-read top_level_await_circular.js", + output: "top_level_await_circular.out", + exit_code: 1, +}); + +itest!(top_level_await_unresolved { + args: "run top_level_await_unresolved.js", + output: "top_level_await_unresolved.out", + exit_code: 1, +}); + itest!(top_level_await { args: "run --allow-read top_level_await.js", output: "top_level_await.out", diff --git a/cli/tests/tla/a.js b/cli/tests/tla/a.js new file mode 100644 index 000000000..c3ef3f7db --- /dev/null +++ b/cli/tests/tla/a.js @@ -0,0 +1,3 @@ +import order from "./order.js"; + +order.push("b"); diff --git a/cli/tests/tla/b.js b/cli/tests/tla/b.js new file mode 100644 index 000000000..3271c92d8 --- /dev/null +++ b/cli/tests/tla/b.js @@ -0,0 +1,7 @@ +import order from "./order.js"; + +await new Promise((resolve) => { + setTimeout(resolve, 200); +}); + +order.push("a"); diff --git a/cli/tests/tla/c.js b/cli/tests/tla/c.js new file mode 100644 index 000000000..806eb0a8b --- /dev/null +++ b/cli/tests/tla/c.js @@ -0,0 +1,3 @@ +import order from "./order.js"; + +order.push("c"); diff --git a/cli/tests/tla/d.js b/cli/tests/tla/d.js new file mode 100644 index 000000000..2b5fd3c45 --- /dev/null +++ b/cli/tests/tla/d.js @@ -0,0 +1,6 @@ +import order from "./order.js"; + +const end = Date.now() + 500; +while (end < Date.now()) {} + +order.push("d"); diff --git a/cli/tests/tla/order.js b/cli/tests/tla/order.js new file mode 100644 index 000000000..f213a562c --- /dev/null +++ b/cli/tests/tla/order.js @@ -0,0 +1 @@ +export default ["order"]; diff --git a/cli/tests/tla/parent.js b/cli/tests/tla/parent.js new file mode 100644 index 000000000..1ecc15463 --- /dev/null +++ b/cli/tests/tla/parent.js @@ -0,0 +1,9 @@ +import order from "./order.js"; +import "./a.js"; +import "./b.js"; +import "./c.js"; +import "./d.js"; + +order.push("parent"); + +export default order; diff --git a/cli/tests/tla2/a.js b/cli/tests/tla2/a.js new file mode 100644 index 000000000..d07bcb94d --- /dev/null +++ b/cli/tests/tla2/a.js @@ -0,0 +1,5 @@ +export default class Foo { + constructor(message) { + this.message = message; + } +} diff --git a/cli/tests/tla2/b.js b/cli/tests/tla2/b.js new file mode 100644 index 000000000..68e357c1e --- /dev/null +++ b/cli/tests/tla2/b.js @@ -0,0 +1,5 @@ +export default class Bar { + constructor(message) { + this.message = message; + } +} diff --git a/cli/tests/tla3/b.js b/cli/tests/tla3/b.js new file mode 100644 index 000000000..b74c659e4 --- /dev/null +++ b/cli/tests/tla3/b.js @@ -0,0 +1,7 @@ +import { foo } from "./timeout_loop.js"; +import { collection } from "../top_level_await_circular.js"; + +console.log("collection in b", collection); +console.log("foo in b", foo); + +export const a = "a"; diff --git a/cli/tests/tla3/timeout_loop.js b/cli/tests/tla3/timeout_loop.js new file mode 100644 index 000000000..860e6cd2a --- /dev/null +++ b/cli/tests/tla3/timeout_loop.js @@ -0,0 +1,23 @@ +export const foo = "foo"; + +export function delay(ms) { + return new Promise((res) => + setTimeout(() => { + res(); + }, ms) + ); +} + +let i = 0; + +async function timeoutLoop() { + await delay(1000); + console.log("timeout loop", i); + i++; + if (i > 5) { + return; + } + timeoutLoop(); +} + +timeoutLoop(); diff --git a/cli/tests/top_level_await_circular.js b/cli/tests/top_level_await_circular.js new file mode 100644 index 000000000..ff2964b6a --- /dev/null +++ b/cli/tests/top_level_await_circular.js @@ -0,0 +1,8 @@ +import { foo } from "./tla3/timeout_loop.js"; + +export const collection = []; + +const mod = await import("./tla3/b.js"); + +console.log("foo in main", foo); +console.log("mod", mod); diff --git a/cli/tests/top_level_await_circular.out b/cli/tests/top_level_await_circular.out new file mode 100644 index 000000000..d47564441 --- /dev/null +++ b/cli/tests/top_level_await_circular.out @@ -0,0 +1,7 @@ +timeout loop 0 +timeout loop 1 +timeout loop 2 +timeout loop 3 +timeout loop 4 +timeout loop 5 +error: Dynamically imported module evaluation is still pending but there are no pending ops. This situation is often caused by unresolved promise. diff --git a/cli/tests/top_level_await_loop.js b/cli/tests/top_level_await_loop.js new file mode 100644 index 000000000..384f8d0ed --- /dev/null +++ b/cli/tests/top_level_await_loop.js @@ -0,0 +1,18 @@ +const importsDir = Deno.readDirSync(Deno.realPathSync("./tla2")); + +const resolvedPaths = []; + +for (const { name } of importsDir) { + const filePath = Deno.realPathSync(`./tla2/${name}`); + resolvedPaths.push(filePath); +} + +resolvedPaths.sort(); + +for (const filePath of resolvedPaths) { + console.log("loading", filePath); + const mod = await import(`file://${filePath}`); + console.log("loaded", mod); +} + +console.log("all loaded"); diff --git a/cli/tests/top_level_await_loop.out b/cli/tests/top_level_await_loop.out new file mode 100644 index 000000000..d704e3afd --- /dev/null +++ b/cli/tests/top_level_await_loop.out @@ -0,0 +1,5 @@ +loading [WILDCARD]a.js +loaded Module { default: [Function: Foo], [Symbol(Symbol.toStringTag)]: "Module" } +loading [WILDCARD]b.js +loaded Module { default: [Function: Bar], [Symbol(Symbol.toStringTag)]: "Module" } +all loaded diff --git a/cli/tests/top_level_await_order.js b/cli/tests/top_level_await_order.js new file mode 100644 index 000000000..30659cdfb --- /dev/null +++ b/cli/tests/top_level_await_order.js @@ -0,0 +1,21 @@ +// Ported from Node +// https://github.com/nodejs/node/blob/54746bb763ebea0dc7e99d88ff4b379bcd680964/test/es-module/test-esm-tla.mjs + +const { default: order } = await import("./tla/parent.js"); + +console.log("order", JSON.stringify(order)); + +if ( + !( + order[0] === "order" && + order[1] === "b" && + order[2] === "c" && + order[3] === "d" && + order[4] === "a" && + order[5] === "parent" + ) +) { + throw new Error("TLA wrong order"); +} + +console.log("TLA order correct"); diff --git a/cli/tests/top_level_await_order.out b/cli/tests/top_level_await_order.out new file mode 100644 index 000000000..4cc27858c --- /dev/null +++ b/cli/tests/top_level_await_order.out @@ -0,0 +1,2 @@ +order ["order","b","c","d","a","parent"] +TLA order correct diff --git a/cli/tests/top_level_await_unresolved.js b/cli/tests/top_level_await_unresolved.js new file mode 100644 index 000000000..231a8cd63 --- /dev/null +++ b/cli/tests/top_level_await_unresolved.js @@ -0,0 +1 @@ +await new Promise(() => {}); diff --git a/cli/tests/top_level_await_unresolved.out b/cli/tests/top_level_await_unresolved.out new file mode 100644 index 000000000..77395f5d0 --- /dev/null +++ b/cli/tests/top_level_await_unresolved.out @@ -0,0 +1 @@ +error: Module evaluation is still pending but there are no pending ops or dynamic imports. This situation is often caused by unresolved promise. diff --git a/cli/worker.rs b/cli/worker.rs index 97e39d20c..d9d9f61c1 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -191,7 +191,7 @@ impl Worker { ) -> Result<(), AnyError> { let id = self.preload_module(module_specifier).await?; self.wait_for_inspector_session(); - self.js_runtime.mod_evaluate(id) + self.js_runtime.mod_evaluate(id).await } /// Returns a way to communicate with the Worker from other threads. diff --git a/core/modules.rs b/core/modules.rs index 130becab8..e12699ca9 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -667,7 +667,7 @@ mod tests { let a_id_fut = runtime.load_module(&spec, None); let a_id = futures::executor::block_on(a_id_fut).expect("Failed to load"); - runtime.mod_evaluate(a_id).unwrap(); + futures::executor::block_on(runtime.mod_evaluate(a_id)).unwrap(); let l = loads.lock().unwrap(); assert_eq!( l.to_vec(), @@ -734,7 +734,7 @@ mod tests { let result = runtime.load_module(&spec, None).await; assert!(result.is_ok()); let circular1_id = result.unwrap(); - runtime.mod_evaluate(circular1_id).unwrap(); + runtime.mod_evaluate(circular1_id).await.unwrap(); let l = loads.lock().unwrap(); assert_eq!( @@ -811,7 +811,7 @@ mod tests { println!(">> result {:?}", result); assert!(result.is_ok()); let redirect1_id = result.unwrap(); - runtime.mod_evaluate(redirect1_id).unwrap(); + runtime.mod_evaluate(redirect1_id).await.unwrap(); let l = loads.lock().unwrap(); assert_eq!( l.to_vec(), @@ -961,7 +961,7 @@ mod tests { let main_id = futures::executor::block_on(main_id_fut).expect("Failed to load"); - runtime.mod_evaluate(main_id).unwrap(); + futures::executor::block_on(runtime.mod_evaluate(main_id)).unwrap(); let l = loads.lock().unwrap(); assert_eq!( diff --git a/core/runtime.rs b/core/runtime.rs index 26b62cbe3..0748abc1d 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -4,6 +4,7 @@ use rusty_v8 as v8; use crate::bindings; use crate::error::attach_handle_to_error; +use crate::error::generic_error; use crate::error::AnyError; use crate::error::ErrWithV8Handle; use crate::error::JsError; @@ -23,6 +24,7 @@ use crate::shared_queue::SharedQueue; use crate::shared_queue::RECOMMENDED_SIZE; use crate::BufVec; use crate::OpState; +use futures::channel::mpsc; use futures::future::poll_fn; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; @@ -83,6 +85,17 @@ pub struct JsRuntime { allocations: IsolateAllocations, } +struct DynImportModEvaluate { + module_id: ModuleId, + promise: v8::Global<v8::Promise>, + module: v8::Global<v8::Module>, +} + +struct ModEvaluate { + promise: v8::Global<v8::Promise>, + sender: mpsc::Sender<Result<(), AnyError>>, +} + /// Internal state for JsRuntime which is stored in one of v8::Isolate's /// embedder slots. pub(crate) struct JsRuntimeState { @@ -91,6 +104,8 @@ pub(crate) struct JsRuntimeState { pub(crate) js_recv_cb: Option<v8::Global<v8::Function>>, pub(crate) js_macrotask_cb: Option<v8::Global<v8::Function>>, pub(crate) pending_promise_exceptions: HashMap<i32, v8::Global<v8::Value>>, + pending_dyn_mod_evaluate: HashMap<ModuleLoadId, DynImportModEvaluate>, + pending_mod_evaluate: Option<ModEvaluate>, pub(crate) js_error_create_fn: Box<JsErrorCreateFn>, pub(crate) shared: SharedQueue, pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>, @@ -264,6 +279,8 @@ impl JsRuntime { isolate.set_slot(Rc::new(RefCell::new(JsRuntimeState { global_context: Some(global_context), pending_promise_exceptions: HashMap::new(), + pending_dyn_mod_evaluate: HashMap::new(), + pending_mod_evaluate: None, shared_ab: None, js_recv_cb: None, js_macrotask_cb: None, @@ -466,6 +483,14 @@ impl JsRuntime { state.waker.register(cx.waker()); } + // Ops + { + let overflow_response = self.poll_pending_ops(cx); + self.async_op_response(overflow_response)?; + self.drain_macrotasks()?; + self.check_promise_exceptions()?; + } + // Dynamic module loading - ie. modules loaded using "import()" { let poll_imports = self.prepare_dyn_imports(cx)?; @@ -474,25 +499,30 @@ impl JsRuntime { let poll_imports = self.poll_dyn_imports(cx)?; assert!(poll_imports.is_ready()); - self.check_promise_exceptions()?; - } + self.evaluate_dyn_imports()?; - // Ops - { - let overflow_response = self.poll_pending_ops(cx); - self.async_op_response(overflow_response)?; - self.drain_macrotasks()?; self.check_promise_exceptions()?; } + // Top level module + self.evaluate_pending_module()?; + let state = state_rc.borrow(); - let is_idle = { - state.pending_ops.is_empty() + 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() - && state.preparing_dyn_imports.is_empty() }; - - if is_idle { + let has_pending_dyn_module_evaluation = + !state.pending_dyn_mod_evaluate.is_empty(); + let has_pending_module_evaluation = state.pending_mod_evaluate.is_some(); + + if !has_pending_ops + && !has_pending_dyn_imports + && !has_pending_dyn_module_evaluation + && !has_pending_module_evaluation + { return Poll::Ready(Ok(())); } @@ -502,6 +532,27 @@ impl JsRuntime { state.waker.wake(); } + if has_pending_module_evaluation { + if has_pending_ops + || has_pending_dyn_imports + || has_pending_dyn_module_evaluation + { + // pass, will be polled again + } else { + let msg = "Module evaluation is still pending but there are no pending ops or dynamic imports. This situation is often caused by unresolved promise."; + return Poll::Ready(Err(generic_error(msg))); + } + } + + if has_pending_dyn_module_evaluation { + if has_pending_ops || has_pending_dyn_imports { + // pass, will be polled again + } else { + let msg = "Dynamically imported module evaluation is still pending but there are no pending ops. This situation is often caused by unresolved promise."; + return Poll::Ready(Err(generic_error(msg))); + } + } + Poll::Pending } } @@ -694,7 +745,100 @@ impl JsRuntime { /// `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 mod_evaluate(&mut self, id: ModuleId) -> Result<(), AnyError> { + pub fn dyn_mod_evaluate( + &mut self, + load_id: ModuleLoadId, + id: ModuleId, + ) -> Result<(), AnyError> { + self.shared_init(); + + let state_rc = Self::state(self.v8_isolate()); + let context = self.global_context(); + let context1 = self.global_context(); + + let module_handle = state_rc + .borrow() + .modules + .get_info(id) + .expect("ModuleInfo not found") + .handle + .clone(); + + let status = { + let scope = + &mut v8::HandleScope::with_context(self.v8_isolate(), context); + let module = module_handle.get(scope); + module.get_status() + }; + + if status == v8::ModuleStatus::Instantiated { + // IMPORTANT: Top-level-await is enabled, which means that return value + // of module evaluation is a promise. + // + // Because that promise is created internally by V8, when error occurs during + // module evaluation the promise is rejected, and since the promise has no rejection + // handler it will result in call to `bindings::promise_reject_callback` adding + // the promise to pending promise rejection table - meaning JsRuntime will return + // error on next poll(). + // + // This situation is not desirable as we want to manually return error at the + // end of this function to handle it further. It means we need to manually + // remove this promise from pending promise rejection table. + // + // For more details see: + // https://github.com/denoland/deno/issues/4908 + // https://v8.dev/features/top-level-await#module-execution-order + let scope = + &mut v8::HandleScope::with_context(self.v8_isolate(), context1); + let module = v8::Local::new(scope, &module_handle); + let maybe_value = module.evaluate(scope); + + // Update status after evaluating. + let status = module.get_status(); + + if let Some(value) = maybe_value { + assert!( + status == v8::ModuleStatus::Evaluated + || status == v8::ModuleStatus::Errored + ); + let promise = v8::Local::<v8::Promise>::try_from(value) + .expect("Expected to get promise as module evaluation result"); + let promise_id = promise.get_identity_hash(); + let mut state = state_rc.borrow_mut(); + state.pending_promise_exceptions.remove(&promise_id); + let promise_global = v8::Global::new(scope, promise); + let module_global = v8::Global::new(scope, module); + + let dyn_import_mod_evaluate = DynImportModEvaluate { + module_id: id, + promise: promise_global, + module: module_global, + }; + + state + .pending_dyn_mod_evaluate + .insert(load_id, dyn_import_mod_evaluate); + } else { + assert!(status == v8::ModuleStatus::Errored); + } + } + + if status == v8::ModuleStatus::Evaluated { + self.dyn_import_done(load_id, id)?; + } + + 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. + fn mod_evaluate_inner( + &mut self, + id: ModuleId, + ) -> Result<mpsc::Receiver<Result<(), AnyError>>, AnyError> { self.shared_init(); let state_rc = Self::state(self.v8_isolate()); @@ -710,6 +854,8 @@ impl JsRuntime { .expect("ModuleInfo not found"); let mut status = module.get_status(); + let (sender, receiver) = mpsc::channel(1); + if status == v8::ModuleStatus::Instantiated { // IMPORTANT: Top-level-await is enabled, which means that return value // of module evaluation is a promise. @@ -742,20 +888,37 @@ impl JsRuntime { let promise_id = promise.get_identity_hash(); let mut state = state_rc.borrow_mut(); state.pending_promise_exceptions.remove(&promise_id); + let promise_global = v8::Global::new(scope, promise); + assert!( + state.pending_mod_evaluate.is_none(), + "There is already pending top level module evaluation" + ); + + state.pending_mod_evaluate = Some(ModEvaluate { + promise: promise_global, + sender, + }); + scope.perform_microtask_checkpoint(); } else { assert!(status == v8::ModuleStatus::Errored); } } - match status { - v8::ModuleStatus::Evaluated => Ok(()), - v8::ModuleStatus::Errored => { - let exception = module.get_exception(); - exception_to_err_result(scope, exception) - .map_err(|err| attach_handle_to_error(scope, err, exception)) + Ok(receiver) + } + + pub async fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), AnyError> { + let mut receiver = self.mod_evaluate_inner(id)?; + + poll_fn(|cx| { + if let Poll::Ready(result) = receiver.poll_next_unpin(cx) { + debug!("received module evaluate"); + return Poll::Ready(result.unwrap()); } - other => panic!("Unexpected module status {:?}", other), - } + let _r = self.poll_event_loop(cx)?; + Poll::Pending + }) + .await } fn dyn_import_error( @@ -916,16 +1079,132 @@ impl JsRuntime { // Load is done. let module_id = load.root_module_id.unwrap(); self.mod_instantiate(module_id)?; - match self.mod_evaluate(module_id) { - Ok(()) => self.dyn_import_done(dyn_import_id, module_id)?, - Err(err) => self.dyn_import_error(dyn_import_id, err)?, - }; + self.dyn_mod_evaluate(dyn_import_id, module_id)?; } } } } } + /// "deno_core" runs V8 with "--harmony-top-level-await" + /// flag on - it means that each module evaluation returns a promise + /// from V8. + /// + /// This promise resolves after all dependent modules have also + /// resolved. Each dependent module may perform calls to "import()" and APIs + /// using async ops will add futures to the runtime's event loop. + /// It means that the promise returned from module evaluation will + /// resolve only after all futures in the event loop are done. + /// + /// Thus during turn of event loop we need to check if V8 has + /// resolved or rejected the promise. If the promise is still pending + /// then another turn of event loop must be performed. + fn evaluate_pending_module(&mut self) -> Result<(), AnyError> { + let state_rc = Self::state(self.v8_isolate()); + + let context = self.global_context(); + { + let scope = + &mut v8::HandleScope::with_context(self.v8_isolate(), context); + + let mut state = state_rc.borrow_mut(); + + if let Some(module_evaluation) = state.pending_mod_evaluate.as_ref() { + let promise = module_evaluation.promise.get(scope); + let mut sender = module_evaluation.sender.clone(); + let promise_state = promise.state(); + + match promise_state { + v8::PromiseState::Pending => { + // pass, poll_event_loop will decide if + // runtime would be woken soon + } + v8::PromiseState::Fulfilled => { + state.pending_mod_evaluate.take(); + scope.perform_microtask_checkpoint(); + sender.try_send(Ok(())).unwrap(); + } + v8::PromiseState::Rejected => { + let exception = promise.result(scope); + state.pending_mod_evaluate.take(); + drop(state); + scope.perform_microtask_checkpoint(); + let err1 = exception_to_err_result::<()>(scope, exception) + .map_err(|err| attach_handle_to_error(scope, err, exception)) + .unwrap_err(); + sender.try_send(Err(err1)).unwrap(); + } + } + } + }; + + Ok(()) + } + + fn evaluate_dyn_imports(&mut self) -> Result<(), AnyError> { + let state_rc = Self::state(self.v8_isolate()); + + loop { + let context = self.global_context(); + let maybe_result = { + let scope = + &mut v8::HandleScope::with_context(self.v8_isolate(), context); + + let mut state = state_rc.borrow_mut(); + if let Some(&dyn_import_id) = + state.pending_dyn_mod_evaluate.keys().next() + { + let handle = state + .pending_dyn_mod_evaluate + .remove(&dyn_import_id) + .unwrap(); + drop(state); + + let module_id = handle.module_id; + let promise = handle.promise.get(scope); + let _module = handle.module.get(scope); + + let promise_state = promise.state(); + + match promise_state { + v8::PromiseState::Pending => { + state_rc + .borrow_mut() + .pending_dyn_mod_evaluate + .insert(dyn_import_id, handle); + None + } + v8::PromiseState::Fulfilled => Some(Ok((dyn_import_id, module_id))), + v8::PromiseState::Rejected => { + let exception = promise.result(scope); + let err1 = exception_to_err_result::<()>(scope, exception) + .map_err(|err| attach_handle_to_error(scope, err, exception)) + .unwrap_err(); + Some(Err((dyn_import_id, err1))) + } + } + } else { + None + } + }; + + if let Some(result) = maybe_result { + match result { + Ok((dyn_import_id, module_id)) => { + self.dyn_import_done(dyn_import_id, module_id)?; + } + Err((dyn_import_id, err1)) => { + self.dyn_import_error(dyn_import_id, err1)?; + } + } + } else { + break; + } + } + + Ok(()) + } + fn register_during_load( &mut self, info: ModuleSource, @@ -1997,7 +2276,7 @@ pub mod tests { runtime.mod_instantiate(mod_a).unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); - runtime.mod_evaluate(mod_a).unwrap(); + runtime.mod_evaluate_inner(mod_a).unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); } @@ -2240,7 +2519,7 @@ pub mod tests { ) .unwrap(); - runtime.mod_evaluate(module_id).unwrap(); + futures::executor::block_on(runtime.mod_evaluate(module_id)).unwrap(); let _snapshot = runtime.snapshot(); } |
