@@ -436,8 +436,10 @@ pub struct JsRuntimeState {
436436 pub ( crate ) function_templates : Rc < RefCell < FunctionTemplateData > > ,
437437 pub ( crate ) callsite_prototype : RefCell < Option < v8:: Global < v8:: Object > > > ,
438438 waker : Arc < AtomicWaker > ,
439- /// Guards the 100ms safety-net timer so at most one is active at a time.
440- safety_net_active : Arc < std:: sync:: atomic:: AtomicBool > ,
439+ /// Level-triggered flag set by the custom V8 platform when a foreground
440+ /// task is posted. Checked and cleared each poll to ensure pumping even
441+ /// if the edge-triggered AtomicWaker notification was lost.
442+ has_foreground_task : Arc < std:: sync:: atomic:: AtomicBool > ,
441443 /// Accessed through [`JsRuntimeState::with_inspector`].
442444 inspector : RefCell < Option < Rc < JsRuntimeInspector > > > ,
443445 has_inspector : Cell < bool > ,
@@ -743,6 +745,8 @@ impl JsRuntime {
743745 // ...now let's set up ` JsRuntimeState`, we'll need to set some fields
744746 // later, after `JsRuntime` is all set up...
745747 let waker = op_state. waker . clone ( ) ;
748+ let has_foreground_task =
749+ Arc :: new ( std:: sync:: atomic:: AtomicBool :: new ( false ) ) ;
746750 let op_state = Rc :: new ( RefCell :: new ( op_state) ) ;
747751 let ( eval_context_get_code_cache_cb, eval_context_set_code_cache_cb) =
748752 options
@@ -765,7 +769,7 @@ impl JsRuntime {
765769 eval_context_set_code_cache_cb,
766770 ) ,
767771 waker : waker. clone ( ) ,
768- safety_net_active : Arc :: new ( std :: sync :: atomic :: AtomicBool :: new ( false ) ) ,
772+ has_foreground_task : has_foreground_task . clone ( ) ,
769773 // Some fields are initialized later after isolate is created
770774 inspector : None . into ( ) ,
771775 has_inspector : false . into ( ) ,
@@ -847,6 +851,7 @@ impl JsRuntime {
847851 setup:: register_isolate_waker (
848852 setup:: isolate_ptr_to_key ( isolate_ptr) ,
849853 waker. clone ( ) ,
854+ has_foreground_task,
850855 ) ;
851856
852857 // ...isolate is fully set up, we can forward its pointer to the ops to finish
@@ -2108,7 +2113,14 @@ impl JsRuntime {
21082113 if has_inspector {
21092114 self . inspector ( ) . poll_sessions_from_event_loop ( cx) ;
21102115 }
2111- if poll_options. pump_v8_message_loop {
2116+ // Clear the foreground-task flag (set by the custom V8 platform callback).
2117+ // If it was set, we must pump the message loop regardless of other state.
2118+ let had_foreground_task = self
2119+ . inner
2120+ . state
2121+ . has_foreground_task
2122+ . swap ( false , std:: sync:: atomic:: Ordering :: Relaxed ) ;
2123+ if poll_options. pump_v8_message_loop || had_foreground_task {
21122124 self . pump_v8_message_loop ( scope) ?;
21132125 }
21142126
@@ -2265,27 +2277,6 @@ impl JsRuntime {
22652277 scope. perform_microtask_checkpoint ( ) ;
22662278 }
22672279
2268- // Safety net: if V8 has pending background tasks (e.g. module compilation),
2269- // schedule a delayed wake to pump the message loop in case the platform
2270- // callback was missed due to a race condition. Uses an OS thread (not
2271- // tokio) to avoid depending on the async runtime being cooperative.
2272- // The AtomicBool guard ensures at most one safety-net timer is active.
2273- if pending_state. has_pending_background_tasks
2274- && !self
2275- . inner
2276- . state
2277- . safety_net_active
2278- . swap ( true , std:: sync:: atomic:: Ordering :: Relaxed )
2279- {
2280- let waker = cx. waker ( ) . clone ( ) ;
2281- let flag = self . inner . state . safety_net_active . clone ( ) ;
2282- std:: thread:: spawn ( move || {
2283- std:: thread:: sleep ( std:: time:: Duration :: from_millis ( 100 ) ) ;
2284- flag. store ( false , std:: sync:: atomic:: Ordering :: Relaxed ) ;
2285- waker. wake_by_ref ( ) ;
2286- } ) ;
2287- }
2288-
22892280 // Re-wake logic for next iteration
22902281 #[ allow( clippy:: suspicious_else_formatting, clippy:: if_same_then_else) ]
22912282 {
0 commit comments