Outer Cloud
Outer Cloud10mo ago

Multiple JsRuntimes crashe if first is not dropped before a second is created and ran

In my application, I want an op2 to take in a function which will be stored in a struct as a Global<v8::Function>. Later this function will be called. This issue is the application crashes with Memory Access Violation. However, this only occurs on the second instance of the JsRuntime. Additionally this doesn't happen for the first few call, which leads me to believe it is something with the handle be garbage collected.
6 Replies
Outer Cloud
Outer CloudOP10mo ago
Here's a simplified source with [...] to show omitted code:
struct RuntimeState {
[...]
advance_function: Option<Global<v8::Function>>,
}

struct ScriptRuntime {
js_runtime: deno_core::JsRuntime,
state: Arc<Mutex<RuntimeState>>,
}

impl ScriptRuntime {
pub fn new() -> ScriptRuntime {
let state = Arc::new(Mutex::new(RuntimeState {
[...]
advance_function: None,
}));

let state_arc = state.clone();

let runtime_extension = Extension::builder("runtime_extension")
.ops(vec![op_register_advance::DECL, [...]])
.state(|extension_state| {
extension_state.put::<Arc<Mutex<RuntimeState>>>(state_arc);
})
.build();

let js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
module_loader: Some(Rc::new(TsModuleLoader)),
extensions: vec![runtime_extension],
..Default::default()
});

ScriptRuntime { js_runtime, state }
}

pub fn initialize(&mut self, script: &String) {
self.js_runtime
.execute_script("vector-engine/runtime.ts", deno_core::FastString::from(transpile_ts(String::from(include_str!("./runtime.ts")))))
.unwrap();

self.js_runtime.execute_script("project/clip.ts", deno_core::FastString::from(transpile_ts(script.clone()))).unwrap();

let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();

runtime.block_on(self.js_runtime.run_event_loop(false)).unwrap();
}

// The crash happens when calling this function
pub fn advance(&mut self) {
let state = self.state.lock().unwrap();

let advance = state.advance_function.clone();

if advance.is_none() {
return;
}

let advance = advance.unwrap();

drop(state);
struct RuntimeState {
[...]
advance_function: Option<Global<v8::Function>>,
}

struct ScriptRuntime {
js_runtime: deno_core::JsRuntime,
state: Arc<Mutex<RuntimeState>>,
}

impl ScriptRuntime {
pub fn new() -> ScriptRuntime {
let state = Arc::new(Mutex::new(RuntimeState {
[...]
advance_function: None,
}));

let state_arc = state.clone();

let runtime_extension = Extension::builder("runtime_extension")
.ops(vec![op_register_advance::DECL, [...]])
.state(|extension_state| {
extension_state.put::<Arc<Mutex<RuntimeState>>>(state_arc);
})
.build();

let js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
module_loader: Some(Rc::new(TsModuleLoader)),
extensions: vec![runtime_extension],
..Default::default()
});

ScriptRuntime { js_runtime, state }
}

pub fn initialize(&mut self, script: &String) {
self.js_runtime
.execute_script("vector-engine/runtime.ts", deno_core::FastString::from(transpile_ts(String::from(include_str!("./runtime.ts")))))
.unwrap();

self.js_runtime.execute_script("project/clip.ts", deno_core::FastString::from(transpile_ts(script.clone()))).unwrap();

let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();

runtime.block_on(self.js_runtime.run_event_loop(false)).unwrap();
}

// The crash happens when calling this function
pub fn advance(&mut self) {
let state = self.state.lock().unwrap();

let advance = state.advance_function.clone();

if advance.is_none() {
return;
}

let advance = advance.unwrap();

drop(state);
let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();

runtime.block_on(self.js_runtime.call_and_await(&advance)).unwrap();
}
}

[...]

#[op2]
fn op_register_advance(state: &mut OpState, scope: &mut v8::HandleScope, #[global] value: v8::Global<v8::Function>) -> Result<(), AnyError> {
let state_mutex = state.borrow_mut::<Arc<Mutex<ClipRuntimeState>>>();
let mut state = state_mutex.lock().unwrap();

state.advance_function = Some(value);

Ok(())
}

[...]
let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();

runtime.block_on(self.js_runtime.call_and_await(&advance)).unwrap();
}
}

[...]

#[op2]
fn op_register_advance(state: &mut OpState, scope: &mut v8::HandleScope, #[global] value: v8::Global<v8::Function>) -> Result<(), AnyError> {
let state_mutex = state.borrow_mut::<Arc<Mutex<ClipRuntimeState>>>();
let mut state = state_mutex.lock().unwrap();

state.advance_function = Some(value);

Ok(())
}

[...]
(had to split it up due to message length limit)
Deno AI Helper
Deno AI Helper10mo ago
I wasn't able to find the answer to your question in the official Deno documentation. However, based on the information you've provided, it seems like there might be an issue with the way the Global<v8::Function> handle is being managed across different instances of JsRuntime. Without seeing the actual code, it's difficult to provide a precise solution, but here are a couple of general points you might want to consider: 1. Ensure that you are properly managing the lifetime of the Global<v8::Function> handle. If it's being garbage collected prematurely, you might need to look into how you're maintaining references to it. 2. Check if you are correctly creating and disposing of JsRuntime instances. Improper handling of these instances could lead to memory access violations. Please note that this advice is not based on the Deno documentation, and I would advise you to double-check the accuracy of the advice given. It might also be helpful to review the V8 embedding documentation or seek assistance from the community if the problem persists.
Outer Cloud
Outer CloudOP10mo ago
After updating the call to instead get the function from the global scope instead of storing it, it still crashes.
pub fn advance(&mut self) {
let binding = self.js_runtime.main_context();

let context = binding.open(&mut self.js_runtime.v8_isolate());

let mut scope = self.js_runtime.handle_scope();

let global = context.global(&mut scope);

let key = v8::String::new(&mut scope, "advance").unwrap();

let advance = global.get(&mut scope, key.into()).unwrap();

let advance = v8::Local::<v8::Function>::try_from(advance).unwrap();

let this = v8::undefined(&mut scope);

advance.call(&mut scope, this.into(), &[]);
}
pub fn advance(&mut self) {
let binding = self.js_runtime.main_context();

let context = binding.open(&mut self.js_runtime.v8_isolate());

let mut scope = self.js_runtime.handle_scope();

let global = context.global(&mut scope);

let key = v8::String::new(&mut scope, "advance").unwrap();

let advance = global.get(&mut scope, key.into()).unwrap();

let advance = v8::Local::<v8::Function>::try_from(advance).unwrap();

let this = v8::undefined(&mut scope);

advance.call(&mut scope, this.into(), &[]);
}
Maybe it has to do with creating multiple Js runtimes...
Outer Cloud
Outer CloudOP10mo ago
It does seem like it could be the isolate
No description
Outer Cloud
Outer CloudOP10mo ago
No description
Outer Cloud
Outer CloudOP10mo ago
I found a fix but I don't understand why it fixes it. If I manually drop first runtime before creating a second, There is no longer a crash. If anyone has any incite into why this happens, it would be helpful