@@ -182,7 +182,7 @@ impl InnerIsolateState {
182182
183183 // Unregister isolate waker before dropping the isolate
184184 let isolate_ptr = unsafe { self . v8_isolate . as_raw_isolate_ptr ( ) } ;
185- setup:: unregister_isolate_waker ( setup:: isolate_ptr_to_key ( isolate_ptr) ) ;
185+ setup:: unregister_isolate ( setup:: isolate_ptr_to_key ( isolate_ptr) ) ;
186186
187187 let state_ptr = self . v8_isolate . get_data ( STATE_DATA_OFFSET ) ;
188188 // SAFETY: We are sure that it's a valid pointer for whole lifetime of
@@ -457,7 +457,10 @@ pub struct JsRuntimeState {
457457 pub ( crate ) function_templates : Rc < RefCell < FunctionTemplateData > > ,
458458 pub ( crate ) callsite_prototype : RefCell < Option < v8:: Global < v8:: Object > > > ,
459459 waker : Arc < AtomicWaker > ,
460- safety_net_active : Arc < std:: sync:: atomic:: AtomicBool > ,
460+ /// Foreground V8 tasks queued by the custom platform. Shared with the
461+ /// global isolate registry so background threads can push tasks, while
462+ /// the event loop drains them without touching the global map.
463+ foreground_tasks : setup:: ForegroundTaskQueue ,
461464 /// Accessed through [`JsRuntimeState::with_inspector`].
462465 inspector : RefCell < Option < Rc < JsRuntimeInspector > > > ,
463466 has_inspector : Cell < bool > ,
@@ -586,19 +589,9 @@ impl RuntimeOptions {
586589 }
587590}
588591
589- #[ derive( Copy , Clone , Debug ) ]
592+ #[ derive( Copy , Clone , Debug , Default ) ]
590593pub struct PollEventLoopOptions {
591594 pub wait_for_inspector : bool ,
592- pub pump_v8_message_loop : bool ,
593- }
594-
595- impl Default for PollEventLoopOptions {
596- fn default ( ) -> Self {
597- Self {
598- wait_for_inspector : false ,
599- pump_v8_message_loop : true ,
600- }
601- }
602595}
603596
604597#[ derive( Default ) ]
@@ -785,7 +778,7 @@ impl JsRuntime {
785778 eval_context_set_code_cache_cb,
786779 ) ,
787780 waker : waker. clone ( ) ,
788- safety_net_active : Arc :: new ( std :: sync :: atomic :: AtomicBool :: new ( false ) ) ,
781+ foreground_tasks : Default :: default ( ) ,
789782 // Some fields are initialized later after isolate is created
790783 inspector : None . into ( ) ,
791784 has_inspector : false . into ( ) ,
@@ -862,12 +855,19 @@ impl JsRuntime {
862855
863856 let isolate_ptr = unsafe { isolate. as_raw_isolate_ptr ( ) } ;
864857
865- // Register this isolate's waker so the custom platform can wake
866- // the event loop when V8 posts foreground tasks from background threads.
867- setup:: register_isolate_waker (
868- setup:: isolate_ptr_to_key ( isolate_ptr) ,
869- waker. clone ( ) ,
870- ) ;
858+ // Register this isolate in the global platform registry so V8 background
859+ // threads can queue foreground tasks. The task queue Arc is already shared
860+ // with JsRuntimeState (created above) so the event loop can drain it
861+ // without touching the global map.
862+ // Not all contexts have a tokio runtime (e.g. snapshot creation, unit tests).
863+ if let Ok ( handle) = tokio:: runtime:: Handle :: try_current ( ) {
864+ setup:: register_isolate (
865+ setup:: isolate_ptr_to_key ( isolate_ptr) ,
866+ waker. clone ( ) ,
867+ handle,
868+ state_rc. foreground_tasks . clone ( ) ,
869+ ) ;
870+ }
871871
872872 // ...isolate is fully set up, we can forward its pointer to the ops to finish
873873 // their' setup...
@@ -1902,32 +1902,6 @@ impl JsRuntime {
19021902 }
19031903 }
19041904
1905- fn pump_v8_message_loop (
1906- & self ,
1907- scope : & mut v8:: PinScope ,
1908- ) -> Result < ( ) , Box < JsError > > {
1909- while v8:: Platform :: pump_message_loop (
1910- & v8:: V8 :: get_current_platform ( ) ,
1911- scope,
1912- false , // don't block if there are no tasks
1913- ) {
1914- // do nothing
1915- }
1916-
1917- v8:: tc_scope!( let tc_scope, scope) ;
1918-
1919- let context_state = JsRealm :: state_from_scope ( tc_scope) ;
1920- if !context_state. has_tick_scheduled ( ) {
1921- tc_scope. perform_microtask_checkpoint ( ) ;
1922- }
1923- match tc_scope. exception ( ) {
1924- None => Ok ( ( ) ) ,
1925- Some ( exception) => {
1926- exception_to_err_result ( tc_scope, exception, false , true )
1927- }
1928- }
1929- }
1930-
19311905 pub fn maybe_init_inspector ( & mut self ) {
19321906 let inspector = & mut self . inner . state . inspector . borrow_mut ( ) ;
19331907 if inspector. is_some ( ) {
@@ -2130,12 +2104,31 @@ impl JsRuntime {
21302104 let has_inspector = self . inner . state . has_inspector . get ( ) ;
21312105 self . inner . state . waker . register ( cx. waker ( ) ) ;
21322106
2133- // Pre-phase: Inspector + V8 message loop pump
2107+ // Pre-phase: Inspector + drain foreground tasks + microtask checkpoint
21342108 if has_inspector {
21352109 self . inspector ( ) . poll_sessions_from_event_loop ( cx) ;
21362110 }
2137- if poll_options. pump_v8_message_loop {
2138- self . pump_v8_message_loop ( scope) ?;
2111+ {
2112+ // Drain and run foreground tasks queued by the custom V8 platform.
2113+ // Uses the local Arc shared with the registry — no global map lookup.
2114+ let tasks =
2115+ std:: mem:: take ( & mut * self . inner . state . foreground_tasks . lock ( ) . unwrap ( ) ) ;
2116+ for task in tasks {
2117+ task. run ( ) ;
2118+ }
2119+
2120+ v8:: tc_scope!( let tc_scope, scope) ;
2121+ let context_state = JsRealm :: state_from_scope ( tc_scope) ;
2122+ if !context_state. has_tick_scheduled ( ) {
2123+ tc_scope. perform_microtask_checkpoint ( ) ;
2124+ }
2125+ if let Some ( exception) = tc_scope. exception ( ) {
2126+ return Poll :: Ready ( Err (
2127+ exception_to_err_result :: < ( ) > ( tc_scope, exception, false , true )
2128+ . unwrap_err ( )
2129+ . into ( ) ,
2130+ ) ) ;
2131+ }
21392132 }
21402133
21412134 let realm = & self . inner . main_realm ;
@@ -2291,24 +2284,6 @@ impl JsRuntime {
22912284 scope. perform_microtask_checkpoint ( ) ;
22922285 }
22932286
2294- // Safety net: if V8 has pending background tasks (e.g. module compilation),
2295- // schedule a delayed wake to pump the message loop in case the platform
2296- // callback was missed due to a race condition.
2297- if pending_state. has_pending_background_tasks
2298- && !self
2299- . inner
2300- . state
2301- . safety_net_active
2302- . swap ( true , std:: sync:: atomic:: Ordering :: SeqCst )
2303- {
2304- cx. waker ( ) . wake_by_ref ( ) ;
2305- self
2306- . inner
2307- . state
2308- . safety_net_active
2309- . store ( false , std:: sync:: atomic:: Ordering :: SeqCst ) ;
2310- }
2311-
23122287 // Re-wake logic for next iteration
23132288 #[ allow(
23142289 clippy:: suspicious_else_formatting,
0 commit comments