3

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?

Maciej Łoziński
  • 812
  • 1
  • 10
  • 16

4 Answers4

3

Use a static variable to keep the runtime accessible to function calls.

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()
});
Congyu WANG
  • 61
  • 1
  • 4
1

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();
    }
}
Maciej Łoziński
  • 812
  • 1
  • 10
  • 16
  • Is runtime actually C-ABI compatible, though? I guess Box helps with that... – Raskell Feb 10 '22 at 04:20
  • Also, do look into https://docs.rs/async-ffi/latest/async_ffi/ I think it could help you get better performance out of your application. – Raskell Feb 10 '22 at 14:54
0

I faced the same issue. Here is my cut: 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()
}
  • Unfortunately, tokio::runtime::Handle is [not FFI-Safe](https://docs.rs/tokio/latest/src/tokio/runtime/handle.rs.html#17-19) and therefore it's ABI could suddenly change (even between two executions with the same compiler version) – mineichen Jun 05 '22 at 10:46
-3

Try this.

From this:

#[tokio::main]
async fn main() {
    println!("hello");
}

Transformed into:

fn main() {
    let mut rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        println!("hello");
    })
}

Reference: https://tokio.rs/tokio/tutorial/hello-tokio#async-main-function

clintbugs
  • 1
  • 1
  • 1
  • 1
    Thanks for help. Although this does not solve my problem, because my code is a library, so I don't have `main()` function. Also, I don't want to create a `Runtime` in every function of my library. – Maciej Łoziński Sep 07 '21 at 10:57