mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 23:34:47 -05:00
perf(core): immediately schedule another tick if there are unpolled ops (#18611)
Currently we are "waking up" the runtime if at the end of the event loop tick there are ops that haven't been polled. Waking up incurs a syscall and it appears we can do another tick of the event loop, without going through the "wake up" machinery.
This commit is contained in:
parent
cb2ca234bb
commit
4317055a3e
1 changed files with 139 additions and 133 deletions
272
core/runtime.rs
272
core/runtime.rs
|
@ -1182,155 +1182,161 @@ impl JsRuntime {
|
||||||
state.waker.register(cx.waker());
|
state.waker.register(cx.waker());
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_inspector {
|
loop {
|
||||||
// We poll the inspector first.
|
|
||||||
let _ = self.inspector().borrow_mut().poll_unpin(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pump_v8_message_loop()?;
|
|
||||||
|
|
||||||
// Dynamic module loading - ie. modules loaded using "import()"
|
|
||||||
{
|
|
||||||
// Run in a loop so that dynamic imports that only depend on another
|
|
||||||
// dynamic import can be resolved in this event loop iteration.
|
|
||||||
//
|
|
||||||
// For example, a dynamically imported module like the following can be
|
|
||||||
// immediately resolved after `dependency.ts` is fully evaluated, but it
|
|
||||||
// wouldn't if not for this loop.
|
|
||||||
//
|
|
||||||
// await delay(1000);
|
|
||||||
// await import("./dependency.ts");
|
|
||||||
// console.log("test")
|
|
||||||
//
|
|
||||||
loop {
|
|
||||||
let poll_imports = self.prepare_dyn_imports(cx)?;
|
|
||||||
assert!(poll_imports.is_ready());
|
|
||||||
|
|
||||||
let poll_imports = self.poll_dyn_imports(cx)?;
|
|
||||||
assert!(poll_imports.is_ready());
|
|
||||||
|
|
||||||
if !self.evaluate_dyn_imports() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
let mut maybe_scheduling = false;
|
|
||||||
{
|
|
||||||
let op_state = self.state.borrow().op_state.clone();
|
|
||||||
for f in &self.event_loop_middlewares {
|
|
||||||
if f(op_state.clone(), cx) {
|
|
||||||
maybe_scheduling = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top level module
|
|
||||||
self.evaluate_pending_module();
|
|
||||||
|
|
||||||
let pending_state = self.event_loop_pending_state();
|
|
||||||
if !pending_state.is_pending() && !maybe_scheduling {
|
|
||||||
if has_inspector {
|
if has_inspector {
|
||||||
let inspector = self.inspector();
|
// We poll the inspector first.
|
||||||
let has_active_sessions = inspector.borrow().has_active_sessions();
|
let _ = self.inspector().borrow_mut().poll_unpin(cx);
|
||||||
let has_blocking_sessions = inspector.borrow().has_blocking_sessions();
|
}
|
||||||
|
|
||||||
if wait_for_inspector && has_active_sessions {
|
self.pump_v8_message_loop()?;
|
||||||
// If there are no blocking sessions (eg. REPL) we can now notify
|
|
||||||
// debugger that the program has finished running and we're ready
|
// Dynamic module loading - ie. modules loaded using "import()"
|
||||||
// to exit the process once debugger disconnects.
|
{
|
||||||
if !has_blocking_sessions {
|
// Run in a loop so that dynamic imports that only depend on another
|
||||||
let context = self.global_context();
|
// dynamic import can be resolved in this event loop iteration.
|
||||||
let scope = &mut self.handle_scope();
|
//
|
||||||
inspector.borrow_mut().context_destroyed(scope, context);
|
// For example, a dynamically imported module like the following can be
|
||||||
println!("Program finished. Waiting for inspector to disconnect to exit the process...");
|
// immediately resolved after `dependency.ts` is fully evaluated, but it
|
||||||
|
// wouldn't if not for this loop.
|
||||||
|
//
|
||||||
|
// await delay(1000);
|
||||||
|
// await import("./dependency.ts");
|
||||||
|
// console.log("test")
|
||||||
|
//
|
||||||
|
loop {
|
||||||
|
let poll_imports = self.prepare_dyn_imports(cx)?;
|
||||||
|
assert!(poll_imports.is_ready());
|
||||||
|
|
||||||
|
let poll_imports = self.poll_dyn_imports(cx)?;
|
||||||
|
assert!(poll_imports.is_ready());
|
||||||
|
|
||||||
|
if !self.evaluate_dyn_imports() {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Poll::Pending;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Poll::Ready(Ok(()));
|
// 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()?;
|
||||||
|
|
||||||
let state = self.state.borrow();
|
// Event loop middlewares
|
||||||
|
let mut maybe_scheduling = false;
|
||||||
|
{
|
||||||
|
let op_state = self.state.borrow().op_state.clone();
|
||||||
|
for f in &self.event_loop_middlewares {
|
||||||
|
if f(op_state.clone(), cx) {
|
||||||
|
maybe_scheduling = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check if more async ops have been dispatched
|
// Top level module
|
||||||
// during this turn of event loop.
|
self.evaluate_pending_module();
|
||||||
// If there are any pending background tasks, we also wake the runtime to
|
|
||||||
// make sure we don't miss them.
|
|
||||||
// TODO(andreubotella) The event loop will spin as long as there are pending
|
|
||||||
// background tasks. We should look into having V8 notify us when a
|
|
||||||
// background task is done.
|
|
||||||
if state.have_unpolled_ops
|
|
||||||
|| pending_state.has_pending_background_tasks
|
|
||||||
|| pending_state.has_tick_scheduled
|
|
||||||
|| maybe_scheduling
|
|
||||||
{
|
|
||||||
state.waker.wake();
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(state);
|
let pending_state = self.event_loop_pending_state();
|
||||||
|
if !pending_state.is_pending() && !maybe_scheduling {
|
||||||
|
if has_inspector {
|
||||||
|
let inspector = self.inspector();
|
||||||
|
let has_active_sessions = inspector.borrow().has_active_sessions();
|
||||||
|
let has_blocking_sessions =
|
||||||
|
inspector.borrow().has_blocking_sessions();
|
||||||
|
|
||||||
if pending_state.has_pending_module_evaluation {
|
if wait_for_inspector && has_active_sessions {
|
||||||
if pending_state.has_pending_refed_ops
|
// If there are no blocking sessions (eg. REPL) we can now notify
|
||||||
|| pending_state.has_pending_dyn_imports
|
// debugger that the program has finished running and we're ready
|
||||||
|| pending_state.has_pending_dyn_module_evaluation
|
// to exit the process once debugger disconnects.
|
||||||
|| pending_state.has_pending_background_tasks
|
if !has_blocking_sessions {
|
||||||
|
let context = self.global_context();
|
||||||
|
let scope = &mut self.handle_scope();
|
||||||
|
inspector.borrow_mut().context_destroyed(scope, context);
|
||||||
|
println!("Program finished. Waiting for inspector to disconnect to exit the process...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = self.state.borrow();
|
||||||
|
|
||||||
|
// Check if more async ops have been dispatched during this turn of
|
||||||
|
// event loop. In such case immediately do another turn of the event loop.
|
||||||
|
if state.have_unpolled_ops {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are any pending background tasks, we also wake the runtime to
|
||||||
|
// make sure we don't miss them.
|
||||||
|
// TODO(andreubotella) The event loop will spin as long as there are pending
|
||||||
|
// background tasks. We should look into having V8 notify us when a
|
||||||
|
// background task is done.
|
||||||
|
if pending_state.has_pending_background_tasks
|
||||||
|| pending_state.has_tick_scheduled
|
|| pending_state.has_tick_scheduled
|
||||||
|| maybe_scheduling
|
|| maybe_scheduling
|
||||||
{
|
{
|
||||||
// pass, will be polled again
|
|
||||||
} else {
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pending_state.has_pending_dyn_module_evaluation {
|
|
||||||
if pending_state.has_pending_refed_ops
|
|
||||||
|| pending_state.has_pending_dyn_imports
|
|
||||||
|| pending_state.has_pending_background_tasks
|
|
||||||
|| pending_state.has_tick_scheduled
|
|
||||||
{
|
|
||||||
// pass, will be polled again
|
|
||||||
} 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.
|
|
||||||
state.dyn_module_evaluate_idle_counter += 1;
|
|
||||||
state.waker.wake();
|
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
|
||||||
|
|| pending_state.has_pending_dyn_module_evaluation
|
||||||
|
|| pending_state.has_pending_background_tasks
|
||||||
|
|| pending_state.has_tick_scheduled
|
||||||
|
|| maybe_scheduling
|
||||||
|
{
|
||||||
|
// pass, will be polled again
|
||||||
|
} else {
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pending_state.has_pending_dyn_module_evaluation {
|
||||||
|
if pending_state.has_pending_refed_ops
|
||||||
|
|| pending_state.has_pending_dyn_imports
|
||||||
|
|| pending_state.has_pending_background_tasks
|
||||||
|
|| pending_state.has_tick_scheduled
|
||||||
|
{
|
||||||
|
// pass, will be polled again
|
||||||
|
} 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.
|
||||||
|
state.dyn_module_evaluate_idle_counter += 1;
|
||||||
|
state.waker.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue