Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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

like image 725
Gatonito Avatar asked Apr 27 '21 05:04

Gatonito


People also ask

Can async methods be used as a trait object?

Traits with async methods can be used as trait objects as long as they meet the usual requirements for dyn – no methods with type parameters, no self by value, no associated types, etc. The one wrinkle is in traits that provide default implementations of async methods.

What is sync trait in C++?

This trait is automatically implemented when the compiler determines it’s appropriate. The precise definition is: a type T is Sync if and only if &T is Send. In other words, if there is no possibility of undefined behavior (including data races) when passing &T references between threads.

Do all async traits need Dyn future + send?

Not all async traits need futures that are dyn Future + Send. To avoid having Send and Sync bounds placed on the async trait methods, invoke the async trait macro as # [async_trait (?Send)] on both the trait and the impl blocks. Be aware that async fn syntax does not allow lifetime elision outside of & and &mut references.

Why can't I lock the mutex in async?

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.


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

like image 155
apetranzilla Avatar answered Nov 03 '22 13:11

apetranzilla