rusty_v8
rusty_v8 copied to clipboard
Unreachable code reached in `ScopeData::get_current_mut(isolate)` called from external interrupt handler
If an external interrupt handler is used to call back into rusty_v8 using CallbackScope to initialise a call stack, there is a chance that the creation of the CallbackScope will panic on unreachable!() macro in the ScopeData::get_current_mut(isolate) call.
The unreachable code is the non ScopeStatus::Current { .. } => self_mut match arm in the method, with the actual state of the scope being ScopeStatus::Shadowed { zombie: false }.
A Linux-only reproduction code is (using the deno crate) as follows:
# test_ffi/Cargo.toml, added
[dependencies]
nix = "0.24.1"
// test_ffi/src/lib.rs, added at the end
use nix::sys::signal::*;
use nix::unistd::alarm;
extern "C" fn signal_handler(_: nix::libc::c_int) {
call_stored_function();
}
#[no_mangle]
extern "C" fn call_stored_function_after_delay() {
let sa = SigAction::new(
SigHandler::Handler(signal_handler),
SaFlags::empty(),
SigSet::empty(),
);
unsafe {
sigaction(Signal::SIGALRM, &sa).unwrap();
}
alarm::set(1); // Increase the timeout to verify that isolate is being kept alive until this fires and that it's not just your computer being slow to run the code
}
// interrupt.js
const libPath = ""; // Get a path to target/debug/libtest_ffi.so somehow
const dylib = Deno.dlopen(libPath, {
store_function: {
parameters: ["function"],
result: "void",
},
call_stored_function_after_delay: {
parameters: [],
result: "void",
},
});
const cb = () => {
// console.log("Sync"); // uncomment this to get random panics on double-borrow of OpState which is not exactly related to this issue
Promise.resolve().then(() => {
console.log("Async");
callback.unref();
});
};
const callback = new Deno.UnsafeCallback({
parameters: [],
result: "void",
}, cb);
callback.ref(); // Keep the isolate from exiting until callback is called
dylib.symbols.store_function(callback.pointer);
dylib.symbols.call_stored_function_after_delay();
I don't exactly understand what the different ScopeStates are, but it might be useful to make it possible to build up a stack from nothing.
@piscisaureus can you take a look?
Oh yeah, I nearly forgot to mention: The panic only occurs about 1 in 5 to 1 in 10 calls, at least on my machine.
- You are not supposed to re-enter v8 inside an interrupt callback.
- CallbackScope doesn't create a scope; it merely asserts that one exists (that's why creating one is unsafe). Apparently a scope does not always exist when an interrupt callback gets called.
- Inside a signal handler you can only call async signal safe functions. V8's API doesn't have async signal safe functions (maybe some are by accident).