How to send and share a type that is !Send and !Sync?

Hi there!

I have some objects from third-party libraries that are neither send nor sync, yet I need to use them from other threads. Blocking is fine, locking is fine, stopping the world is fine.

Wrapping the type into a Mutex<T> makes it shareable. Wrapping the mutex into an arc Arc<Mutex<T>> does not change anything, it can still not be sent.

  1. How can I send the value?
  2. Have I misunderstood Arc<Mutex<T>>?

Here is the complete function:

pub fn run_server(compute: impl Fn(Request) -> Response) {
    // compute cannot be shared or synced because it contains types from third party libraries 
    let compute = Arc::new(Mutex::new(compute));

    // the passed callback must be send + sync
    rouille::start_server_with_pool("localhost:3000", Some(1), move |request| {
        
        let compute = compute.lock().unwrap();
        let answer = compute(request);

        answer
    });
}

Thanks for your time,
Johannes

Wrapping in a Mutex can make a Send + !Sync type Send + Sync, but if the underlying type is not Send, there's nothing a Mutex can do. An Arc can share types, but if the underlying type is not Sync, it can't share it across threads.

More or less the only approach to share !Send + !Sync types is to not share them, and instead simulate it by having other threads use message passing to tell the thread do stuff to the object.

3 Likes

Thanks for the clarification.

I can't see how to implement your suggestion it with this specific architecture. I guess I'm going to code up a simple non-multithreaded server myself, with TCP and an HTTP parsing library, as my use case is really not that complicated and I need it to "just work" (but also high performance crates from Rust).

Thanks again, for your time.

I think the suggestion was to use channels to synchronize between threads.

Form your pool you can send message received from the thread where you have your !Send + !Sync.

As there are a lot of crates that implement a simple web server, you can check the code of those as an example on how to implement this stuff.

For example, you can have a look on this repo: GitHub - crossbeam-rs/crossbeam: Tools for concurrent programming in Rust for examples for concurrent tasks.

For web servers you can have a look of hyper: https://crates.io/crates/hyper or warp: https://crates.io/crates/warp

1 Like

The form of communication would be equivalent to Fn(Request) -> Response. How can I do this responding mechanism? That's certainly not what the std::sync::mpsc::channels, are meant for?

Well, you can send a pair of (Request, Receiver<Response>), if this helps.

I found a solution that avoided multithreading in general using tiny_http. Using channels would be the better architecture in general, but in this case would have been overly complicated and not required. Thanks anyways for your suggetions :slight_smile:

3 Likes

I realize you've already found a solution, but for future reference, I think you probably never needed !Send objects in the first place. It is likely that you just need to add a Send trait bound to your function signature.

Fn(Request) -> Response is not an object type, but a trait, meaning, essentially, an interface that multiple different types can implement. The impl keyword means that the function must accept any type that "implements" that trait. (For completeness: it also means that the exact type is determined at compile time rather than at runtime.)

Send and Sync are also traits. But, because it's possible for some types to implement Fn(Request) -> Response without implementing Send, your function parameter, impl Fn(Request) -> Response, must be usable with types that are !Sync and/or !Send.

But unless you actually need to call run_server with a !Send type, this restriction is unnecessary! So you can remove it by requiring compute to implement both the Fn... trait and the Send trait. The syntax for this uses + to specify multiple traits:

pub fn run_server(compute: impl Fn(Request) -> Response + Send) {
...

I think your analysis is probably correct! But it's worth mentioning that there is one common !Send type: Rc. If the library uses Rcs to manage data and that can't be changed, then it could really be a problem.

1 Like

Yes, this would be the best solution. I tried that first, but unfortunately it didn't work because the closure needed to capture variables that neither Send nor Sync. I honestly don't know why those types are not Send, I have not checked it, as they are defined in a third party library, which I can't change right now. For future visitors this approach should be tried first :slight_smile:

Out of curiosity, which types are they?

The GPT-2 text generator from Rust-BERT and a patricia tree :slight_smile:

This is cool: the documentation shows whether the type is Sync and/or Send or not: rust_bert::gpt2::GPT2LMHeadModel - Rust

PatriciaMap is Send if its type argument is Send, but GPT2LMHeadModel is !Send.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.