1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

feat(core): Reland support for async ops in realms (#17204)

Currently realms are supported on `deno_core`, but there was no support
for async ops anywhere other than the main realm. The main issue is that
the `js_recv_cb` callback, which resolves promises corresponding to
async ops, was only set for the main realm, so async ops in other realms
would never resolve. Furthermore, promise ID's are specific to each
realm, which meant that async ops from other realms would result in a
wrong promise from the main realm being resolved.

This change takes the `ContextState` struct added in #17050, and adds to
it a `js_recv_cb` callback for each realm. Combined with the fact that
that same PR also added a list of known realms to `JsRuntimeState`, and
that #17174 made `OpCtx` instances realm-specific and had them include
an index into that list of known realms, this makes it possible to know
the current realm in the `queue_async_op` and `queue_fast_async_op`
methods, and therefore to send the results of promises for each realm to
that realm, and prevent the ID's from getting mixed up.

Additionally, since promise ID's are no longer unique to the isolate,
having a single set of unrefed ops doesn't work. This change therefore
also moves `unrefed_ops` from `JsRuntimeState` to `ContextState`, and
adds the lengths of the unrefed op sets for all known realms to get the
total number of unrefed ops to compare in the event loop.

This PR is a reland of #14734 after it was reverted in #16366, except
that `ContextState` and `JsRuntimeState::known_realms` were previously
relanded in #17050. Another significant difference with the original PR
is passing around an index into `JsRuntimeState::known_realms` instead
of a `v8::Global<v8::Context>` to identify the realm, because async op
queuing in fast calls cannot call into V8, and therefore cannot have
access to V8 globals. This also simplified the implementation of
`resolve_async_ops`.

Co-authored-by: Luis Malheiro <luismalheiro@gmail.com>
This commit is contained in:
Andreu Botella 2023-01-14 05:40:16 -08:00 committed by GitHub
parent b9cd19a1e8
commit 68782346d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 338 additions and 67 deletions

View file

@ -91,6 +91,7 @@ where
} }
} }
pub type RealmIdx = usize;
pub type PromiseId = i32; pub type PromiseId = i32;
pub type OpAsyncFuture = OpCall<(PromiseId, OpId, OpResult)>; pub type OpAsyncFuture = OpCall<(PromiseId, OpId, OpResult)>;
pub type OpFn = pub type OpFn =
@ -156,7 +157,7 @@ pub struct OpCtx {
pub decl: Rc<OpDecl>, pub decl: Rc<OpDecl>,
pub runtime_state: Weak<RefCell<JsRuntimeState>>, pub runtime_state: Weak<RefCell<JsRuntimeState>>,
// Index of the current realm into `JsRuntimeState::known_realms`. // Index of the current realm into `JsRuntimeState::known_realms`.
pub realm_idx: usize, pub realm_idx: RealmIdx,
} }
/// Maintains the resources and ops inside a JS runtime. /// Maintains the resources and ops inside a JS runtime.

View file

@ -76,14 +76,14 @@ fn to_v8_local_fn(
#[op(v8)] #[op(v8)]
fn op_ref_op(scope: &mut v8::HandleScope, promise_id: i32) { fn op_ref_op(scope: &mut v8::HandleScope, promise_id: i32) {
let state_rc = JsRuntime::state(scope); let context_state = JsRealm::state_from_scope(scope);
state_rc.borrow_mut().unrefed_ops.remove(&promise_id); context_state.borrow_mut().unrefed_ops.remove(&promise_id);
} }
#[op(v8)] #[op(v8)]
fn op_unref_op(scope: &mut v8::HandleScope, promise_id: i32) { fn op_unref_op(scope: &mut v8::HandleScope, promise_id: i32) {
let state_rc = JsRuntime::state(scope); let context_state = JsRealm::state_from_scope(scope);
state_rc.borrow_mut().unrefed_ops.insert(promise_id); context_state.borrow_mut().unrefed_ops.insert(promise_id);
} }
#[op(v8)] #[op(v8)]

View file

@ -48,7 +48,7 @@ use std::task::Context;
use std::task::Poll; use std::task::Poll;
use v8::OwnedIsolate; use v8::OwnedIsolate;
type PendingOpFuture = OpCall<(PromiseId, OpId, OpResult)>; type PendingOpFuture = OpCall<(RealmIdx, PromiseId, OpId, OpResult)>;
pub enum Snapshot { pub enum Snapshot {
Static(&'static [u8]), Static(&'static [u8]),
@ -150,7 +150,11 @@ pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
#[derive(Default)] #[derive(Default)]
pub(crate) struct ContextState { pub(crate) struct ContextState {
js_recv_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_build_custom_error_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) unrefed_ops: HashSet<i32>,
// We don't explicitly re-read this prop but need the slice to live alongside // We don't explicitly re-read this prop but need the slice to live alongside
// the context // the context
pub(crate) op_ctxs: Box<[OpCtx]>, pub(crate) op_ctxs: Box<[OpCtx]>,
@ -161,7 +165,6 @@ pub(crate) struct ContextState {
pub struct JsRuntimeState { pub struct JsRuntimeState {
global_realm: Option<JsRealm>, global_realm: Option<JsRealm>,
known_realms: Vec<v8::Weak<v8::Context>>, known_realms: Vec<v8::Weak<v8::Context>>,
pub(crate) js_recv_cb: Option<v8::Global<v8::Function>>,
pub(crate) js_macrotask_cbs: Vec<v8::Global<v8::Function>>, pub(crate) js_macrotask_cbs: Vec<v8::Global<v8::Function>>,
pub(crate) js_nexttick_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_promise_reject_cb: Option<v8::Global<v8::Function>>,
@ -178,7 +181,6 @@ pub struct JsRuntimeState {
pub(crate) source_map_getter: Option<Box<dyn SourceMapGetter>>, pub(crate) source_map_getter: Option<Box<dyn SourceMapGetter>>,
pub(crate) source_map_cache: SourceMapCache, pub(crate) source_map_cache: SourceMapCache,
pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>, pub(crate) pending_ops: FuturesUnordered<PendingOpFuture>,
pub(crate) unrefed_ops: HashSet<i32>,
pub(crate) have_unpolled_ops: bool, pub(crate) have_unpolled_ops: bool,
pub(crate) op_state: Rc<RefCell<OpState>>, pub(crate) op_state: Rc<RefCell<OpState>>,
pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>, pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>,
@ -388,7 +390,6 @@ impl JsRuntime {
pending_dyn_mod_evaluate: vec![], pending_dyn_mod_evaluate: vec![],
pending_mod_evaluate: None, pending_mod_evaluate: None,
dyn_module_evaluate_idle_counter: 0, dyn_module_evaluate_idle_counter: 0,
js_recv_cb: None,
js_macrotask_cbs: vec![], js_macrotask_cbs: vec![],
js_nexttick_cbs: vec![], js_nexttick_cbs: vec![],
js_promise_reject_cb: None, js_promise_reject_cb: None,
@ -398,7 +399,6 @@ impl JsRuntime {
source_map_getter: options.source_map_getter, source_map_getter: options.source_map_getter,
source_map_cache: Default::default(), source_map_cache: Default::default(),
pending_ops: FuturesUnordered::new(), pending_ops: FuturesUnordered::new(),
unrefed_ops: HashSet::new(),
shared_array_buffer_store: options.shared_array_buffer_store, shared_array_buffer_store: options.shared_array_buffer_store,
compiled_wasm_module_store: options.compiled_wasm_module_store, compiled_wasm_module_store: options.compiled_wasm_module_store,
op_state: op_state.clone(), op_state: op_state.clone(),
@ -525,8 +525,8 @@ impl JsRuntime {
global_context.open(&mut isolate).set_slot( global_context.open(&mut isolate).set_slot(
&mut isolate, &mut isolate,
Rc::new(RefCell::new(ContextState { Rc::new(RefCell::new(ContextState {
js_build_custom_error_cb: None,
op_ctxs, op_ctxs,
..Default::default()
})), })),
); );
@ -581,7 +581,8 @@ impl JsRuntime {
let realm = js_runtime.global_realm(); let realm = js_runtime.global_realm();
js_runtime.init_extension_js(&realm).unwrap(); js_runtime.init_extension_js(&realm).unwrap();
// Init callbacks (opresolve) // Init callbacks (opresolve)
js_runtime.init_cbs(); let global_realm = js_runtime.global_realm();
js_runtime.init_cbs(&global_realm);
js_runtime js_runtime
} }
@ -664,8 +665,8 @@ impl JsRuntime {
context.set_slot( context.set_slot(
scope, scope,
Rc::new(RefCell::new(ContextState { Rc::new(RefCell::new(ContextState {
js_build_custom_error_cb: None,
op_ctxs, op_ctxs,
..Default::default()
})), })),
); );
@ -679,7 +680,7 @@ impl JsRuntime {
}; };
self.init_extension_js(&realm)?; self.init_extension_js(&realm)?;
self.init_realm_cbs(&realm); self.init_cbs(&realm);
Ok(realm) Ok(realm)
} }
@ -845,37 +846,25 @@ impl JsRuntime {
} }
/// Grabs a reference to core.js' opresolve & syncOpsCache() /// Grabs a reference to core.js' opresolve & syncOpsCache()
fn init_cbs(&mut self) { fn init_cbs(&mut self, realm: &JsRealm) {
{ let (recv_cb, build_custom_error_cb) = {
let scope = &mut self.handle_scope(); let scope = &mut realm.handle_scope(self.v8_isolate());
let recv_cb = let recv_cb =
Self::eval::<v8::Function>(scope, "Deno.core.opresolve").unwrap(); Self::eval::<v8::Function>(scope, "Deno.core.opresolve").unwrap();
let recv_cb = v8::Global::new(scope, recv_cb);
// Put global handle in state
let state_rc = JsRuntime::state(scope);
let mut state = state_rc.borrow_mut();
state.js_recv_cb.replace(recv_cb);
}
// Also run init_realm_cbs for the main realm.
// TODO(@andreubotella): Merge this method back with `init_realm_cbs` when
// `js_recv_cb` is moved to ContextState.
let global_realm = self.global_realm();
self.init_realm_cbs(&global_realm);
}
fn init_realm_cbs(&mut self, realm: &JsRealm) {
let build_custom_error_cb = {
let scope = &mut realm.handle_scope(self.v8_isolate());
let build_custom_error_cb = let build_custom_error_cb =
Self::eval::<v8::Function>(scope, "Deno.core.buildCustomError") Self::eval::<v8::Function>(scope, "Deno.core.buildCustomError")
.expect("Deno.core.buildCustomError is undefined in the realm"); .expect("Deno.core.buildCustomError is undefined in the realm");
v8::Global::new(scope, build_custom_error_cb) (
v8::Global::new(scope, recv_cb),
v8::Global::new(scope, build_custom_error_cb),
)
}; };
// Put global handle in the realm's ContextState
let state = realm.state(self.v8_isolate()); // Put global handles in the realm's ContextState
let state_rc = realm.state(self.v8_isolate());
let mut state = state_rc.borrow_mut();
state.js_recv_cb.replace(recv_cb);
state state
.borrow_mut()
.js_build_custom_error_cb .js_build_custom_error_cb
.replace(build_custom_error_cb); .replace(build_custom_error_cb);
} }
@ -944,6 +933,7 @@ impl JsRuntime {
if let Some(context) = weak_context.to_global(v8_isolate) { if let Some(context) = weak_context.to_global(v8_isolate) {
let realm = JsRealm::new(context.clone()); let realm = JsRealm::new(context.clone());
let realm_state = realm.state(v8_isolate); let realm_state = realm.state(v8_isolate);
std::mem::take(&mut realm_state.borrow_mut().js_recv_cb);
std::mem::take( std::mem::take(
&mut realm_state.borrow_mut().js_build_custom_error_cb, &mut realm_state.borrow_mut().js_build_custom_error_cb,
); );
@ -952,7 +942,6 @@ impl JsRuntime {
} }
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
std::mem::take(&mut state.js_recv_cb);
std::mem::take(&mut state.js_promise_reject_cb); std::mem::take(&mut state.js_promise_reject_cb);
std::mem::take(&mut state.js_format_exception_cb); std::mem::take(&mut state.js_format_exception_cb);
std::mem::take(&mut state.js_wasm_streaming_cb); std::mem::take(&mut state.js_wasm_streaming_cb);
@ -1387,8 +1376,16 @@ impl EventLoopPendingState {
state: &mut JsRuntimeState, state: &mut JsRuntimeState,
module_map: &ModuleMap, module_map: &ModuleMap,
) -> EventLoopPendingState { ) -> EventLoopPendingState {
let mut num_unrefed_ops = 0;
for weak_context in &state.known_realms {
if let Some(context) = weak_context.to_global(isolate) {
let realm = JsRealm(context);
num_unrefed_ops += realm.state(isolate).borrow().unrefed_ops.len();
}
}
EventLoopPendingState { EventLoopPendingState {
has_pending_refed_ops: state.pending_ops.len() > state.unrefed_ops.len(), has_pending_refed_ops: state.pending_ops.len() > num_unrefed_ops,
has_pending_dyn_imports: module_map.has_pending_dynamic_imports(), has_pending_dyn_imports: module_map.has_pending_dynamic_imports(),
has_pending_dyn_module_evaluation: !state has_pending_dyn_module_evaluation: !state
.pending_dyn_mod_evaluate .pending_dyn_mod_evaluate
@ -2160,6 +2157,93 @@ impl JsRuntime {
// Send finished responses to JS // Send finished responses to JS
fn resolve_async_ops(&mut self, cx: &mut Context) -> Result<(), Error> { fn resolve_async_ops(&mut self, cx: &mut Context) -> Result<(), Error> {
// We have a specialized implementation of this method for the common case
// where there is only one realm.
let num_realms = self.state.borrow().known_realms.len();
if num_realms == 1 {
return self.resolve_single_realm_async_ops(cx);
}
// `responses_per_realm[idx]` is a vector containing the promise ID and
// response for all promises in realm `self.state.known_realms[idx]`.
let mut responses_per_realm: Vec<Vec<(PromiseId, OpResult)>> =
(0..num_realms).map(|_| vec![]).collect();
// Now handle actual ops.
{
let mut state = self.state.borrow_mut();
state.have_unpolled_ops = false;
while let Poll::Ready(Some(item)) = state.pending_ops.poll_next_unpin(cx)
{
let (realm_idx, promise_id, op_id, resp) = item;
state.op_state.borrow().tracker.track_async_completed(op_id);
responses_per_realm[realm_idx].push((promise_id, resp));
}
}
// Handle responses for each realm.
let isolate = self.v8_isolate.as_mut().unwrap();
for (realm_idx, responses) in responses_per_realm.into_iter().enumerate() {
if responses.is_empty() {
continue;
}
let realm = {
let context = self.state.borrow().known_realms[realm_idx]
.to_global(isolate)
.unwrap();
JsRealm(context)
};
let context_state_rc = realm.state(isolate);
let mut context_state = context_state_rc.borrow_mut();
let scope = &mut realm.handle_scope(isolate);
// We return async responses to JS in unbounded batches (may change),
// each batch is a flat vector of tuples:
// `[promise_id1, op_result1, promise_id2, op_result2, ...]`
// promise_id is a simple integer, op_result is an ops::OpResult
// which contains a value OR an error, encoded as a tuple.
// This batch is received in JS via the special `arguments` variable
// and then each tuple is used to resolve or reject promises
//
// This can handle 16 promises (32 / 2) futures in a single batch without heap
// allocations.
let mut args: SmallVec<[v8::Local<v8::Value>; 32]> =
SmallVec::with_capacity(responses.len() * 2);
for (promise_id, mut resp) in responses {
context_state.unrefed_ops.remove(&promise_id);
args.push(v8::Integer::new(scope, promise_id).into());
args.push(match resp.to_v8(scope) {
Ok(v) => v,
Err(e) => OpResult::Err(OpError::new(&|_| "TypeError", e.into()))
.to_v8(scope)
.unwrap(),
});
}
let js_recv_cb_handle = context_state.js_recv_cb.clone().unwrap();
let tc_scope = &mut v8::TryCatch::new(scope);
let js_recv_cb = js_recv_cb_handle.open(tc_scope);
let this = v8::undefined(tc_scope).into();
drop(context_state);
js_recv_cb.call(tc_scope, this, args.as_slice());
if let Some(exception) = tc_scope.exception() {
// TODO(@andreubotella): Returning here can cause async ops in other
// realms to never resolve.
return exception_to_err_result(tc_scope, exception, false);
}
}
Ok(())
}
fn resolve_single_realm_async_ops(
&mut self,
cx: &mut Context,
) -> Result<(), Error> {
let isolate = self.v8_isolate.as_mut().unwrap(); let isolate = self.v8_isolate.as_mut().unwrap();
let scope = &mut self let scope = &mut self
.state .state
@ -2186,10 +2270,17 @@ impl JsRuntime {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
state.have_unpolled_ops = false; state.have_unpolled_ops = false;
let realm_state_rc = state.global_realm.as_ref().unwrap().state(scope);
let mut realm_state = realm_state_rc.borrow_mut();
while let Poll::Ready(Some(item)) = state.pending_ops.poll_next_unpin(cx) while let Poll::Ready(Some(item)) = state.pending_ops.poll_next_unpin(cx)
{ {
let (promise_id, op_id, mut resp) = item; let (realm_idx, promise_id, op_id, mut resp) = item;
state.unrefed_ops.remove(&promise_id); debug_assert_eq!(
state.known_realms[realm_idx],
state.global_realm.as_ref().unwrap().context()
);
realm_state.unrefed_ops.remove(&promise_id);
state.op_state.borrow().tracker.track_async_completed(op_id); state.op_state.borrow().tracker.track_async_completed(op_id);
args.push(v8::Integer::new(scope, promise_id).into()); args.push(v8::Integer::new(scope, promise_id).into());
args.push(match resp.to_v8(scope) { args.push(match resp.to_v8(scope) {
@ -2205,7 +2296,12 @@ impl JsRuntime {
return Ok(()); return Ok(());
} }
let js_recv_cb_handle = self.state.borrow().js_recv_cb.clone().unwrap(); let js_recv_cb_handle = {
let state = self.state.borrow_mut();
let realm_state_rc = state.global_realm.as_ref().unwrap().state(scope);
let handle = realm_state_rc.borrow().js_recv_cb.clone().unwrap();
handle
};
let tc_scope = &mut v8::TryCatch::new(scope); let tc_scope = &mut v8::TryCatch::new(scope);
let js_recv_cb = js_recv_cb_handle.open(tc_scope); let js_recv_cb = js_recv_cb_handle.open(tc_scope);
let this = v8::undefined(tc_scope).into(); let this = v8::undefined(tc_scope).into();
@ -2412,7 +2508,7 @@ impl JsRealm {
#[inline] #[inline]
pub fn queue_fast_async_op( pub fn queue_fast_async_op(
ctx: &OpCtx, ctx: &OpCtx,
op: impl Future<Output = (PromiseId, OpId, OpResult)> + 'static, op: impl Future<Output = (RealmIdx, PromiseId, OpId, OpResult)> + 'static,
) { ) {
let runtime_state = match ctx.runtime_state.upgrade() { let runtime_state = match ctx.runtime_state.upgrade() {
Some(rc_state) => rc_state, Some(rc_state) => rc_state,
@ -2430,7 +2526,7 @@ pub fn queue_async_op(
ctx: &OpCtx, ctx: &OpCtx,
scope: &mut v8::HandleScope, scope: &mut v8::HandleScope,
deferred: bool, deferred: bool,
op: impl Future<Output = (PromiseId, OpId, OpResult)> + 'static, op: impl Future<Output = (RealmIdx, PromiseId, OpId, OpResult)> + 'static,
) { ) {
let runtime_state = match ctx.runtime_state.upgrade() { let runtime_state = match ctx.runtime_state.upgrade() {
Some(rc_state) => rc_state, Some(rc_state) => rc_state,
@ -2457,18 +2553,20 @@ pub fn queue_async_op(
// const p = setPromise(); // const p = setPromise();
// op.op_async(promiseId, ...); // Calls `opresolve` // op.op_async(promiseId, ...); // Calls `opresolve`
// return p; // return p;
EagerPollResult::Ready((promise_id, op_id, mut resp)) if !deferred => { EagerPollResult::Ready((_, promise_id, op_id, mut resp)) if !deferred => {
let context_state_rc = JsRealm::state_from_scope(scope);
let context_state = context_state_rc.borrow();
let args = &[ let args = &[
v8::Integer::new(scope, promise_id).into(), v8::Integer::new(scope, promise_id).into(),
resp.to_v8(scope).unwrap(), resp.to_v8(scope).unwrap(),
]; ];
let js_recv_cb_handle =
runtime_state.borrow().js_recv_cb.clone().unwrap();
ctx.state.borrow_mut().tracker.track_async_completed(op_id); ctx.state.borrow_mut().tracker.track_async_completed(op_id);
let tc_scope = &mut v8::TryCatch::new(scope); let tc_scope = &mut v8::TryCatch::new(scope);
let js_recv_cb = js_recv_cb_handle.open(tc_scope); let js_recv_cb =
context_state.js_recv_cb.as_ref().unwrap().open(tc_scope);
let this = v8::undefined(tc_scope).into(); let this = v8::undefined(tc_scope).into();
js_recv_cb.call(tc_scope, this, args); js_recv_cb.call(tc_scope, this, args);
} }
@ -2611,11 +2709,11 @@ pub mod tests {
) )
.unwrap(); .unwrap();
{ {
let realm = runtime.global_realm();
let isolate = runtime.v8_isolate(); let isolate = runtime.v8_isolate();
let state_rc = JsRuntime::state(isolate); let state_rc = JsRuntime::state(isolate);
let state = state_rc.borrow(); assert_eq!(state_rc.borrow().pending_ops.len(), 2);
assert_eq!(state.pending_ops.len(), 2); assert_eq!(realm.state(isolate).borrow().unrefed_ops.len(), 0);
assert_eq!(state.unrefed_ops.len(), 0);
} }
runtime runtime
.execute_script( .execute_script(
@ -2627,11 +2725,11 @@ pub mod tests {
) )
.unwrap(); .unwrap();
{ {
let realm = runtime.global_realm();
let isolate = runtime.v8_isolate(); let isolate = runtime.v8_isolate();
let state_rc = JsRuntime::state(isolate); let state_rc = JsRuntime::state(isolate);
let state = state_rc.borrow(); assert_eq!(state_rc.borrow().pending_ops.len(), 2);
assert_eq!(state.pending_ops.len(), 2); assert_eq!(realm.state(isolate).borrow().unrefed_ops.len(), 2);
assert_eq!(state.unrefed_ops.len(), 2);
} }
runtime runtime
.execute_script( .execute_script(
@ -2643,11 +2741,11 @@ pub mod tests {
) )
.unwrap(); .unwrap();
{ {
let realm = runtime.global_realm();
let isolate = runtime.v8_isolate(); let isolate = runtime.v8_isolate();
let state_rc = JsRuntime::state(isolate); let state_rc = JsRuntime::state(isolate);
let state = state_rc.borrow(); assert_eq!(state_rc.borrow().pending_ops.len(), 2);
assert_eq!(state.pending_ops.len(), 2); assert_eq!(realm.state(isolate).borrow().unrefed_ops.len(), 0);
assert_eq!(state.unrefed_ops.len(), 0);
} }
} }
@ -4354,6 +4452,143 @@ Deno.core.ops.op_async_serialize_object_with_numbers_as_keys({
} }
} }
#[tokio::test]
async fn js_realm_async_ops() {
// Test that returning a ZeroCopyBuf and throwing an exception from a async
// op result in objects with prototypes from the right realm. Note that we
// don't test the result of returning structs, because they will be
// serialized to objects with null prototype.
#[op]
async fn op_test(fail: bool) -> Result<ZeroCopyBuf, Error> {
if !fail {
Ok(ZeroCopyBuf::empty())
} else {
Err(crate::error::type_error("Test"))
}
}
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![Extension::builder("test_ext")
.ops(vec![op_test::decl()])
.build()],
get_error_class_fn: Some(&|error| {
crate::error::get_custom_error_class(error).unwrap()
}),
..Default::default()
});
let global_realm = runtime.global_realm();
let new_realm = runtime.create_realm().unwrap();
let mut rets = vec![];
// Test in both realms
for realm in [global_realm, new_realm].into_iter() {
let ret = realm
.execute_script(
runtime.v8_isolate(),
"",
r#"
Deno.core.initializeAsyncOps();
(async function () {
const buf = await Deno.core.ops.op_test(false);
let err;
try {
await Deno.core.ops.op_test(true);
} catch(e) {
err = e;
}
return buf instanceof Uint8Array && buf.byteLength === 0 &&
err instanceof TypeError && err.message === "Test" ;
})();
"#,
)
.unwrap();
rets.push((realm, ret));
}
runtime.run_event_loop(false).await.unwrap();
for ret in rets {
let scope = &mut ret.0.handle_scope(runtime.v8_isolate());
let value = v8::Local::new(scope, ret.1);
let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
let result = promise.result(scope);
assert!(result.is_boolean() && result.is_true());
}
}
#[tokio::test]
async fn js_realm_ref_unref_ops() {
run_in_task(|cx| {
// Never resolves.
#[op]
async fn op_pending() {
futures::future::pending().await
}
let mut runtime = JsRuntime::new(RuntimeOptions {
extensions: vec![Extension::builder("test_ext")
.ops(vec![op_pending::decl()])
.build()],
..Default::default()
});
let main_realm = runtime.global_realm();
let other_realm = runtime.create_realm().unwrap();
main_realm
.execute_script(
runtime.v8_isolate(),
"",
r#"
Deno.core.initializeAsyncOps();
var promise = Deno.core.ops.op_pending();
"#,
)
.unwrap();
other_realm
.execute_script(
runtime.v8_isolate(),
"",
r#"
Deno.core.initializeAsyncOps();
var promise = Deno.core.ops.op_pending();
"#,
)
.unwrap();
assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
main_realm
.execute_script(
runtime.v8_isolate(),
"",
r#"
let promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
Deno.core.unrefOp(promise[promiseIdSymbol]);
"#,
)
.unwrap();
assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending));
other_realm
.execute_script(
runtime.v8_isolate(),
"",
r#"
let promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId");
Deno.core.unrefOp(promise[promiseIdSymbol]);
"#,
)
.unwrap();
assert!(matches!(
runtime.poll_event_loop(cx, false),
Poll::Ready(Ok(()))
));
});
}
#[test] #[test]
fn test_array_by_copy() { fn test_array_by_copy() {
// Verify that "array by copy" proposal is enabled (https://github.com/tc39/proposal-change-array-by-copy) // Verify that "array by copy" proposal is enabled (https://github.com/tc39/proposal-change-array-by-copy)

View file

@ -265,10 +265,12 @@ pub(crate) fn generate(
let queue_future = if optimizer.returns_result { let queue_future = if optimizer.returns_result {
q!({ q!({
let realm_idx = __ctx.realm_idx;
let __get_class = __state.get_error_class_fn; let __get_class = __state.get_error_class_fn;
let result = _ops::queue_fast_async_op(__ctx, async move { let result = _ops::queue_fast_async_op(__ctx, async move {
let result = result.await; let result = result.await;
( (
realm_idx,
__promise_id, __promise_id,
__op_id, __op_id,
_ops::to_op_result(__get_class, result), _ops::to_op_result(__get_class, result),
@ -277,9 +279,15 @@ pub(crate) fn generate(
}) })
} else { } else {
q!({ q!({
let realm_idx = __ctx.realm_idx;
let result = _ops::queue_fast_async_op(__ctx, async move { let result = _ops::queue_fast_async_op(__ctx, async move {
let result = result.await; let result = result.await;
(__promise_id, __op_id, _ops::OpResult::Ok(result.into())) (
realm_idx,
__promise_id,
__op_id,
_ops::OpResult::Ok(result.into()),
)
}); });
}) })
}; };

View file

@ -221,7 +221,7 @@ fn codegen_v8_async(
quote! { quote! {
let result = match result { let result = match result {
Ok(fut) => fut.await, Ok(fut) => fut.await,
Err(e) => return (promise_id, op_id, #core::_ops::to_op_result::<()>(get_class, Err(e))), Err(e) => return (realm_idx, promise_id, op_id, #core::_ops::to_op_result::<()>(get_class, Err(e))),
}; };
} }
} else { } else {
@ -240,6 +240,7 @@ fn codegen_v8_async(
as *const #core::_ops::OpCtx) as *const #core::_ops::OpCtx)
}; };
let op_id = ctx.id; let op_id = ctx.id;
let realm_idx = ctx.realm_idx;
let promise_id = args.get(0); let promise_id = args.get(0);
let promise_id = #core::v8::Local::<#core::v8::Integer>::try_from(promise_id) let promise_id = #core::v8::Local::<#core::v8::Integer>::try_from(promise_id)
@ -267,7 +268,7 @@ fn codegen_v8_async(
#core::_ops::queue_async_op(ctx, scope, #deferred, async move { #core::_ops::queue_async_op(ctx, scope, #deferred, async move {
let result = #result_fut let result = #result_fut
#result_wrapper #result_wrapper
(promise_id, op_id, #core::_ops::to_op_result(get_class, result)) (realm_idx, promise_id, op_id, #core::_ops::to_op_result(get_class, result))
}); });
}, },
argc, argc,

View file

@ -43,6 +43,7 @@ impl op_void_async {
as *const deno_core::_ops::OpCtx) as *const deno_core::_ops::OpCtx)
}; };
let op_id = ctx.id; let op_id = ctx.id;
let realm_idx = ctx.realm_idx;
let promise_id = args.get(0); let promise_id = args.get(0);
let promise_id = deno_core::v8::Local::< let promise_id = deno_core::v8::Local::<
deno_core::v8::Integer, deno_core::v8::Integer,
@ -71,7 +72,12 @@ impl op_void_async {
async move { async move {
let result = Self::call().await; let result = Self::call().await;
let result = Ok(result); let result = Ok(result);
(promise_id, op_id, deno_core::_ops::to_op_result(get_class, result)) (
realm_idx,
promise_id,
op_id,
deno_core::_ops::to_op_result(get_class, result),
)
}, },
); );
} }
@ -111,11 +117,12 @@ fn op_void_async_fast_fn<'scope>(
let __op_id = __ctx.id; let __op_id = __ctx.id;
let __state = ::std::cell::RefCell::borrow(&__ctx.state); let __state = ::std::cell::RefCell::borrow(&__ctx.state);
__state.tracker.track_async(__op_id); __state.tracker.track_async(__op_id);
let realm_idx = __ctx.realm_idx;
let result = _ops::queue_fast_async_op( let result = _ops::queue_fast_async_op(
__ctx, __ctx,
async move { async move {
let result = result.await; let result = result.await;
(__promise_id, __op_id, _ops::OpResult::Ok(result.into())) (realm_idx, __promise_id, __op_id, _ops::OpResult::Ok(result.into()))
}, },
); );
result result

View file

@ -47,6 +47,7 @@ impl op_read {
as *const deno_core::_ops::OpCtx) as *const deno_core::_ops::OpCtx)
}; };
let op_id = ctx.id; let op_id = ctx.id;
let realm_idx = ctx.realm_idx;
let promise_id = args.get(0); let promise_id = args.get(0);
let promise_id = deno_core::v8::Local::< let promise_id = deno_core::v8::Local::<
deno_core::v8::Integer, deno_core::v8::Integer,
@ -130,7 +131,12 @@ impl op_read {
false, false,
async move { async move {
let result = Self::call(ctx.state.clone(), arg_0, arg_1).await; let result = Self::call(ctx.state.clone(), arg_0, arg_1).await;
(promise_id, op_id, deno_core::_ops::to_op_result(get_class, result)) (
realm_idx,
promise_id,
op_id,
deno_core::_ops::to_op_result(get_class, result),
)
}, },
); );
} }
@ -179,12 +185,13 @@ fn op_read_fast_fn<'scope>(
let __op_id = __ctx.id; let __op_id = __ctx.id;
let __state = ::std::cell::RefCell::borrow(&__ctx.state); let __state = ::std::cell::RefCell::borrow(&__ctx.state);
__state.tracker.track_async(__op_id); __state.tracker.track_async(__op_id);
let realm_idx = __ctx.realm_idx;
let __get_class = __state.get_error_class_fn; let __get_class = __state.get_error_class_fn;
let result = _ops::queue_fast_async_op( let result = _ops::queue_fast_async_op(
__ctx, __ctx,
async move { async move {
let result = result.await; let result = result.await;
(__promise_id, __op_id, _ops::to_op_result(__get_class, result)) (realm_idx, __promise_id, __op_id, _ops::to_op_result(__get_class, result))
}, },
); );
} }

View file

@ -43,6 +43,7 @@ impl send_stdin {
as *const deno_core::_ops::OpCtx) as *const deno_core::_ops::OpCtx)
}; };
let op_id = ctx.id; let op_id = ctx.id;
let realm_idx = ctx.realm_idx;
let promise_id = args.get(0); let promise_id = args.get(0);
let promise_id = deno_core::v8::Local::< let promise_id = deno_core::v8::Local::<
deno_core::v8::Integer, deno_core::v8::Integer,
@ -85,7 +86,12 @@ impl send_stdin {
arg_0, arg_0,
) )
.await; .await;
(promise_id, op_id, deno_core::_ops::to_op_result(get_class, result)) (
realm_idx,
promise_id,
op_id,
deno_core::_ops::to_op_result(get_class, result),
)
}, },
); );
} }

View file

@ -41,6 +41,7 @@ impl send_stdin {
as *const deno_core::_ops::OpCtx) as *const deno_core::_ops::OpCtx)
}; };
let op_id = ctx.id; let op_id = ctx.id;
let realm_idx = ctx.realm_idx;
let promise_id = args.get(0); let promise_id = args.get(0);
let promise_id = deno_core::v8::Local::< let promise_id = deno_core::v8::Local::<
deno_core::v8::Integer, deno_core::v8::Integer,
@ -83,7 +84,12 @@ impl send_stdin {
arg_0, arg_0,
) )
.await; .await;
(promise_id, op_id, deno_core::_ops::to_op_result(get_class, result)) (
realm_idx,
promise_id,
op_id,
deno_core::_ops::to_op_result(get_class, result),
)
}, },
); );
} }