diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2022-11-28 23:07:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-28 23:07:23 +0100 |
commit | f526513d74d34ac254aa40ef9b73238cb21c395b (patch) | |
tree | 066e2c3685e33f611d2d38b04d49b01e4604573a /core/runtime.rs | |
parent | fd51b2e506f3ea3cc49bfb2bcb19bc684f563f60 (diff) |
feat(core): show unresolved promise origin (#16650)
This commit updates unhelpful messages that are raised when event loop
stalls on unresolved top-level promises.
Instead of "Module evaluation is still pending but there are no pending
ops or dynamic imports. This situation is often caused by unresolved
promises." and "Dynamically imported module evaluation is still pending
but there are no pending ops. This situation is often caused by
unresolved promises." we are now printing a message like:
error: Top-level await promise never resolved
[SOURCE LINE]
^
at [FUNCTION NAME] ([FILENAME])
eg:
error: Top-level await promise never resolved
await new Promise((_resolve, _reject) => {});
^
at <anonymous>
(file:///Users/ib/dev/deno/cli/tests/testdata/test/unresolved_promise.ts:1:1)
Co-authored-by: David Sherret <dsherret@users.noreply.github.com>
Diffstat (limited to 'core/runtime.rs')
-rw-r--r-- | core/runtime.rs | 93 |
1 files changed, 78 insertions, 15 deletions
diff --git a/core/runtime.rs b/core/runtime.rs index 527226626..4647160c9 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -1168,7 +1168,7 @@ impl JsRuntime { return Poll::Ready(Ok(())); } - let mut state = self.state.borrow_mut(); + let state = self.state.borrow(); // Check if more async ops have been dispatched // during this turn of event loop. @@ -1185,6 +1185,8 @@ impl JsRuntime { state.waker.wake(); } + drop(state); + if pending_state.has_pending_module_evaluation { if pending_state.has_pending_refed_ops || pending_state.has_pending_dyn_imports @@ -1195,8 +1197,16 @@ impl JsRuntime { { // 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 promises."; - return Poll::Ready(Err(generic_error(msg))); + let scope = &mut self.handle_scope(); + let messages = find_stalled_top_level_await(scope); + // We are gonna print only a single message to provide a nice formatting + // with source line of offending promise shown. Once user fixed it, then + // they will get another error message for the next promise (but this + // situation is gonna be very rare, if ever happening). + assert!(!messages.is_empty()); + let msg = v8::Local::new(scope, messages[0].clone()); + let js_error = JsError::from_v8_message(scope, msg); + return Poll::Ready(Err(js_error.into())); } } @@ -1207,19 +1217,19 @@ impl JsRuntime { || pending_state.has_tick_scheduled { // pass, will be polled again - } else if state.dyn_module_evaluate_idle_counter >= 1 { - let mut msg = "Dynamically imported module evaluation is still pending but there are no pending ops. This situation is often caused by unresolved promises. -Pending dynamic modules:\n".to_string(); - let module_map = self.module_map.as_mut().unwrap().borrow_mut(); - for pending_evaluate in &state.pending_dyn_mod_evaluate { - let module_info = module_map - .get_info_by_id(&pending_evaluate.module_id) - .unwrap(); - msg.push_str("- "); - msg.push_str(module_info.name.as_str()); - } - return Poll::Ready(Err(generic_error(msg))); + } else if self.state.borrow().dyn_module_evaluate_idle_counter >= 1 { + let scope = &mut self.handle_scope(); + let messages = find_stalled_top_level_await(scope); + // We are gonna print only a single message to provide a nice formatting + // with source line of offending promise shown. Once user fixed it, then + // they will get another error message for the next promise (but this + // situation is gonna be very rare, if ever happening). + assert!(!messages.is_empty()); + let msg = v8::Local::new(scope, messages[0].clone()); + let js_error = JsError::from_v8_message(scope, msg); + return Poll::Ready(Err(js_error.into())); } else { + let mut state = self.state.borrow_mut(); // Delay the above error by one spin of the event loop. A dynamic import // evaluation may complete during this, in which case the counter will // reset. @@ -1269,6 +1279,59 @@ Pending dynamic modules:\n".to_string(); } } +fn get_stalled_top_level_await_message_for_module<'s>( + scope: &'s mut v8::HandleScope, + module_id: ModuleId, +) -> Vec<v8::Global<v8::Message>> { + let module_map = JsRuntime::module_map(scope); + let module_map = module_map.borrow(); + let module_handle = module_map.handles_by_id.get(&module_id).unwrap(); + + let module = v8::Local::new(scope, module_handle); + let stalled = module.get_stalled_top_level_await_message(scope); + let mut messages = vec![]; + for (_, message) in stalled { + messages.push(v8::Global::new(scope, message)); + } + messages +} + +fn find_stalled_top_level_await<'s>( + scope: &'s mut v8::HandleScope, +) -> Vec<v8::Global<v8::Message>> { + let module_map = JsRuntime::module_map(scope); + let module_map = module_map.borrow(); + + // First check if that's root module + let root_module_id = module_map + .info + .values() + .filter(|m| m.main) + .map(|m| m.id) + .next(); + + if let Some(root_module_id) = root_module_id { + let messages = + get_stalled_top_level_await_message_for_module(scope, root_module_id); + if !messages.is_empty() { + return messages; + } + } + + // It wasn't a top module, so iterate over all modules and try to find + // any with stalled top level await + let module_ids = module_map.handles_by_id.keys().copied().collect::<Vec<_>>(); + for module_id in module_ids { + let messages = + get_stalled_top_level_await_message_for_module(scope, module_id); + if !messages.is_empty() { + return messages; + } + } + + unreachable!() +} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) struct EventLoopPendingState { has_pending_refed_ops: bool, |