Sender cannot be shared between threads safely when using question mark operator

Viewed 49

Sending a struct containing a Sender through a channel and using the question mark to handle errors, Rust complains about Sender<..> not implementing Sync. Without the question mark, it doesn't.

So, is the Sync trait expected from the Sender? Not understanding the question mark operator in detail, I assume an anyhow::Error is being constructed - but why would the Sender end up in the error object?

(My application has one database thread that handles all queries and communicates via channels) Here's the code:

|db: Db| -> anyhow::Result<()> {
    let (res_tx, res_rx) =  mpsc::channel::<Result<Vec<Row>>>();

    db.send(database_handler::Request::Query {
        query: "select name from actions",
        params: Vec::new(),
        result: res_tx,
    })?; // dis be line 35
}

Here's the error:

error[E0277]: `Sender<Result<Vec<postgres::Row>, anyhow::Error>>` cannot be shared between threads safely
  --> src/events.rs:35:6
   |
35 |             })?;
   |               ^ `Sender<Result<Vec<postgres::Row>, anyhow::Error>>` cannot be shared between threads safely
   |
   = help: within `SendError<Request<'_>>`, the trait `Sync` is not implemented for `Sender<Result<Vec<postgres::Row>, anyhow::Error>>`
   = help: the following other types implement trait `FromResidual<R>`:
             <Result<T, F> as FromResidual<Result<Infallible, E>>>
             <Result<T, F> as FromResidual<Yeet<E>>>
note: required because it appears within the type `Request<'_>`
  --> src/database.rs:39:10
   |
39 | pub enum Request<'a> {
   |          ^^^^^^^
   = note: required because it appears within the type `SendError<Request<'_>>`
   = note: required because of the requirements on the impl of `From<SendError<Request<'_>>>` for `anyhow::Error`
   = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, SendError<Request<'_>>>>` for `Result<(), anyhow::Error>`

And these are the involved types:

type Db<'a> = Sender<database_handler::Request<'a>>;

type ParamsType<'a> = Vec<&'a (dyn ToSql + Sync)>;

pub enum Request<'a> {
  Query { query: &'static str, params: ParamsType<'a>, result: Sender<Result<Vec<Row>>> },
  QueryOne { query: &'static str, params: ParamsType<'a>, result: Sender<Result<Option<Row>>> },
  Execute { query: &'static str, params: ParamsType<'a>, result: Sender<Result<u64>> },
}
1 Answers

You are correct that an anyhow::Error is being constructed, but - from what? Since Sender::send() takes ownership of the value you give it, the error it returns when it fails to send includes the original value, allowing you to retry sending later or in a different manner. So your anyhow::Error gets constructed from SendError<Query> that contains a Query.

The Sync requirement comes from anyhow::Error itself being Send and Sync (because it's useful to move error to a different thread), so its From implementation must require the same of the source error. In your case the source error is SendError<Query> which is not Sync because it contains Query whose result field contains a (different) Sender, which not Sync.

Once aware of this, you can see the error message telling you this, too. The fix is to replace db.send(...)? with something like db.send(...).map_err(|_| anyhow!("receiver is gone")). Also, you'll need an explicit Ok(()) at the end of the closure.

Related