I am trying to catch a v8 isolate that has exceeded it's heap memory limit, but the v8::TryCatch is not catching this, rather it is dumping a C stack trace (below).
The isolate is created like:
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
{
let mb = 1 << 20;
let params = v8::CreateParams::default().heap_limits(mb, 10 * mb);
// Create a new Isolate and make it the current one.
let isolate = &mut v8::Isolate::new(params);
// Create a stack-allocated handle scope.
let handle_scope = &mut v8::HandleScope::new(isolate);
// Create a new context.
let context = v8::Context::new(handle_scope);
// Enter the context for compiling and running scripts.
let scope = &mut v8::ContextScope::new(handle_scope, context);
let mut scope = v8::TryCatch::new(scope);
// ... more code
}
Executed like:
// Create a string containing the JavaScript source code for MyClass.
let c_source = r#"
class MyClass {
multiply(a, b) {
let z = []
while (true) {
z.push("THIS IS A VERY LONG STRING")
}
return a * b;
}
testQuery() {
let a = query("SELECT * FROM data", [1, 2.1, 'test', true]);
console.log("hey", a)
console.error("hey", JSON.stringify(a), JSON.parse(JSON.stringify(a)))
}
}
this.MyClass = MyClass;"#;
let source = v8::String::new(&mut scope, c_source).unwrap();
// Compile the source code.
let script = v8::Script::compile(&mut scope, source, None).unwrap();
// Run the script to define the class.
script.run(&mut scope).unwrap();
// Get the MyClass constructor from the global object.
let global = context.global(&mut scope);
let key = v8::String::new(&mut scope, "MyClass").unwrap();
let class_value = global.get(&mut scope, key.into()).unwrap();
// Ensure it's a function (constructor).
if !class_value.is_function() {
panic!("MyClass is not a function");
}
let class_constructor = v8::Local::<v8::Function>::try_from(class_value).unwrap();
// Create an instance of MyClass.
let instance = class_constructor.new_instance(&mut scope, &[]).unwrap();
// Get the multiply method from the instance.
let multiply_key = v8::String::new(&mut scope, "multiply").unwrap();
let multiply_value = instance.get(&mut scope, multiply_key.into()).unwrap();
// Ensure it's a function.
if !multiply_value.is_function() {
panic!("multiply is not a function");
}
let multiply_fn = v8::Local::<v8::Function>::try_from(multiply_value).unwrap();
// Now we can call the `multiply` method on the instance from Rust.
let arg1 = v8::Number::new(&mut scope, 3.0);
let arg2 = v8::Number::new(&mut scope, 4.0);
let args = &[arg1.into(), arg2.into()];
let result = match multiply_fn.call(&mut scope, instance.into(), args) {
Some(result) => {
println!("result");
result
},
None => {
panic!("SOMETHING BAD")
}
};
When I cargo run
I get:
<--- Last few GCs --->
[94083:0x130008000] 51 ms: Mark-Compact 12.5 (14.2) -> 7.6 (9.2) MB, pooled: 0 MB, 10.54 / 0.00 ms (average mu = 0.242, current mu = 0.159) allocation failure; scavenge might not succeed
[94083:0x130008000] 68 ms: Mark-Compact 18.7 (20.4) -> 11.3 (13.0) MB, pooled: 0 MB, 13.96 / 0.00 ms (average mu = 0.192, current mu = 0.154) allocation failure; scavenge might not succeed
<--- JS stacktrace --->
#
# Fatal JavaScript out of memory: Reached heap limit
#
==== C stack trace ===============================
0 rust_v8 0x0000000104782b38 v8::base::debug::StackTrace::StackTrace() + 24
1 rust_v8 0x0000000104787998 v8::platform::(anonymous namespace)::PrintStackTrace() + 24
2 rust_v8 0x0000000104778860 v8::base::FatalOOM(v8::base::OOMType, char const*) + 68
3 rust_v8 0x00000001047d66fc v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) + 628
4 rust_v8 0x0000000104978430 v8::internal::Heap::stack() + 0
5 rust_v8 0x0000000104976954 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) + 920
6 rust_v8 0x000000010496bcac v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) + 1888
7 rust_v8 0x000000010496c558 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) + 52
8 rust_v8 0x00000001049520c0 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) + 440
9 rust_v8 0x0000000104947eb8 v8::internal::FactoryBase<v8::internal::Factory>::NewFixedArray(int, v8::internal::AllocationType) + 72
10 rust_v8 0x0000000104ae4d88 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 0x0000000104ae3668 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 0x0000000104d44064 v8::internal::Runtime_GrowArrayElements(int, unsigned long*, v8::internal::Isolate*) + 252
13 rust_v8 0x0000000105931834 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit + 84
14 ??? 0x000000016f6004d0 0x0 + 6163530960
15 rust_v8 0x0000000105893648 Builtins_JSEntryTrampoline + 168
16 rust_v8 0x0000000105893294 Builtins_JSEntry + 180
17 rust_v8 0x00000001048d5efc v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 1828
18 rust_v8 0x00000001048d57a8 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 0x00000001047dcd3c v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) + 532
20 rust_v8 0x000000010475e108 _ZN2v88function36_$LT$impl$u20$v8..data..Function$GT$4call28_$u7b$$u7b$closure$u7d$$u7d$17he6ea0103300fadb1E + 276
21 rust_v8 0x00000001047509d8 _ZN2v88function36_$LT$impl$u20$v8..data..Function$GT$4call17h8aef9e084672239aE + 1076
22 rust_v8 0x000000010473fa30 rust_v8::main::h456c1786d30674fc + 48744
23 rust_v8 0x0000000104759dbc core::ops::function::FnOnce::call_once::hc25835c1f1743493 + 20
24 rust_v8 0x0000000104733528 std::sys_common::backtrace::__rust_begin_short_backtrace::h0b79715916e323b7 + 24
25 rust_v8 0x0000000104732598 _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h410c755b067bb036E + 28
26 rust_v8 0x0000000105a92b7c std::rt::lang_start_internal::h0e09503d2b7f298e + 656
27 rust_v8 0x0000000104732564 std::rt::lang_start::h9455c4f327998a81 + 84
28 rust_v8 0x000000010474d5f4 main + 36
29 dyld 0x0000000197a660e0 start + 2360
[1] 94083 trace trap cargo run
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")
}
};