Passing a ffi struct to actix::web data

I have created one ffi library and I want to use it in my main project which is a web service using actix web.

Basically the ffi library struct which i want to use in my project is as following

pub struct FfiWrapper {
    task_ptr: *const FfiTask,
}

impl FfiWrapper {
    pub fn new() -> Result<Self, ()> {
        unsafe {
            let task_ptr = bindgen_bindings::CreateTask();
            if task_ptr.is_null() {
                Err(())
            } else {
                Ok(Self { task_ptr })
            }
        }
    }

    pub fn start_task(&self) -> i32 {
        let res = unsafe { bindgen_bindings::StartTask(self.task_ptr) };
        res
    }
}

impl Drop for FfiWrapper {
    fn drop(&mut self) {
        unsafe {
            bindgen_bindings::DeleteTask(self.task_ptr);
        }
    }
}

The above library code is written by me and can be modified if required
where bindgen_bindings is binding created by bindgen for the c library i am using
In my main actix web project I want to pass the FfiWrapper so that I can use it when new request hits one of my endpoint ffi_endpoint like so

    let ffi_wrapper = Data::new(Mutex::new(tts_engine));

    let server = HttpServer::new(|| App::new().service(ffi_endpoint).app_data(ffi_wrapper))
        .listen(listener)?
        .run();

But I am getting following error

`*const bindgen_sys::bindgen_bindings::FfiTask_T` cannot be sent between threads safely
within `FfiWrapper`, the trait `Send` is not implemented for `*const bindgen_sys::bindgen_bindings::FfiTask_T`, which is required by `{closure@src/startup.rsSend`
required for `Mutex<EngineWrapper>` to implement `Sync`
required for `Arc<Mutex<EngineWrapper>>` to implement `Send`

My bindgen bindings which were created

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FfiTask_T {
    _unused: [u8; 0],
}
pub type FfiTask = FfiTask_T;
extern "C" {
    pub fn CreateTask() -> *const FfiTask;
}
extern "C" {
    pub fn DeleteTask(ptask: *const FfiTask);
}

My C library header file function definitions

    struct FfiTask_T;
    typedef struct FfiTask_T FfiTask;
    const FfiTask* CreateTask();
    int    StartTask(const FfiTask* pTask);
    void   DeleteTask(const FfiTask *ptask);

I am new to rust and ffi in general. Can someone please help me with above query.
Thanks

Rust doesn't know whether structs from C code are thread-safe or not, and by default assumes that anything containing a raw C pointer isn't thread-safe.

If you know that C functions using the task can be called from any thread, tell it to Rust by implementing Send

unsafe impl Send for FfiWrapper {}

Send allows moving it to another thread, but still doesn't allow using it from many threads at the same time. That's usually compatible with C code, as long as C doesn't rely on things like thread-local storage.

If C code is completely thread-safe and has its own locks, you can impl Sync too.

3 Likes

you are using a raw pointer, which is not Send. if you are sure the ffi library is thread safe, you can manually mark your wrapper type as Send (note Send is an unsafe trait, so you must not implement it unless you are really sure):

unsafe impl Send for FfiWrapper {}

if your type cannot be made thread safe, the wrapper type can use an actor-like design.

1 Like

Can you elaborate on the actor like design please if possible. My type is not thread safe

basically, you use a dedicated thread as the owner of the data, and APIs that need to be accessed from different thread are implemented using some message passing mechanism, such as channels. in some sense, you can think an actor like a server. actors are commonly used (e.g. the Erlang virtual machine) as building blocks for complex concurrent system.

search key words like "actor model", "actor pattern", or "erlang actor" etc, to learn more about the concept.

that said, more than likely, you don't actually need the actor model, especially for small piece of data. there are plenty of ways to make a thread safe wrapper type in rust. for instance, you can use Arc<Mutex<FfiTask> to replace the raw pointer.

2 Likes

Arc and Mutex won't help at all if the type isn't Send. They require the type to be Send.

How to convert between const* FfiTask and Arc<Mutex<FfiTask>> when interacting with the library
I am trying following

            let task_ptr: *const FfiTask = ffi_bindings::CreateTask();
            let arc_task =  Arc::from_raw(Mutex::new(task_ptr)); 

The following seems to work for converting from Arc<Mutex> to const*

        let res = unsafe { ffi_bindings::StartTask(Arc::into_raw(self.task_ptr.clone()) as *const ffiTask) };