denoland / rusty_v8

Rust bindings for the V8 JavaScript engine
https://crates.io/crates/v8
MIT License
3.08k stars 299 forks source link

Catch Reached heap limit crashes? #1486

Closed danthegoodman1 closed 1 month ago

danthegoodman1 commented 1 month ago

How do you catch when an isolate runs out of a configured heap limit like:

let isolate = &mut v8::Isolate::new(v8::CreateParams::default().heap_limits(mb, 10 * mb));

It seems to crash the whole rust currently:

✗ cargo run                                                                                        4:36PM
   Compiling rust_v8 v0.1.0 (/Users/dangoodman/code/learningRust/rust_v8)
    Finished dev [unoptimized + debuginfo] target(s) in 0.35s
     Running `target/debug/rust_v8`

<--- Last few GCs --->

[15141:0x158008000]       53 ms: Mark-Compact 12.5 (14.2) -> 7.6 (9.2) MB, pooled: 0 MB, 10.46 / 0.00 ms  (average mu = 0.246, current mu = 0.155) allocation failure; scavenge might not succeed
[15141:0x158008000]       69 ms: Mark-Compact 18.7 (20.4) -> 11.3 (13.0) MB, pooled: 0 MB, 13.96 / 0.00 ms  (average mu = 0.195, current mu = 0.156) allocation failure; scavenge might not succeed

<--- JS stacktrace --->

#
# Fatal JavaScript out of memory: Reached heap limit
#
==== C stack trace ===============================

    0   rust_v8                             0x00000001044aeb00 v8::base::debug::StackTrace::StackTrace() + 24
    1   rust_v8                             0x00000001044b3960 v8::platform::(anonymous namespace)::PrintStackTrace() + 24
    2   rust_v8                             0x00000001044a4828 v8::base::FatalOOM(v8::base::OOMType, char const*) + 68
    3   rust_v8                             0x00000001045026c4 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) + 628
    4   rust_v8                             0x00000001046a43f8 v8::internal::Heap::stack() + 0
    5   rust_v8                             0x00000001046a291c v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) + 920
    6   rust_v8                             0x0000000104697c74 v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) + 1888
    7   rust_v8                             0x0000000104698520 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) + 52
    8   rust_v8                             0x000000010467e088 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) + 440
    9   rust_v8                             0x0000000104673e80 v8::internal::FactoryBase<v8::internal::Factory>::NewFixedArray(int, v8::internal::AllocationType) + 72
    10  rust_v8                             0x0000000104810d50 v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::FastPackedObjectElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)2>>::ConvertElementsWithCapacity(v8::internal::Handle<v8::internal::JSObject>, v8::internal::Handle<v8::internal::FixedArrayBase>, v8::internal::ElementsKind, unsigned int, unsigned int, unsigned int) + 156
    11  rust_v8                             0x000000010480f630 v8::internal::(anonymous namespace)::ElementsAccessorBase<v8::internal::(anonymous namespace)::FastPackedObjectElementsAccessor, v8::internal::(anonymous namespace)::ElementsKindTraits<(v8::internal::ElementsKind)2>>::GrowCapacity(v8::internal::Handle<v8::internal::JSObject>, unsigned int) + 236
    12  rust_v8                             0x0000000104a7002c v8::internal::Runtime_GrowArrayElements(int, unsigned long*, v8::internal::Isolate*) + 252
    13  rust_v8                             0x000000010565d834 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit + 84
    14  ???                                 0x000000016f6004d0 0x0 + 6163530960
    15  rust_v8                             0x00000001055bf648 Builtins_JSEntryTrampoline + 168
    16  rust_v8                             0x00000001055bf294 Builtins_JSEntry + 180
    17  rust_v8                             0x0000000104601ec4 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 1828
    18  rust_v8                             0x0000000104601770 v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 140
    19  rust_v8                             0x0000000104508d04 v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 532
    20  rust_v8                             0x000000010448a0d0 _ZN2v88function36_$LT$impl$u20$v8..data..Function$GT$4call28_$u7b$$u7b$closure$u7d$$u7d$17he6ea0103300fadb1E + 276
    21  rust_v8                             0x000000010447c9a0 _ZN2v88function36_$LT$impl$u20$v8..data..Function$GT$4call17h8aef9e084672239aE + 1076
    22  rust_v8                             0x000000010446ba24 rust_v8::main::h456c1786d30674fc + 48732
    23  rust_v8                             0x0000000104485d84 core::ops::function::FnOnce::call_once::hc25835c1f1743493 + 20
    24  rust_v8                             0x000000010445f528 std::sys_common::backtrace::__rust_begin_short_backtrace::h0b79715916e323b7 + 24
    25  rust_v8                             0x000000010445e598 _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h410c755b067bb036E + 28
    26  rust_v8                             0x00000001057beb7c std::rt::lang_start_internal::h0e09503d2b7f298e + 656
    27  rust_v8                             0x000000010445e564 std::rt::lang_start::h9455c4f327998a81 + 84
    28  rust_v8                             0x00000001044795bc main + 36
    29  dyld                                0x0000000197a660e0 start + 2360
[1]    15141 trace trap  cargo run
danthegoodman1 commented 1 month ago

I believe I solved it.

The way is to first setup a heap_limit_callback callback that points to the isolate:

extern "C" fn oom_handler(_: *const std::os::raw::c_char, _: &v8::OomDetails) {
            panic!("OOM! I should never happen")
        }
        isolate.set_oom_error_handler(oom_handler);

        extern "C" fn heap_limit_callback(
            data: *mut c_void,
            current_heap_limit: usize,
            _initial_heap_limit: usize,
        ) -> usize {
            let isolate = unsafe {&mut *(data as *mut v8::Isolate)};
            // murder the isolate
            let terminated = isolate.terminate_execution();
            println!("near limit! {:?}", terminated);
            current_heap_limit * 2 // give us some space to kill it
        }
        let isolate_ptr: &mut v8::Isolate = &mut isolate;

        // Cast the isolate pointer to *mut c_void
        let data: *mut c_void = isolate_ptr as *mut v8::Isolate as *mut c_void;
        isolate.add_near_heap_limit_callback(heap_limit_callback, data);

When you kill the isolate while it is running, the expected failure happens:

let result = match multiply_fn.call(&mut scope, instance.into(), args) {
            Some(result) => {
                println!("result");
                result
            }
            None => {
                println!("Has caught: {}, can continue: {}", scope.has_caught(), scope.can_continue());
                panic!("exiting now")
            }
        };