77//! ## How it works
88//!
99//! When V8's heap approaches the configured limit, it calls our callback.
10- //! The callback:
11- //! 1. First call: Increases the limit slightly (10%) to give V8 room to GC
12- //! 2. Subsequent calls: Terminates execution via `isolate.terminate_execution()`
10+ //! The callback calls `terminate_execution()` and returns a slightly increased
11+ //! limit (+2 MB headroom). The headroom is critical: V8 only checks the
12+ //! termination flag when returning to JS execution, not during GC. Without it,
13+ //! V8 sees "no room given" and calls `FatalProcessOutOfMemory` (process crash).
1314//!
1415//! This prevents a single misbehaving worker from crashing the entire runner.
1516//!
@@ -55,11 +56,20 @@ impl HeapLimitState {
5556 }
5657}
5758
59+ /// Headroom given to V8 after calling `terminate_execution()`.
60+ ///
61+ /// V8 only checks the termination flag when returning to JS execution.
62+ /// If we return the same `current_heap_limit`, V8 considers the allocation
63+ /// failed and goes straight to `FatalProcessOutOfMemory` (process crash).
64+ /// By returning a slightly higher limit, V8 can complete the pending
65+ /// allocation, return to JS, and see the termination flag.
66+ const TERMINATION_HEADROOM : usize = 2 * 1024 * 1024 ; // 2 MB
67+
5868/// Near-heap-limit callback for V8.
5969///
6070/// This callback is invoked when V8's heap is approaching its limit.
61- /// Returns a new heap limit to allow V8 to continue, or terminates execution
62- /// if we've already given V8 extra room .
71+ /// On first call, terminates execution and gives V8 a small headroom
72+ /// so it can unwind back to JS where `terminate_execution()` takes effect .
6373///
6474/// # Safety
6575///
@@ -68,50 +78,37 @@ impl HeapLimitState {
6878pub unsafe extern "C" fn near_heap_limit_callback (
6979 data : * mut c_void ,
7080 current_heap_limit : usize ,
71- initial_heap_limit : usize ,
81+ _initial_heap_limit : usize ,
7282) -> usize {
7383 // SAFETY: data is a valid pointer to HeapLimitState, passed from install_heap_limit_callback
7484 let state = unsafe { & * ( data as * const HeapLimitState ) } ;
7585
7686 let count = state. invocation_count . fetch_add ( 1 , Ordering :: SeqCst ) ;
7787
7888 tracing:: warn!(
79- "Near heap limit callback invoked (count: {}, current: {} MB, initial: {} MB, max: {} MB)" ,
89+ "Near heap limit callback invoked (count: {}, current: {} MB, max: {} MB)" ,
8090 count + 1 ,
8191 current_heap_limit / ( 1024 * 1024 ) ,
82- initial_heap_limit / ( 1024 * 1024 ) ,
8392 state. max_heap_bytes / ( 1024 * 1024 )
8493 ) ;
8594
86- // First invocation: give V8 a bit more room to try GC
87- if count == 0 {
88- // Increase by 10%, but don't exceed max
89- let extra = current_heap_limit / 10 ;
90- let new_limit = ( current_heap_limit + extra) . min ( state. max_heap_bytes ) ;
91-
92- if new_limit > current_heap_limit {
93- tracing:: warn!(
94- "Increasing heap limit to {} MB to allow GC" ,
95- new_limit / ( 1024 * 1024 )
96- ) ;
97- return new_limit;
98- }
99- }
100-
101- // We've already given extra room or can't give more - terminate execution
102- tracing:: error!(
103- "Heap limit exhausted after {} callbacks, terminating execution" ,
104- count + 1
105- ) ;
106-
10795 // Set the memory limit flag so the runner knows why execution stopped
10896 state. memory_limit_hit . store ( true , Ordering :: SeqCst ) ;
10997
11098 // Terminate execution gracefully instead of letting V8 crash
11199 state. isolate_handle . terminate_execution ( ) ;
112100
113- // Return current limit (V8 will see termination and stop)
114- current_heap_limit
101+ tracing:: error!(
102+ "Heap limit exhausted after {} callbacks, terminating execution" ,
103+ count + 1
104+ ) ;
105+
106+ // CRITICAL: Always return more than current_heap_limit.
107+ // V8 does not check terminate_execution() during GC — only when returning
108+ // to JS. If we return <= current_heap_limit, V8 sees "no room given" and
109+ // calls FatalProcessOutOfMemory, crashing the entire process.
110+ // The headroom lets V8 complete the pending allocation and unwind to JS.
111+ current_heap_limit + TERMINATION_HEADROOM
115112}
116113
117114/// OOM error handler for V8 isolates.
0 commit comments