mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor(core): Move optional callbacks from JsRuntimeState
to ContextState
(#17422)
The `JsRuntimeState` struct stores a number of JS callbacks that are used either in the event loop or when interacting with V8. Some of these callback fields are vectors of callbacks, and therefore could plausibly store at least one callback per realm. However, some of those fields are `Option<v8::Global<v8::Function>>`, which would make the callbacks set by a realm override the one that might have been set by a different realm. As it turns out, all of the current such optional callbacks (`js_promise_reject_cb`, `js_format_exception_cb` and `js_wasm_streaming_cb`) are only used from inside a realm, and therefore this change makes it so such callbacks can only be set from inside a realm, and will only affect that realm. This is a reland of #15599. Towards #13239.
This commit is contained in:
parent
df8bfa26be
commit
05ef925eb0
4 changed files with 93 additions and 33 deletions
|
@ -16,6 +16,7 @@ use crate::modules::ModuleMap;
|
|||
use crate::modules::ResolutionKind;
|
||||
use crate::ops::OpCtx;
|
||||
use crate::runtime::SnapshotOptions;
|
||||
use crate::JsRealm;
|
||||
use crate::JsRuntime;
|
||||
|
||||
pub fn external_references(
|
||||
|
@ -453,15 +454,15 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
|
|||
// SAFETY: `CallbackScope` can be safely constructed from `&PromiseRejectMessage`
|
||||
let scope = &mut unsafe { v8::CallbackScope::new(&message) };
|
||||
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let mut state = state_rc.borrow_mut();
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
|
||||
if let Some(js_promise_reject_cb) = state.js_promise_reject_cb.clone() {
|
||||
let promise_reject_cb =
|
||||
context_state_rc.borrow().js_promise_reject_cb.clone();
|
||||
if let Some(js_promise_reject_cb) = promise_reject_cb {
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into();
|
||||
let type_ = v8::Integer::new(tc_scope, message.get_event() as i32);
|
||||
let promise = message.get_promise();
|
||||
drop(state); // Drop borrow, callbacks can call back into runtime.
|
||||
|
||||
let reason = match message.get_event() {
|
||||
PromiseRejectWithNoHandler
|
||||
|
@ -484,6 +485,7 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
|
|||
};
|
||||
|
||||
if has_unhandled_rejection_handler {
|
||||
let state_rc = JsRuntime::state(tc_scope);
|
||||
let mut state = state_rc.borrow_mut();
|
||||
if let Some(pending_mod_evaluate) = state.pending_mod_evaluate.as_mut() {
|
||||
if !pending_mod_evaluate.has_evaluated {
|
||||
|
@ -494,6 +496,8 @@ pub extern "C" fn promise_reject_callback(message: v8::PromiseRejectMessage) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let mut state = state_rc.borrow_mut();
|
||||
let promise = message.get_promise();
|
||||
let promise_global = v8::Global::new(scope, promise);
|
||||
match message.get_event() {
|
||||
|
|
|
@ -312,10 +312,10 @@ impl JsError {
|
|||
let msg = v8::Exception::create_message(scope, exception);
|
||||
|
||||
let mut exception_message = None;
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
|
||||
let js_format_exception_cb =
|
||||
state_rc.borrow().js_format_exception_cb.clone();
|
||||
context_state_rc.borrow().js_format_exception_cb.clone();
|
||||
if let Some(format_exception_cb) = js_format_exception_cb {
|
||||
let format_exception_cb = format_exception_cb.open(scope);
|
||||
let this = v8::undefined(scope).into();
|
||||
|
@ -381,6 +381,7 @@ impl JsError {
|
|||
let mut source_line = None;
|
||||
let mut source_line_frame_index = None;
|
||||
{
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let state = &mut *state_rc.borrow_mut();
|
||||
|
||||
// When the stack frame array is empty, but the source location given by
|
||||
|
|
|
@ -114,8 +114,11 @@ fn op_set_promise_reject_callback<'a>(
|
|||
cb: serde_v8::Value,
|
||||
) -> Result<Option<serde_v8::Value<'a>>, Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let old = state_rc.borrow_mut().js_promise_reject_cb.replace(cb);
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let old = context_state_rc
|
||||
.borrow_mut()
|
||||
.js_promise_reject_cb
|
||||
.replace(cb);
|
||||
let old = old.map(|v| v8::Local::new(scope, v));
|
||||
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
|
||||
}
|
||||
|
@ -682,22 +685,28 @@ fn op_set_wasm_streaming_callback(
|
|||
cb: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let mut state = state_rc.borrow_mut();
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let mut context_state = context_state_rc.borrow_mut();
|
||||
// The callback to pass to the v8 API has to be a unit type, so it can't
|
||||
// borrow or move any local variables. Therefore, we're storing the JS
|
||||
// callback in a JsRuntimeState slot.
|
||||
if state.js_wasm_streaming_cb.is_some() {
|
||||
if context_state.js_wasm_streaming_cb.is_some() {
|
||||
return Err(type_error("op_set_wasm_streaming_callback already called"));
|
||||
}
|
||||
state.js_wasm_streaming_cb = Some(cb);
|
||||
context_state.js_wasm_streaming_cb = Some(cb);
|
||||
|
||||
scope.set_wasm_streaming_callback(|scope, arg, wasm_streaming| {
|
||||
let (cb_handle, streaming_rid) = {
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let cb_handle = context_state_rc
|
||||
.borrow()
|
||||
.js_wasm_streaming_cb
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.clone();
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let state = state_rc.borrow();
|
||||
let cb_handle = state.js_wasm_streaming_cb.as_ref().unwrap().clone();
|
||||
let streaming_rid = state
|
||||
let streaming_rid = state_rc
|
||||
.borrow()
|
||||
.op_state
|
||||
.borrow_mut()
|
||||
.resource_table
|
||||
|
@ -835,8 +844,11 @@ fn op_set_format_exception_callback<'a>(
|
|||
cb: serde_v8::Value<'a>,
|
||||
) -> Result<Option<serde_v8::Value<'a>>, Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
let old = state_rc.borrow_mut().js_format_exception_cb.replace(cb);
|
||||
let context_state_rc = JsRealm::state_from_scope(scope);
|
||||
let old = context_state_rc
|
||||
.borrow_mut()
|
||||
.js_format_exception_cb
|
||||
.replace(cb);
|
||||
let old = old.map(|v| v8::Local::new(scope, v));
|
||||
Ok(old.map(|v| from_v8(scope, v.into()).unwrap()))
|
||||
}
|
||||
|
|
|
@ -152,8 +152,9 @@ pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
|
|||
pub(crate) struct ContextState {
|
||||
js_recv_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) js_build_custom_error_cb: Option<v8::Global<v8::Function>>,
|
||||
// TODO(andreubotella): Move the rest of Option<Global<Function>> fields from
|
||||
// JsRuntimeState to this struct.
|
||||
pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) js_format_exception_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) unrefed_ops: HashSet<i32>,
|
||||
// We don't explicitly re-read this prop but need the slice to live alongside
|
||||
// the context
|
||||
|
@ -167,10 +168,7 @@ pub struct JsRuntimeState {
|
|||
known_realms: Vec<v8::Weak<v8::Context>>,
|
||||
pub(crate) js_macrotask_cbs: Vec<v8::Global<v8::Function>>,
|
||||
pub(crate) js_nexttick_cbs: Vec<v8::Global<v8::Function>>,
|
||||
pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) js_format_exception_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) has_tick_scheduled: bool,
|
||||
pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) pending_promise_exceptions:
|
||||
HashMap<v8::Global<v8::Promise>, v8::Global<v8::Value>>,
|
||||
pub(crate) pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
|
||||
|
@ -392,10 +390,7 @@ impl JsRuntime {
|
|||
dyn_module_evaluate_idle_counter: 0,
|
||||
js_macrotask_cbs: vec![],
|
||||
js_nexttick_cbs: vec![],
|
||||
js_promise_reject_cb: None,
|
||||
js_format_exception_cb: None,
|
||||
has_tick_scheduled: false,
|
||||
js_wasm_streaming_cb: None,
|
||||
source_map_getter: options.source_map_getter,
|
||||
source_map_cache: Default::default(),
|
||||
pending_ops: FuturesUnordered::new(),
|
||||
|
@ -932,19 +927,18 @@ impl JsRuntime {
|
|||
let v8_isolate = self.v8_isolate();
|
||||
if let Some(context) = weak_context.to_global(v8_isolate) {
|
||||
let realm = JsRealm::new(context.clone());
|
||||
let realm_state = realm.state(v8_isolate);
|
||||
std::mem::take(&mut realm_state.borrow_mut().js_recv_cb);
|
||||
std::mem::take(
|
||||
&mut realm_state.borrow_mut().js_build_custom_error_cb,
|
||||
);
|
||||
let realm_state_rc = realm.state(v8_isolate);
|
||||
let mut realm_state = realm_state_rc.borrow_mut();
|
||||
std::mem::take(&mut realm_state.js_recv_cb);
|
||||
std::mem::take(&mut realm_state.js_build_custom_error_cb);
|
||||
std::mem::take(&mut realm_state.js_promise_reject_cb);
|
||||
std::mem::take(&mut realm_state.js_format_exception_cb);
|
||||
std::mem::take(&mut realm_state.js_wasm_streaming_cb);
|
||||
context.open(v8_isolate).clear_all_slots(v8_isolate);
|
||||
}
|
||||
}
|
||||
|
||||
let mut state = self.state.borrow_mut();
|
||||
std::mem::take(&mut state.js_promise_reject_cb);
|
||||
std::mem::take(&mut state.js_format_exception_cb);
|
||||
std::mem::take(&mut state.js_wasm_streaming_cb);
|
||||
state.js_macrotask_cbs.clear();
|
||||
state.js_nexttick_cbs.clear();
|
||||
state.known_realms.clear();
|
||||
|
@ -4080,6 +4074,55 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
|
|||
assert_eq!(2, PROMISE_REJECT.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_set_promise_reject_callback_realms() {
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions::default());
|
||||
let global_realm = runtime.global_realm();
|
||||
let realm1 = runtime.create_realm().unwrap();
|
||||
let realm2 = runtime.create_realm().unwrap();
|
||||
|
||||
let realm_expectations = &[
|
||||
(&global_realm, "global_realm", 42),
|
||||
(&realm1, "realm1", 140),
|
||||
(&realm2, "realm2", 720),
|
||||
];
|
||||
|
||||
// Set up promise reject callbacks.
|
||||
for (realm, realm_name, number) in realm_expectations {
|
||||
realm
|
||||
.execute_script(
|
||||
runtime.v8_isolate(),
|
||||
"",
|
||||
&format!(
|
||||
r#"
|
||||
Deno.core.initializeAsyncOps();
|
||||
globalThis.rejectValue = undefined;
|
||||
Deno.core.setPromiseRejectCallback((_type, _promise, reason) => {{
|
||||
globalThis.rejectValue = `{realm_name}/${{reason}}`;
|
||||
}});
|
||||
Deno.core.ops.op_void_async().then(() => Promise.reject({number}));
|
||||
"#,
|
||||
realm_name=realm_name,
|
||||
number=number
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
runtime.run_event_loop(false).await.unwrap();
|
||||
|
||||
for (realm, realm_name, number) in realm_expectations {
|
||||
let reject_value = realm
|
||||
.execute_script(runtime.v8_isolate(), "", "globalThis.rejectValue")
|
||||
.unwrap();
|
||||
let scope = &mut realm.handle_scope(runtime.v8_isolate());
|
||||
let reject_value = v8::Local::new(scope, reject_value);
|
||||
assert!(reject_value.is_string());
|
||||
let reject_value_string = reject_value.to_rust_string_lossy(scope);
|
||||
assert_eq!(reject_value_string, format!("{}/{}", realm_name, number));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_set_promise_reject_callback_top_level_await() {
|
||||
static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
|
Loading…
Reference in a new issue