Search code examples
rustffirust-tokio

How to reuse Tokio runtime in Rust FFI library


I want to write a FFI wrapper for sn_api library, which contains async functions. It will be used in single-threaded non-async code written in Red.

I found, that the easy way is to use Runtime::new().unwrap().block_on(...) in every exported function, although it involves a lot of creating new Tokio runtimes and seem to be too heavy to be run on every call:

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)));
}

Is it possible to run all async functions on a common Runtime? I imagine it would require creating some singleton / global, but my library is compiled with crate-type = ["cdylib"], which seems not a good place for globals. What would be the best approach?


Solution

  • I've decided for an approach, where I create a Tokio Runtime, and then pass it to every FFI function call containing async code:

    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();
        }
    }