@@ -430,6 +430,12 @@ pub struct JsRuntimeState {
430430 pub ( crate ) function_templates : Rc < RefCell < FunctionTemplateData > > ,
431431 pub ( crate ) callsite_prototype : RefCell < Option < v8:: Global < v8:: Object > > > ,
432432 waker : Arc < AtomicWaker > ,
433+ /// Tracks whether V8 had pending background tasks on the previous
434+ /// event loop iteration. Used to detect the transition from
435+ /// has_pending_background_tasks=true to false, which is when a
436+ /// background compilation may have just posted a foreground callback
437+ /// that needs an extra pump_message_loop call to process.
438+ had_pending_background_tasks : Cell < bool > ,
433439 /// Accessed through [`JsRuntimeState::with_inspector`].
434440 inspector : RefCell < Option < Rc < JsRuntimeInspector > > > ,
435441 has_inspector : Cell < bool > ,
@@ -757,6 +763,7 @@ impl JsRuntime {
757763 eval_context_set_code_cache_cb,
758764 ) ,
759765 waker,
766+ had_pending_background_tasks : false . into ( ) ,
760767 // Some fields are initialized later after isolate is created
761768 inspector : None . into ( ) ,
762769 has_inspector : false . into ( ) ,
@@ -1786,23 +1793,27 @@ impl JsRuntime {
17861793 }
17871794 }
17881795
1796+ /// Pump V8's foreground message loop, processing tasks posted by
1797+ /// background threads (e.g. module compilation callbacks).
1798+ /// Returns `true` if any tasks were processed.
17891799 fn pump_v8_message_loop (
17901800 & self ,
17911801 scope : & mut v8:: PinScope ,
1792- ) -> Result < ( ) , Box < JsError > > {
1802+ ) -> Result < bool , Box < JsError > > {
1803+ let mut did_work = false ;
17931804 while v8:: Platform :: pump_message_loop (
17941805 & v8:: V8 :: get_current_platform ( ) ,
17951806 scope,
17961807 false , // don't block if there are no tasks
17971808 ) {
1798- // do nothing
1809+ did_work = true ;
17991810 }
18001811
18011812 v8:: tc_scope!( let tc_scope, scope) ;
18021813
18031814 tc_scope. perform_microtask_checkpoint ( ) ;
18041815 match tc_scope. exception ( ) {
1805- None => Ok ( ( ) ) ,
1816+ None => Ok ( did_work ) ,
18061817 Some ( exception) => {
18071818 exception_to_err_result ( tc_scope, exception, false , true )
18081819 }
@@ -2015,8 +2026,9 @@ impl JsRuntime {
20152026 if has_inspector {
20162027 self . inspector ( ) . poll_sessions_from_event_loop ( cx) ;
20172028 }
2029+ let mut v8_tasks_processed = false ;
20182030 if poll_options. pump_v8_message_loop {
2019- self . pump_v8_message_loop ( scope) ?;
2031+ v8_tasks_processed = self . pump_v8_message_loop ( scope) ?;
20202032 }
20212033
20222034 let realm = & self . inner . main_realm ;
@@ -2127,6 +2139,24 @@ impl JsRuntime {
21272139 let pending_state =
21282140 EventLoopPendingState :: new ( scope, context_state, modules) ;
21292141
2142+ // Second V8 message pump: only needed on the transition from
2143+ // had_pending_background_tasks=true to false. This is the exact
2144+ // moment a background compilation thread may have just posted a
2145+ // foreground callback that the first pump (pre-phase) missed.
2146+ // Avoids pumping on every iteration during module loading.
2147+ let had_bg_tasks = self . inner . state . had_pending_background_tasks . get ( ) ;
2148+ self
2149+ . inner
2150+ . state
2151+ . had_pending_background_tasks
2152+ . set ( pending_state. has_pending_background_tasks ) ;
2153+ if poll_options. pump_v8_message_loop
2154+ && had_bg_tasks
2155+ && !pending_state. has_pending_background_tasks
2156+ {
2157+ v8_tasks_processed |= self . pump_v8_message_loop ( scope) ?;
2158+ }
2159+
21302160 if !pending_state. is_pending ( ) {
21312161 if has_inspector {
21322162 let inspector = self . inspector ( ) ;
@@ -2164,6 +2194,7 @@ impl JsRuntime {
21642194 || pending_state. has_refed_immediates > 0
21652195 || pending_state. has_pending_promise_events
21662196 || uv_did_io
2197+ || v8_tasks_processed
21672198 {
21682199 self . inner . state . waker . wake ( ) ;
21692200 } else
0 commit comments