async function: the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, Client>`

Viewed 4660

I reached a strange error while using recursion inside an async.

I can use client inside the async. However, if I call do_something again it complains about std::sync::MutexGuard<'_, Client> not being send. I think it's trying to send c to another thread. I thought do_something executed in a thread, and do_something(client.clone()).await in another. I don't see why c is moving from the async thread to this new thread.

use std::sync::{Arc, Mutex};
use std::future::Future;
use futures::future::{BoxFuture, FutureExt};

struct Client{
}

impl Client {}

fn do_something<'a>(
        client: Arc<Mutex<Client>>,
) -> BoxFuture<'a, std::result::Result<(), ()>> {
    async move {
        let c = client.lock().unwrap();
        do_something(client.clone()).await;
        Ok(())
    }.boxed()
}

fn main() {
    let c = Arc::new(Mutex::new(Client{}));
    do_something(c.clone());
}

Error:

error: future cannot be sent between threads safely
  --> src/main.rs:17:7
   |
17 |     }.boxed()
   |       ^^^^^ future created by async block is not `Send`
   |
   = help: within `impl futures::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, Client>`
note: future is not `Send` as this value is used across an await
  --> src/main.rs:15:9
   |
14 |         let c = client.lock().unwrap();
   |             - has type `std::sync::MutexGuard<'_, Client>` which is not `Send`
15 |         do_something(client.clone()).await;
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `c` maybe used later
16 |         Ok(())
17 |     }.boxed()
   |     - `c` is later dropped here

Playground

1 Answers

There's two problems here, both stemming from the fact that the standard library Mutex is designed to be used with regular blocking code, rather than async code:

  • Locking the mutex will block the thread, which is generally something you want to avoid in async code as it can prevent other tasks from running.
  • As seen in the compiler error, the mutex guard that you get by locking it can't be shared between threads safely, so it won't compile anyways.

Both of these issues can be fixed by using a Mutex designed for async code. Since you're already using something from the futures crate, you can just use futures::lock::Mutex, changing client.lock().unwrap() to client.lock().await. This version of the lock will yield control back to the task executor when it needs to wait rather than blocking the thread, and will also allow it to be shared between threads if necessary.

The complete version of the code looks like this - however, it's not very useful because you're never actually executing the future returned by do_something (note that you're not doing anything with the returned future) and even if you did, it would immediately deadlock due to the recursion:

use std::sync::Arc;
use std::future::Future;
use futures::future::{BoxFuture, FutureExt};
use futures::lock::Mutex;

struct Client {}

impl Client {}

fn do_something<'a>(
        client: Arc<Mutex<Client>>,
) -> BoxFuture<'a, std::result::Result<(), ()>> {
    async move {
        let c = client.lock().await;
        do_something(client.clone()).await;
        Ok(())
    }.boxed()
}

fn main() {
    let c = Arc::new(Mutex::new(Client {}));
    do_something(c.clone());
}

Playground

Related