如何在 Rust FFI 库中重用 Tokio 运行时

3
我想为sn_api库编写FFI包装器,其中包含async函数。它将用于在Red中编写的单线程非异步代码中。
发现,简单的方法是在每个导出函数中使用Runtime::new().unwrap().block_on(...),尽管这涉及创建许多新的Tokio运行时,似乎太重以至于无法在每次调用时运行:
use std::os::raw::c_char;
use std::ffi::{CString, CStr};
use sn_api::{BootstrapConfig, Safe};
use tokio::runtime::Runtime;

#[no_mangle]
pub extern "C" _safe_connect(ptr: *const Safe, bootstrap_contact: *const c_char) {
    assert!(!ptr.is_null());
    let _safe = unsafe {
        &*ptr
    };

    let bootstrap_contact = unsafe {
        CStr::from_ptr(bootstrap_contact)
    }

    let mut bootstrap_contacts = BootstrapConfig::default();
    bootstrap_contacts.insert(bootstrap_contact.parse().expect("Invalid bootstrap address"));

    // how to reuse the Runtime in other functions?
    Runtime::new().unwrap().block_on(_safe.connect(None, None, Some(bootstrap_contacts)));
}

是否可能在一个共同的Runtime上运行所有异步函数?我想这需要创建一些单例/全局变量,但我的库是使用crate-type = ["cdylib"]编译的,这似乎不是全局变量的好地方。最好的方法是什么?
4个回答

3
使用静态变量来保持运行时对函数调用的可访问性。
use once_cell::sync::Lazy;
use tokio::runtime::{self, Runtime};

static RUNTIME: Lazy<Runtime> = Lazy::new(|| {
    runtime::Builder::new_multi_thread()
        .enable_io()
        .build()
        .unwrap()
});

或者使用一个线程本地的单线程运行时。 - Chayim Friedman

1
我决定采取一种方法,创建一个Tokio 运行时,然后将其传递给包含异步代码的每个FFI函数调用:
use std::os::raw::c_char;
use std::ffi::{CString, CStr};
use sn_api::{BootstrapConfig, Safe};
use tokio::runtime::Runtime;

#[no_mangle]
pub extern "C" fn init_runtime() -> *mut Runtime {
    Box::into_raw(Box::new(Runtime::new().unwrap()))
}

#[no_mangle]
pub extern "C" _safe_connect(rt_ptr: *mut Runtime, safe_ptr: *mut Safe, bootstrap_contact: *const c_char) {
    assert!(!safe_ptr.is_null());
    assert!(!rt_ptr.is_null());

    let bootstrap_contact = unsafe {
        CStr::from_ptr(bootstrap_contact)
    }
    let mut bootstrap_contacts = BootstrapConfig::default();
    bootstrap_contacts.insert(bootstrap_contact.parse().expect("Invalid bootstrap address"));

    unsafe {
        let _safe = &mut *safe_ptr;
        let rt = &mut *rt_ptr;
        rt.block_on(_safe.connect(None, None, Some(bootstrap_contacts))).unwrap();
    }
}

运行时实际上是C-ABI兼容的吗?我猜Box可以帮助解决这个问题... - Raskell
另外,你也可以查看一下https://docs.rs/async-ffi/latest/async_ffi/。我认为它可以帮助你提升应用程序的性能。 - Raskell

0

我遇到了同样的问题。这是我的解决方案:export-tokio-to-lib

plugin.rs:

use async_ffi::{FfiFuture, FutureExt};
use tokio::runtime::Handle;

/// # Safety
#[no_mangle]
pub unsafe extern "C" fn test(arg: f32, handle: *const Handle) -> FfiFuture<safer_ffi::String> {
    let handle = &*handle;

    async move {
        let _enter = handle.enter();
        tokio::time::sleep(std::time::Duration::from_secs_f32(arg)).await;

        format!("slept {arg} secs").into()
    }
    .into_ffi()
}

很遗憾,tokio::runtime::Handle不是FFI安全的(https://docs.rs/tokio/latest/src/tokio/runtime/handle.rs.html#17-19),因此它的ABI可能会突然改变(即使是在相同编译器版本下的两次执行之间)。 - mineichen

-3

1
感谢您的帮助。虽然这并没有解决我的问题,因为我的代码是一个库,所以我没有main()函数。而且,我也不想在每个函数中都创建一个Runtime - Maciej Łoziński

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接