Skip to content

Commit

Permalink
feat: v8::Module::get_stalled_top_level_await_message (denoland#1123)
Browse files Browse the repository at this point in the history
This commit adds "v8::Module::get_stalled_top_level_await_message" API
that allows to retrieve a vector of tuples with handles to v8::Module and v8::Message.
This information can be used to display a nice error when event loop runs out
of work to do but there are still unresolved promises.
  • Loading branch information
bartlomieju authored Nov 17, 2022
1 parent cc7183d commit 3d30e7c
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <iostream>

#include "support.h"
Expand Down Expand Up @@ -2690,6 +2691,26 @@ const v8::UnboundModuleScript* v8__Module__GetUnboundModuleScript(
return local_to_ptr(ptr_to_local(&self)->GetUnboundModuleScript());
}

struct StalledTopLevelAwaitMessage {
const v8::Module* module;
const v8::Message* message;
};


size_t v8__Module__GetStalledTopLevelAwaitMessage(
const v8::Module& self, v8::Isolate* isolate,
StalledTopLevelAwaitMessage* out_vec, size_t out_len) {
auto messages = ptr_to_local(&self)->GetStalledTopLevelAwaitMessage(isolate);
auto len = std::min(messages.size(), out_len);
for (size_t i = 0; i < len; i += 1) {
StalledTopLevelAwaitMessage stalled_message;
stalled_message.module = local_to_ptr(std::get<0>(messages[i]));
stalled_message.message = local_to_ptr(std::get<1>(messages[i]));
out_vec[i] = stalled_message;
}
return len;
}

const v8::String* v8__ModuleRequest__GetSpecifier(
const v8::ModuleRequest& self) {
return local_to_ptr(self.GetSpecifier());
Expand Down
51 changes: 51 additions & 0 deletions src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::FixedArray;
use crate::HandleScope;
use crate::Isolate;
use crate::Local;
use crate::Message;
use crate::Module;
use crate::ModuleRequest;
use crate::String;
Expand Down Expand Up @@ -195,6 +196,18 @@ extern "C" {
fn v8__ModuleRequest__GetImportAssertions(
this: *const ModuleRequest,
) -> *const FixedArray;
fn v8__Module__GetStalledTopLevelAwaitMessage(
this: *const Module,
isolate: *const Isolate,
out_vec: *mut StalledTopLevelAwaitMessage,
vec_len: usize,
) -> usize;
}

#[repr(C)]
pub struct StalledTopLevelAwaitMessage {
pub module: *const Module,
pub message: *const Message,
}

/// A location in JavaScript source.
Expand Down Expand Up @@ -413,6 +426,44 @@ impl Module {
.unwrap()
}
}

/// Search the modules requested directly or indirectly by the module for
/// any top-level await that has not yet resolved. If there is any, the
/// returned vector contains a tuple of the unresolved module and a message
/// with the pending top-level await.
/// An embedder may call this before exiting to improve error messages.
pub fn get_stalled_top_level_await_message(
&self,
scope: &mut HandleScope,
) -> Vec<(Local<Module>, Local<Message>)> {
let mut out_vec: Vec<StalledTopLevelAwaitMessage> = Vec::with_capacity(16);
for _i in 0..16 {
out_vec.push(StalledTopLevelAwaitMessage {
module: std::ptr::null(),
message: std::ptr::null(),
});
}

let returned_len = unsafe {
v8__Module__GetStalledTopLevelAwaitMessage(
&*self,
scope.get_isolate_ptr(),
out_vec.as_mut_ptr(),
out_vec.len(),
)
};

let mut ret_vec = Vec::with_capacity(returned_len);
for item in out_vec.iter().take(returned_len) {
unsafe {
ret_vec.push((
Local::from_raw(item.module).unwrap(),
Local::from_raw(item.message).unwrap(),
));
}
}
ret_vec
}
}

impl ModuleRequest {
Expand Down
60 changes: 60 additions & 0 deletions tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3506,6 +3506,66 @@ fn module_evaluation() {
}
}

#[test]
fn module_stalled_top_level_await() {
let _setup_guard = setup();
let isolate = &mut v8::Isolate::new(Default::default());
{
let scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);

let source_text =
v8::String::new(scope, "await new Promise((_resolve, _reject) => {});")
.unwrap();
let origin = mock_script_origin(scope, "foo.js");
let source = v8::script_compiler::Source::new(source_text, Some(&origin));

let module = v8::script_compiler::compile_module(scope, source).unwrap();
assert!(module.script_id().is_some());
assert!(module.is_source_text_module());
assert!(!module.is_synthetic_module());
assert_eq!(v8::ModuleStatus::Uninstantiated, module.get_status());
module.hash(&mut DefaultHasher::new()); // Should not crash.

let result = module
.instantiate_module(scope, compile_specifier_as_module_resolve_callback);
assert!(result.unwrap());
assert_eq!(v8::ModuleStatus::Instantiated, module.get_status());

let result = module.evaluate(scope);
assert!(result.is_some());
assert_eq!(v8::ModuleStatus::Evaluated, module.get_status());

let promise: v8::Local<v8::Promise> = result.unwrap().try_into().unwrap();
scope.perform_microtask_checkpoint();
assert_eq!(promise.state(), v8::PromiseState::Pending);
let stalled = module.get_stalled_top_level_await_message(scope);
assert_eq!(stalled.len(), 1);
let (_module, message) = stalled[0];
let message_str = message.get(scope);
assert_eq!(
message_str.to_rust_string_lossy(scope),
"Top-level await promise never resolved"
);
assert_eq!(Some(1), message.get_line_number(scope));
assert_eq!(
message
.get_script_resource_name(scope)
.unwrap()
.to_rust_string_lossy(scope),
"foo.js"
);
assert_eq!(
message
.get_source_line(scope)
.unwrap()
.to_rust_string_lossy(scope),
"await new Promise((_resolve, _reject) => {});"
);
}
}

#[test]
fn import_assertions() {
let _setup_guard = setup();
Expand Down

0 comments on commit 3d30e7c

Please sign in to comment.