1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -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:
Bartek Iwańczuk 2023-04-14 12:55:47 +02:00 committed by GitHub
parent cb2ca234bb
commit 4317055a3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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
} }