27

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 Answer 1

42

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

3
  • 1
    The async{} runs in a thread. c never exists this thread, it's created and destructed inside of it. So why I get the error that it cannot be sent between thread safely?
    – Gatonito
    Commented Apr 27, 2021 at 5:46
  • 5
    When you use async {}, the rust compiler automatically converts it into a Future by making a state machine, where each .await causes it to wait for something to happen before transitioning to the next state to continue running. When this wait occurs, control is passed to the executor running your future, and at this point, it could be sent to another thread (e.g. in a multithreaded runtime), so any variables that live across .await points need to be Send if the future itself is to be Send - which BoxFuture requires. Commented Apr 27, 2021 at 5:50
  • 5
    If you're using tokio, tokio::sync::Mutex has a similar API. Commented Jan 17, 2022 at 21:03

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.