0

I have a program that is largely synchronous. Only for certain operations, it needs to perform async calls from a private library. So I'm building a new tokio runtime on which I spawn some worker task, and then pass the data from that task to my sync world using a channel, which calls recv() in a blocking manner.

Greatly simplified, the problematic part of the code looks like this:

use std::sync::mpsc::{channel, Sender};

struct Worker {
    chan: Sender<bool>
}

impl Worker {
    async fn do_work(&self) {
        loop {
            // do some more async work
            self.chan.send(true).unwrap();
        }
    }
}

fn main() {
    let (tx, rx) = channel::<bool>();
    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();
    
    let _ = rt.spawn(async move {
        let worker = Worker { chan: tx };
        worker.do_work().await;
    });
    
    println!("received {}", rx.recv().unwrap());
}

On playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=156e86c82839ef9d2a50fbcb8a67b412

But this code does not compile, and the relevant message is:

within `Worker`, the trait `Sync` is not implemented for `std::sync::mpsc::Sender<bool>`
 note: future is not `Send` as this value is used across an await

The channel is only passed to one thread, and cloning the sender like in this question does not solve it. Similarly, using an async channel is not an option since the receiver is sync. Instead it might have to do with how it is contained within the worker object, as the error message suggests. But in my case, the worker object needs to do other things, and call the method that calls send() multiple times, among some other async operations. It needs to be wrapped in an object since it is passed to another library function (it is always moved, not passed by reference though).

What can I do to make this work?

Boris Mulder
  • 308
  • 2
  • 14

1 Answers1

1

The hard way

Never hold a reference to the channel or the worker between await points. For example, take self instead of &self in your code.

The easy way

Use an async channel. tokio's channels have a blocking_recv() method you can use in synchronous contexts.

Or use a different channel implementation, such as crossbeam's, whose Sender is Sync.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • This makes me wonder why `std::sync:mpsc::channel` exists at all. Is there anything it does better than crossbeam? And didn't they copy it in to fix that horrific bug a while back, or am I remembering incorrectly? – Kevin Anderson Apr 03 '23 at 16:53
  • 1
    @KevinAnderson Yes, they copied their implementation, but left the API as-is although there were talkings about relaxing it in the future. Some indeed think that including a channel implementation in std was a mistake, but now it's too late to fix :) – Chayim Friedman Apr 03 '23 at 16:54
  • I also came up with taking self instead of &self, but it still gave me the same error. Using a tokio channel did solve it for me. I think it is strange that you cannot pass the std Sender to threads. – Boris Mulder Apr 04 '23 at 08:01
  • @BorisMulder In the code you provided using `self` works. I don't know your actual code, it may not be enough there. – Chayim Friedman Apr 04 '23 at 12:53
  • In my case it wasn't, because the Sender was contained in a sub-struct that was passed by reference across multiple `await`s. Using another channel implementation did solve it, though. – Boris Mulder Apr 06 '23 at 13:50