How to pass &self through thread barrier in proper way?

I'm trying to port some threading C++ code to rust, hitting strange issues on my way.

I have a Server struct defined as:

pub struct Server {
    server: *mut ffi::server,
    queue: Queue,
    methods: Vec<SyncRequest>,
    workers: ::threadpool::ThreadPool,
    running_callbacks: Mutex<usize>,
}

It wraps some bare c++ pointers and a bunch of other things but is pretty much immutable at that point (all modifications throughout server lifetime happen inside SyncRequest structs).

The code works reasonably fine if used in one thread, but getting this to work over a threadpool::ThreadPool is surprisingly awful.

First, I want to keep Server implementation details, such as its threading internal and not exposed to the library user. The basic use case would be for user to call server.start() and that would kick off Server::run_rpc() in thread pool as required, while blocking on a shared mutex. start() is guaranteed to not return until any worker thread is alive, so I guess it's pretty sane to pass &self through into thread.

So, to the code that works:

fn schedule_callback(&mut self) {
    {
        let mut running_callbacks = self.running_callbacks.lock().unwrap();
        *running_callbacks += 1;
    }

    // turn Server into something Send-able
    let selfptr: *mut ::libc::types::common::c95::c_void = unsafe { ::std::mem::transmute(&self) };
    // which *mut c_void isn't...
    let selfiptr: usize = selfptr as usize;

    self.workers.execute(move ||{
        // at this point of time I have no idea where the *_self lives. Double pointer here is confusing
        let _self: &mut &mut Server = unsafe { ::std::mem::transmute(selfiptr) };
        _self.run_rpc();
    });
}

A bunch of problems I got on the way:

Server-owned insides don't implement Send and I have vague ideas on how can I even implement Send for *mut c_void.

I can't wrap Server in Arc anyway, as that will require to make the method static, and expose Arc usage to public API.

The way I wrote it is basically fitting C++ way of thinking into rust, looks horrible and code is twice as big. What would be the idiomatic way to solve this problem?

I can't say that I fully understand the problem, but if you are sure that you can send Server, you can flag it as such. Send and Sync - The Rustonomicon

unsafe impl Send for Server {}

Note that this is unsafe for a reason :).

1 Like

I actually tried that, but I'm not sure how to pass self into closure:

self.workers.execute(||{
    self.run_rpc();
});

fails with

src/server/server.rs:169:30: 171:10 error: cannot infer an appropriate lifetime for capture of `self` by closure due to conflicting requirements [E0495]
src/server/server.rs:169         self.workers.execute(||{
src/server/server.rs:170             self.run_rpc();
src/server/server.rs:171         });

I guess I need to wrap Server in Arc first?

pub fn schedule_callback(server: Arc<Server>) {
    {
        let mut running_callbacks = server.running_callbacks.lock().unwrap();
        *running_callbacks += 1;
    }

    let inner = server.clone();
    server.workers.execute(move ||{
        inner.run_rpc();
    });
}

fails with

src/server/server.rs:170:24: 172:11 error: the trait `core::marker::Sync` is not implemented for the type `*mut ffi::Struct_server` [E0277]

and all the relevant structs (while I still have unsafe impl Send for Server {}).

Proof of concept breakage: Rust Playground

1 Like

Arc<T> requires T: Send + Sync to become Send itself because it doesn't synchronize access to T, so T has to handle shared access somehow. Mutex provides a way to do that and Arc<Mutex<T>> only wants T: Send.

2 Likes

Right, so I refactored my method from pub fn schedule_callback(&mut self) into pub fn schedule_callback(server: Arc<Server>) now and it works, but now the implementation detail (Arc) is exposed to public API, and schedule_callback() caller must use same interface, that is be a static method on struct.

The only way I see to fix this is to wrap Server, now renamed to ServerImpl into another class:

pub struct Server (Arc<ServerImpl>)

that will proxy all the public API, and ServerImpl will have all static methods taking Arc<Server> or &Arc<Server> as appropriate.

This does sound like an awful mess and the initial approach with casting Server to *mut c_void starts to look better. Can you advice on a proper way to design such an interface to reduce support code?