通过 FFI 删除 Tokio 运行时会出现“无法在不允许阻塞的上下文中删除运行时”的恐慌。
Dropping a Tokio runtime through FFI panics with `Cannot drop a runtime in a context where blocking is not allowed.`
我正在使用一个 tokio 运行time,它被装箱并由 FFI 函数的指针返回。 运行time 用于 运行 futures,并且包装在 C# Task 中。
我可以自由地创建和使用 运行time,但是当我再次尝试从 FFI 函数中删除它时,出现以下错误:
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context
.
发生恐慌的 close_connection
函数不是从任何 Rust 异步上下文调用的,但它可能是从与创建它的线程不同的 C# 线程调用的。 AFAIS 在 运行 时间产生的所有期货都在 运行 时间被删除之前完成。
处理运行时间的正确方法是什么?
防锈代码:
pub struct MyConnection {
connection: MultiplexedConnection,
runtime: Runtime
}
#[no_mangle]
pub extern "C" fn create_connection() -> *const c_void {
let runtime = Runtime::new().unwrap();
let _runtime_handle = runtime.enter();
let connection = runtime.block_on(get_multiplexed_async_connection()).unwrap();
let connection_object = MyConnection {
connection, runtime
};
let connection_box = Box::new(connection_object);
Box::into_raw(connection_box) as *const c_void
}
#[no_mangle]
pub extern "C" fn close_connection(connection_ptr: *const c_void) {
let connection_box = unsafe {Box::from_raw(connection_ptr as * mut MyConnection)};
let _runtime_handle = connection_box.runtime.enter(); // I'm not sure this line is required. I get the same error regardless of whether it's here/
drop(connection_box); // here I get the error.
}
#[no_mangle]
pub extern "C" fn get(key: *const c_char, connection_ptr: *const c_void, callback: unsafe extern "C" fn(*const c_char) -> ()) {
let connection_box_cast = connection_ptr as usize;
let key_cstring = unsafe {CString::from_raw(key as *mut c_char)};
let key_cstring_clone = key_cstring.clone();
let connection_box = unsafe {Box::from_raw(connection_ptr as * mut MyConnection)};
let _runtime_handle = connection_box.runtime.enter();
connection_box.runtime.spawn(async move {
let key_bytes = key_cstring_clone.as_bytes();
let mut connection_box_internal = unsafe { Box::from_raw(connection_box_cast as * mut MyConnection) };
let result = connection_box_internal.connection.get(key_bytes).await.unwrap_or("".to_string());
std::mem::forget(connection_box_internal);
let result_c = CString::new(result).unwrap();
let ptr = result_c.as_ptr();
std::mem::forget(result_c);
unsafe { callback(ptr) };
});
std::mem::forget(key_cstring);
std::mem::forget(connection_box);
}
C#代码:
[DllImport("redis_rs_benchmark", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_connection")]
public static extern IntPtr CreateConnectionFfi();
[DllImport("redis_rs_benchmark", CallingConvention = CallingConvention.Cdecl, EntryPoint = "close_connection")]
public static extern void CloseConnectionFfi(IntPtr connection);
public delegate void StringAction(string arg);
[DllImport("test", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")]
public static extern void GetFfi(string key, IntPtr connection, StringAction callback);
static Task<string> GetFfiAsync(string key,IntPtr connection) {
var tsc = new TaskCompletionSource<String>();
GetFfi(key, connection, (result) => {
tsc.SetResult(result);
});
return tsc.Task;
}
async string test() {
var connection = CreateConnectionFfi();
var result = await GetFfiAsync("checkfoo32", connection);
CloseConnectionFfi(connection);
return result;
}
问题是因为在 C# 中我在 await
之后调用了 CloseConnectionFfi
,它是从内部 tokio 运行时线程调用的。
var result = await GetFfiAsync("checkfoo32", connection);
CloseConnectionFfi(connection);
一旦我将关闭委托给一个单独的线程,问题就消失了。
await Task.Run(() => CloseConnectionFfi(connection));
我正在使用一个 tokio 运行time,它被装箱并由 FFI 函数的指针返回。 运行time 用于 运行 futures,并且包装在 C# Task 中。
我可以自由地创建和使用 运行time,但是当我再次尝试从 FFI 函数中删除它时,出现以下错误:
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context
.
发生恐慌的 close_connection
函数不是从任何 Rust 异步上下文调用的,但它可能是从与创建它的线程不同的 C# 线程调用的。 AFAIS 在 运行 时间产生的所有期货都在 运行 时间被删除之前完成。
处理运行时间的正确方法是什么?
防锈代码:
pub struct MyConnection {
connection: MultiplexedConnection,
runtime: Runtime
}
#[no_mangle]
pub extern "C" fn create_connection() -> *const c_void {
let runtime = Runtime::new().unwrap();
let _runtime_handle = runtime.enter();
let connection = runtime.block_on(get_multiplexed_async_connection()).unwrap();
let connection_object = MyConnection {
connection, runtime
};
let connection_box = Box::new(connection_object);
Box::into_raw(connection_box) as *const c_void
}
#[no_mangle]
pub extern "C" fn close_connection(connection_ptr: *const c_void) {
let connection_box = unsafe {Box::from_raw(connection_ptr as * mut MyConnection)};
let _runtime_handle = connection_box.runtime.enter(); // I'm not sure this line is required. I get the same error regardless of whether it's here/
drop(connection_box); // here I get the error.
}
#[no_mangle]
pub extern "C" fn get(key: *const c_char, connection_ptr: *const c_void, callback: unsafe extern "C" fn(*const c_char) -> ()) {
let connection_box_cast = connection_ptr as usize;
let key_cstring = unsafe {CString::from_raw(key as *mut c_char)};
let key_cstring_clone = key_cstring.clone();
let connection_box = unsafe {Box::from_raw(connection_ptr as * mut MyConnection)};
let _runtime_handle = connection_box.runtime.enter();
connection_box.runtime.spawn(async move {
let key_bytes = key_cstring_clone.as_bytes();
let mut connection_box_internal = unsafe { Box::from_raw(connection_box_cast as * mut MyConnection) };
let result = connection_box_internal.connection.get(key_bytes).await.unwrap_or("".to_string());
std::mem::forget(connection_box_internal);
let result_c = CString::new(result).unwrap();
let ptr = result_c.as_ptr();
std::mem::forget(result_c);
unsafe { callback(ptr) };
});
std::mem::forget(key_cstring);
std::mem::forget(connection_box);
}
C#代码:
[DllImport("redis_rs_benchmark", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_connection")]
public static extern IntPtr CreateConnectionFfi();
[DllImport("redis_rs_benchmark", CallingConvention = CallingConvention.Cdecl, EntryPoint = "close_connection")]
public static extern void CloseConnectionFfi(IntPtr connection);
public delegate void StringAction(string arg);
[DllImport("test", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")]
public static extern void GetFfi(string key, IntPtr connection, StringAction callback);
static Task<string> GetFfiAsync(string key,IntPtr connection) {
var tsc = new TaskCompletionSource<String>();
GetFfi(key, connection, (result) => {
tsc.SetResult(result);
});
return tsc.Task;
}
async string test() {
var connection = CreateConnectionFfi();
var result = await GetFfiAsync("checkfoo32", connection);
CloseConnectionFfi(connection);
return result;
}
问题是因为在 C# 中我在 await
之后调用了 CloseConnectionFfi
,它是从内部 tokio 运行时线程调用的。
var result = await GetFfiAsync("checkfoo32", connection);
CloseConnectionFfi(connection);
一旦我将关闭委托给一个单独的线程,问题就消失了。
await Task.Run(() => CloseConnectionFfi(connection));