/// Call a Ruby proc with the GVL acquired via rb_sys.
/// Called from within a `rb_thread_call_without_gvl` callback (same OS thread).
fn call_ruby_proc_with_gvl(
proc_handle: &Opaque<Value>,
req_json: &str,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let box_err = |e: Box<dyn std::error::Error + Send + Sync>| e;
// SAFETY: rb_thread_call_with_gvl is safe to call from any thread.
// It acquires the GVL and calls the callback with it held.
// We use a helper extern fn to bridge the gap.
unsafe {
let mut state = RubyProcCallState {
proc_handle: proc_handle.clone(),
req_json: req_json.to_string(),
result: None,
};
rb_sys::rb_thread_call_with_gvl(
Some(ruby_proc_gvl_callback),
&mut state as *mut _ as *mut std::ffi::c_void,
);
state.result.unwrap_or_else(|| {
Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"GVL callback failed to set result",
)) as Box<dyn std::error::Error + Send + Sync>)
})
}
}
struct RubyProcCallState {
proc_handle: Opaque<Value>,
req_json: String,
result: Option<Result<String, Box<dyn std::error::Error + Send + Sync>>>,
}
// SAFETY: RubyProcCallState is only accessed from within the GVL callback.
unsafe impl Send for RubyProcCallState {}
unsafe impl Sync for RubyProcCallState {}
// Callback invoked by rb_thread_call_with_gvl with the GVL held.
extern "C" fn ruby_proc_gvl_callback(data: *mut std::ffi::c_void) -> *mut std::ffi::c_void {
// SAFETY: data is a pointer to our RubyProcCallState, guaranteed valid for the duration of the callback.
unsafe {
let state = &mut *(data as *mut RubyProcCallState);
let box_err = |e: Box<dyn std::error::Error + Send + Sync>| e;
// We are now on a Ruby thread with the GVL held. Safe to call Magnus APIs.
let ruby = match Ruby::get() {
Ok(r) => r,
Err(_) => {
state.result = Some(Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"Could not obtain Ruby handle within GVL callback",
)) as Box<dyn std::error::Error + Send + Sync>));
return std::ptr::null_mut();
}
};
let proc_value = state.proc_handle.get_inner_with(&ruby);
// Parse request JSON into a Ruby Hash
let json_mod = match ruby.eval::<Value>("JSON") {
Ok(m) => m,
Err(e) => {
state.result = Some(Err(Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error + Send + Sync>));
return std::ptr::null_mut();
}
};
let req_hash = match json_mod.funcall::<_, _, Value>("parse", (state.req_json.as_str(),)) {
Ok(h) => h,
Err(e) => {
state.result = Some(Err(Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error + Send + Sync>));
return std::ptr::null_mut();
}
};
// Call the proc with the request hash
let result = match proc_value.funcall::<_, _, Value>("call", (req_hash,)) {
Ok(r) => r,
Err(e) => {
state.result = Some(Err(Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error + Send + Sync>));
return std::ptr::null_mut();
}
};
// Serialize result back to JSON
match json_mod.funcall::<_, _, String>("generate", (result,)) {
Ok(resp_json_str) => {
state.result = Some(Ok(resp_json_str));
}
Err(e) => {
state.result = Some(Err(Box::new(std::io::Error::other(e.to_string())) as Box<dyn std::error::Error + Send + Sync>));
}
}
}
std::ptr::null_mut()
}