mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
refactor(core): limit number of boundary crossings between Rust and V8 (#18652)
This commit refactors "deno_core" to do fewer boundary crossings from Rust to V8. In other words we are now calling V8 from Rust fewer times. This is done by merging 3 distinct callbacks into a single one. Instead of having "op resolve" callback, "next tick" callback and "macrotask queue" callback, we now have only "Deno.core.eventLoopTick" callback, which is responsible for doing the same actions previous 3 callbacks. On each of the event loop we were doing at least 2 boundary crosses (timers macrotask queue callback and unhandled promise rejection callback) and up to 4 crosses if there were op response and next tick callbacks coming from Node.js compatibility layer. Now this is all done in a single callback. Closes https://github.com/denoland/deno/issues/18620
This commit is contained in:
parent
1a7991d7a0
commit
774944b250
18 changed files with 126 additions and 173 deletions
|
@ -6,3 +6,4 @@ error: Uncaught (in worker "") Error
|
|||
at handleTimerMacrotask (ext:deno_web/02_timers.js:[WILDCARD])
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl (ext:runtime/11_workers.js:[WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[WILDCARD]error: Uncaught (in worker "") Module not found "file:///[WILDCARD]/workers/doesnt_exist.js".
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at Worker.#pollControl[WILDCARD]
|
||||
|
|
|
@ -2,3 +2,4 @@ error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts"
|
|||
at blob:null/[WILDCARD]:1:8
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -2,3 +2,4 @@ error: Uncaught (in worker "") Requires net access to "example.com", run again w
|
|||
at blob:null/[WILDCARD]:1:8
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -2,3 +2,4 @@ error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts"
|
|||
at data:application/javascript;base64,[WILDCARD]:1:8
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -2,3 +2,4 @@ error: Uncaught (in worker "") Requires net access to "example.com", run again w
|
|||
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -4,3 +4,4 @@ await import("https://example.com/some/file.ts");
|
|||
at async http://localhost:4545/workers/dynamic_remote.ts:2:1
|
||||
[WILDCARD]error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -2,3 +2,4 @@ error: Uncaught (in worker "") Requires net access to "example.com", run again w
|
|||
at http://localhost:4545/workers/static_remote.ts:2:8
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -5,3 +5,4 @@ error: Uncaught (in worker "foo") (in promise) Error: bar
|
|||
at [WILDCARD]/async_error.ts:[WILDCARD]
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
at [WILDCARD]
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -5,3 +5,4 @@ error: Uncaught (in worker "foo") Error: bar
|
|||
at [WILDCARD]
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -5,5 +5,7 @@
|
|||
at [WILDCARD]/workers/error.ts:[WILDCARD]
|
||||
error: Uncaught (in worker "baz") (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
error: Uncaught (in promise) Error: Unhandled error in child worker.
|
||||
at Worker.#pollControl ([WILDCARD])
|
||||
at eventLoopTick (ext:core/01_core.js:[WILDCARD])
|
||||
|
|
|
@ -133,13 +133,48 @@
|
|||
return promiseRing[idx] != NO_PROMISE;
|
||||
}
|
||||
|
||||
function opresolve() {
|
||||
for (let i = 0; i < arguments.length; i += 2) {
|
||||
const macrotaskCallbacks = [];
|
||||
const nextTickCallbacks = [];
|
||||
|
||||
function setMacrotaskCallback(cb) {
|
||||
ArrayPrototypePush(macrotaskCallbacks, cb);
|
||||
}
|
||||
|
||||
function setNextTickCallback(cb) {
|
||||
ArrayPrototypePush(nextTickCallbacks, cb);
|
||||
}
|
||||
|
||||
// This function has variable number of arguments. The last argument describes
|
||||
// if there's a "next tick" scheduled by the Node.js compat layer. Arguments
|
||||
// before last are alternating integers and any values that describe the
|
||||
// responses of async ops.
|
||||
function eventLoopTick() {
|
||||
// First respond to all pending ops.
|
||||
for (let i = 0; i < arguments.length - 1; i += 2) {
|
||||
const promiseId = arguments[i];
|
||||
const res = arguments[i + 1];
|
||||
const promise = getPromise(promiseId);
|
||||
promise.resolve(res);
|
||||
}
|
||||
// Drain nextTick queue if there's a tick scheduled.
|
||||
if (arguments[arguments.length - 1]) {
|
||||
for (let i = 0; i < nextTickCallbacks.length; i++) {
|
||||
nextTickCallbacks[i]();
|
||||
}
|
||||
} else {
|
||||
ops.op_run_microtasks();
|
||||
}
|
||||
// Finally drain macrotask queue.
|
||||
for (let i = 0; i < macrotaskCallbacks.length; i++) {
|
||||
const cb = macrotaskCallbacks[i];
|
||||
while (true) {
|
||||
const res = cb();
|
||||
ops.op_run_microtasks();
|
||||
if (res === true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerErrorClass(className, errorClass) {
|
||||
|
@ -406,7 +441,7 @@
|
|||
registerErrorBuilder,
|
||||
registerErrorClass,
|
||||
buildCustomError,
|
||||
opresolve,
|
||||
eventLoopTick,
|
||||
BadResource,
|
||||
BadResourcePrototype,
|
||||
Interrupted,
|
||||
|
@ -428,8 +463,8 @@
|
|||
writeSync: (rid, buffer) => ops.op_write_sync(rid, buffer),
|
||||
shutdown: opAsync.bind(null, "op_shutdown"),
|
||||
print: (msg, isErr) => ops.op_print(msg, isErr),
|
||||
setMacrotaskCallback: (fn) => ops.op_set_macrotask_callback(fn),
|
||||
setNextTickCallback: (fn) => ops.op_set_next_tick_callback(fn),
|
||||
setMacrotaskCallback,
|
||||
setNextTickCallback,
|
||||
runMicrotasks: () => ops.op_run_microtasks(),
|
||||
hasTickScheduled: () => ops.op_has_tick_scheduled(),
|
||||
setHasTickScheduled: (bool) => ops.op_set_has_tick_scheduled(bool),
|
||||
|
|
|
@ -348,6 +348,7 @@ pub struct Extension {
|
|||
name: &'static str,
|
||||
deps: Option<&'static [&'static str]>,
|
||||
force_op_registration: bool,
|
||||
pub(crate) is_core: bool,
|
||||
}
|
||||
|
||||
// Note: this used to be a trait, but we "downgraded" it to a single concrete type
|
||||
|
@ -472,6 +473,7 @@ pub struct ExtensionBuilder {
|
|||
name: &'static str,
|
||||
deps: &'static [&'static str],
|
||||
force_op_registration: bool,
|
||||
is_core: bool,
|
||||
}
|
||||
|
||||
impl ExtensionBuilder {
|
||||
|
@ -547,6 +549,7 @@ impl ExtensionBuilder {
|
|||
name: self.name,
|
||||
force_op_registration: self.force_op_registration,
|
||||
deps,
|
||||
is_core: self.is_core,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,8 +571,15 @@ impl ExtensionBuilder {
|
|||
name: self.name,
|
||||
deps,
|
||||
force_op_registration: self.force_op_registration,
|
||||
is_core: self.is_core,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(crate) fn deno_core(&mut self) -> &mut Self {
|
||||
self.is_core = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Helps embed JS files in an extension. Returns a vector of
|
||||
|
|
|
@ -43,8 +43,6 @@ crate::extension!(
|
|||
op_str_byte_length,
|
||||
ops_builtin_v8::op_ref_op,
|
||||
ops_builtin_v8::op_unref_op,
|
||||
ops_builtin_v8::op_set_macrotask_callback,
|
||||
ops_builtin_v8::op_set_next_tick_callback,
|
||||
ops_builtin_v8::op_set_promise_reject_callback,
|
||||
ops_builtin_v8::op_run_microtasks,
|
||||
ops_builtin_v8::op_has_tick_scheduled,
|
||||
|
@ -74,6 +72,9 @@ crate::extension!(
|
|||
ops_builtin_v8::op_arraybuffer_was_detached,
|
||||
],
|
||||
js = ["00_primordials.js", "01_core.js", "02_error.js"],
|
||||
customizer = |ext: &mut crate::ExtensionBuilder| {
|
||||
ext.deno_core();
|
||||
}
|
||||
);
|
||||
|
||||
/// Return map of resources with id as key
|
||||
|
|
|
@ -49,28 +49,6 @@ fn op_unref_op(scope: &mut v8::HandleScope, promise_id: i32) {
|
|||
context_state.borrow_mut().unrefed_ops.insert(promise_id);
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_macrotask_callback(
|
||||
scope: &mut v8::HandleScope,
|
||||
cb: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
state_rc.borrow_mut().js_macrotask_cbs.push(cb);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_next_tick_callback(
|
||||
scope: &mut v8::HandleScope,
|
||||
cb: serde_v8::Value,
|
||||
) -> Result<(), Error> {
|
||||
let cb = to_v8_fn(scope, cb)?;
|
||||
let state_rc = JsRuntime::state(scope);
|
||||
state_rc.borrow_mut().js_nexttick_cbs.push(cb);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op(v8)]
|
||||
fn op_set_promise_reject_callback<'a>(
|
||||
scope: &mut v8::HandleScope<'a>,
|
||||
|
|
|
@ -15,7 +15,7 @@ use v8::Local;
|
|||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ContextState {
|
||||
pub(crate) js_recv_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) js_event_loop_tick_cb: Option<v8::Global<v8::Function>>,
|
||||
pub(crate) js_build_custom_error_cb: Option<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>>,
|
||||
|
|
201
core/runtime.rs
201
core/runtime.rs
|
@ -158,8 +158,6 @@ pub type CompiledWasmModuleStore = CrossIsolateStore<v8::CompiledWasmModule>;
|
|||
pub struct JsRuntimeState {
|
||||
global_realm: Option<JsRealm>,
|
||||
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) has_tick_scheduled: bool,
|
||||
pub(crate) pending_dyn_mod_evaluate: Vec<DynImportModEvaluate>,
|
||||
pub(crate) pending_mod_evaluate: Option<ModEvaluate>,
|
||||
|
@ -342,8 +340,6 @@ impl JsRuntime {
|
|||
pending_dyn_mod_evaluate: vec![],
|
||||
pending_mod_evaluate: None,
|
||||
dyn_module_evaluate_idle_counter: 0,
|
||||
js_macrotask_cbs: vec![],
|
||||
js_nexttick_cbs: vec![],
|
||||
has_tick_scheduled: false,
|
||||
source_map_getter: options.source_map_getter,
|
||||
source_map_cache: Default::default(),
|
||||
|
@ -541,9 +537,6 @@ impl JsRuntime {
|
|||
js_runtime.init_extension_ops().unwrap();
|
||||
let realm = js_runtime.global_realm();
|
||||
js_runtime.init_extension_js(&realm).unwrap();
|
||||
// Init callbacks (opresolve)
|
||||
let global_realm = js_runtime.global_realm();
|
||||
js_runtime.init_cbs(&global_realm);
|
||||
|
||||
js_runtime
|
||||
}
|
||||
|
@ -645,7 +638,6 @@ impl JsRuntime {
|
|||
};
|
||||
|
||||
self.init_extension_js(&realm)?;
|
||||
self.init_cbs(&realm);
|
||||
Ok(realm)
|
||||
}
|
||||
|
||||
|
@ -741,6 +733,12 @@ impl JsRuntime {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): this not great that we need to have this conditional
|
||||
// here, but I haven't found a better way to do it yet.
|
||||
if ext.is_core {
|
||||
self.init_cbs(realm);
|
||||
}
|
||||
}
|
||||
// Restore extensions
|
||||
self.extensions = extensions;
|
||||
|
@ -825,9 +823,9 @@ impl JsRuntime {
|
|||
scope.escape(v).try_into().ok()
|
||||
}
|
||||
|
||||
/// Grabs a reference to core.js' opresolve & syncOpsCache()
|
||||
/// Grabs a reference to core.js' eventLoopTick & buildCustomError
|
||||
fn init_cbs(&mut self, realm: &JsRealm) {
|
||||
let (recv_cb, build_custom_error_cb) = {
|
||||
let (event_loop_tick_cb, build_custom_error_cb) = {
|
||||
let scope = &mut realm.handle_scope(self.v8_isolate());
|
||||
let context = realm.context();
|
||||
let context_local = v8::Local::new(scope, context);
|
||||
|
@ -836,8 +834,9 @@ impl JsRuntime {
|
|||
v8::String::new_external_onebyte_static(scope, b"Deno").unwrap();
|
||||
let core_str =
|
||||
v8::String::new_external_onebyte_static(scope, b"core").unwrap();
|
||||
let opresolve_str =
|
||||
v8::String::new_external_onebyte_static(scope, b"opresolve").unwrap();
|
||||
let event_loop_tick_str =
|
||||
v8::String::new_external_onebyte_static(scope, b"eventLoopTick")
|
||||
.unwrap();
|
||||
let build_custom_error_str =
|
||||
v8::String::new_external_onebyte_static(scope, b"buildCustomError")
|
||||
.unwrap();
|
||||
|
@ -853,8 +852,8 @@ impl JsRuntime {
|
|||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let recv_cb: v8::Local<v8::Function> = core_obj
|
||||
.get(scope, opresolve_str.into())
|
||||
let event_loop_tick_cb: v8::Local<v8::Function> = core_obj
|
||||
.get(scope, event_loop_tick_str.into())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
@ -864,7 +863,7 @@ impl JsRuntime {
|
|||
.try_into()
|
||||
.unwrap();
|
||||
(
|
||||
v8::Global::new(scope, recv_cb),
|
||||
v8::Global::new(scope, event_loop_tick_cb),
|
||||
v8::Global::new(scope, build_custom_error_cb),
|
||||
)
|
||||
};
|
||||
|
@ -872,7 +871,7 @@ impl JsRuntime {
|
|||
// 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.js_event_loop_tick_cb.replace(event_loop_tick_cb);
|
||||
state
|
||||
.js_build_custom_error_cb
|
||||
.replace(build_custom_error_cb);
|
||||
|
@ -983,7 +982,7 @@ impl JsRuntime {
|
|||
let realm = JsRealm::new(context.clone());
|
||||
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_event_loop_tick_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);
|
||||
|
@ -993,8 +992,6 @@ impl JsRuntime {
|
|||
}
|
||||
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.js_macrotask_cbs.clear();
|
||||
state.js_nexttick_cbs.clear();
|
||||
state.known_realms.clear();
|
||||
}
|
||||
|
||||
|
@ -1218,13 +1215,11 @@ impl JsRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
// Ops
|
||||
self.resolve_async_ops(cx)?;
|
||||
// Run all next tick callbacks and macrotasks callbacks and only then
|
||||
// check for any promise exceptions (`unhandledrejection` handlers are
|
||||
// run in macrotasks callbacks so we need to let them run first).
|
||||
self.drain_nexttick()?;
|
||||
self.drain_macrotasks()?;
|
||||
// Resolve async ops, run all next tick callbacks and macrotasks callbacks
|
||||
// and only then check for any promise exceptions (`unhandledrejection`
|
||||
// handlers are run in macrotasks callbacks so we need to let them run
|
||||
// first).
|
||||
self.do_js_event_loop_tick(cx)?;
|
||||
self.check_promise_rejections()?;
|
||||
|
||||
// Event loop middlewares
|
||||
|
@ -2191,13 +2186,13 @@ impl JsRuntime {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Send finished responses to JS
|
||||
fn resolve_async_ops(&mut self, cx: &mut Context) -> Result<(), Error> {
|
||||
// Polls pending ops and then runs `Deno.core.eventLoopTick` callback.
|
||||
fn do_js_event_loop_tick(&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);
|
||||
return self.do_single_realm_js_event_loop_tick(cx);
|
||||
}
|
||||
|
||||
// `responses_per_realm[idx]` is a vector containing the promise ID and
|
||||
|
@ -2221,10 +2216,6 @@ impl JsRuntime {
|
|||
// 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)
|
||||
|
@ -2243,10 +2234,10 @@ impl JsRuntime {
|
|||
// 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
|
||||
// This can handle 15 promises futures in a single batch without heap
|
||||
// allocations.
|
||||
let mut args: SmallVec<[v8::Local<v8::Value>; 32]> =
|
||||
SmallVec::with_capacity(responses.len() * 2);
|
||||
SmallVec::with_capacity(responses.len() * 2 + 2);
|
||||
|
||||
for (promise_id, mut resp) in responses {
|
||||
context_state.unrefed_ops.remove(&promise_id);
|
||||
|
@ -2259,24 +2250,33 @@ impl JsRuntime {
|
|||
});
|
||||
}
|
||||
|
||||
let js_recv_cb_handle = context_state.js_recv_cb.clone().unwrap();
|
||||
let has_tick_scheduled =
|
||||
v8::Boolean::new(scope, self.state.borrow().has_tick_scheduled);
|
||||
args.push(has_tick_scheduled.into());
|
||||
|
||||
let js_event_loop_tick_cb_handle =
|
||||
context_state.js_event_loop_tick_cb.clone().unwrap();
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let js_recv_cb = js_recv_cb_handle.open(tc_scope);
|
||||
let js_event_loop_tick_cb = js_event_loop_tick_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());
|
||||
js_event_loop_tick_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);
|
||||
}
|
||||
|
||||
if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_single_realm_async_ops(
|
||||
fn do_single_realm_js_event_loop_tick(
|
||||
&mut self,
|
||||
cx: &mut Context,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -2297,7 +2297,7 @@ impl JsRuntime {
|
|||
// 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
|
||||
// This can handle 15 promises futures in a single batch without heap
|
||||
// allocations.
|
||||
let mut args: SmallVec<[v8::Local<v8::Value>; 32]> = SmallVec::new();
|
||||
|
||||
|
@ -2328,94 +2328,33 @@ impl JsRuntime {
|
|||
}
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let has_tick_scheduled =
|
||||
v8::Boolean::new(scope, self.state.borrow().has_tick_scheduled);
|
||||
args.push(has_tick_scheduled.into());
|
||||
|
||||
let js_recv_cb_handle = {
|
||||
let js_event_loop_tick_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();
|
||||
let handle = realm_state_rc
|
||||
.borrow()
|
||||
.js_event_loop_tick_cb
|
||||
.clone()
|
||||
.unwrap();
|
||||
handle
|
||||
};
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let js_recv_cb = js_recv_cb_handle.open(tc_scope);
|
||||
let js_event_loop_tick_cb = js_event_loop_tick_cb_handle.open(tc_scope);
|
||||
let this = v8::undefined(tc_scope).into();
|
||||
js_recv_cb.call(tc_scope, this, args.as_slice());
|
||||
js_event_loop_tick_cb.call(tc_scope, this, args.as_slice());
|
||||
|
||||
match tc_scope.exception() {
|
||||
None => Ok(()),
|
||||
Some(exception) => exception_to_err_result(tc_scope, exception, false),
|
||||
if let Some(exception) = tc_scope.exception() {
|
||||
return exception_to_err_result(tc_scope, exception, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_macrotasks(&mut self) -> Result<(), Error> {
|
||||
if self.state.borrow().js_macrotask_cbs.is_empty() {
|
||||
if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let js_macrotask_cb_handles = self.state.borrow().js_macrotask_cbs.clone();
|
||||
let scope = &mut self.handle_scope();
|
||||
|
||||
for js_macrotask_cb_handle in js_macrotask_cb_handles {
|
||||
let js_macrotask_cb = js_macrotask_cb_handle.open(scope);
|
||||
|
||||
// Repeatedly invoke macrotask callback until it returns true (done),
|
||||
// such that ready microtasks would be automatically run before
|
||||
// next macrotask is processed.
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let this = v8::undefined(tc_scope).into();
|
||||
loop {
|
||||
let is_done = js_macrotask_cb.call(tc_scope, this, &[]);
|
||||
|
||||
if let Some(exception) = tc_scope.exception() {
|
||||
return exception_to_err_result(tc_scope, exception, false);
|
||||
}
|
||||
|
||||
if tc_scope.has_terminated() || tc_scope.is_execution_terminating() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_done = is_done.unwrap();
|
||||
if is_done.is_true() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn drain_nexttick(&mut self) -> Result<(), Error> {
|
||||
if self.state.borrow().js_nexttick_cbs.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let state = self.state.clone();
|
||||
if !state.borrow().has_tick_scheduled {
|
||||
let scope = &mut self.handle_scope();
|
||||
scope.perform_microtask_checkpoint();
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): Node also checks for absence of "rejection_to_warn"
|
||||
if !state.borrow().has_tick_scheduled {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let js_nexttick_cb_handles = state.borrow().js_nexttick_cbs.clone();
|
||||
let scope = &mut self.handle_scope();
|
||||
|
||||
for js_nexttick_cb_handle in js_nexttick_cb_handles {
|
||||
let js_nexttick_cb = js_nexttick_cb_handle.open(scope);
|
||||
|
||||
let tc_scope = &mut v8::TryCatch::new(scope);
|
||||
let this = v8::undefined(tc_scope).into();
|
||||
js_nexttick_cb.call(tc_scope, this, &[]);
|
||||
if let Some(exception) = tc_scope.exception() {
|
||||
return exception_to_err_result(tc_scope, exception, false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -3088,7 +3027,7 @@ pub mod tests {
|
|||
.execute_script_static(
|
||||
"a.js",
|
||||
r#"
|
||||
Deno.core.ops.op_set_macrotask_callback(() => {
|
||||
Deno.core.setMacrotaskCallback(() => {
|
||||
return true;
|
||||
});
|
||||
Deno.core.ops.op_set_format_exception_callback(()=> {
|
||||
|
@ -3882,11 +3821,11 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
|
|||
|
||||
(async function () {
|
||||
const results = [];
|
||||
Deno.core.ops.op_set_macrotask_callback(() => {
|
||||
Deno.core.setMacrotaskCallback(() => {
|
||||
results.push("macrotask");
|
||||
return true;
|
||||
});
|
||||
Deno.core.ops.op_set_next_tick_callback(() => {
|
||||
Deno.core.setNextTickCallback(() => {
|
||||
results.push("nextTick");
|
||||
Deno.core.ops.op_set_has_tick_scheduled(false);
|
||||
});
|
||||
|
@ -3905,28 +3844,6 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
|
|||
runtime.run_event_loop(false).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_set_macrotask_callback_set_next_tick_callback_multiple() {
|
||||
let mut runtime = JsRuntime::new(Default::default());
|
||||
|
||||
runtime
|
||||
.execute_script_static(
|
||||
"multiple_macrotasks_and_nextticks.js",
|
||||
r#"
|
||||
Deno.core.ops.op_set_macrotask_callback(() => { return true; });
|
||||
Deno.core.ops.op_set_macrotask_callback(() => { return true; });
|
||||
Deno.core.ops.op_set_next_tick_callback(() => {});
|
||||
Deno.core.ops.op_set_next_tick_callback(() => {});
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let isolate = runtime.v8_isolate();
|
||||
let state_rc = JsRuntime::state(isolate);
|
||||
let state = state_rc.borrow();
|
||||
assert_eq!(state.js_macrotask_cbs.len(), 2);
|
||||
assert_eq!(state.js_nexttick_cbs.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_tick_scheduled() {
|
||||
use futures::task::ArcWake;
|
||||
|
@ -3956,11 +3873,11 @@ Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", {
|
|||
.execute_script_static(
|
||||
"has_tick_scheduled.js",
|
||||
r#"
|
||||
Deno.core.ops.op_set_macrotask_callback(() => {
|
||||
Deno.core.setMacrotaskCallback(() => {
|
||||
Deno.core.ops.op_macrotask();
|
||||
return true; // We're done.
|
||||
});
|
||||
Deno.core.ops.op_set_next_tick_callback(() => Deno.core.ops.op_next_tick());
|
||||
Deno.core.setNextTickCallback(() => Deno.core.ops.op_next_tick());
|
||||
Deno.core.ops.op_set_has_tick_scheduled(true);
|
||||
"#,
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue