Holding a MutexGuard across an .await

You might write code that looks like this:

  1. use std::sync::Mutex;
  2. async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
  3. let mut lock = mutex.lock().unwrap();
  4. *lock += 1;
  5. do_something_async().await;
  6. } // lock goes out of scope here

When you try to spawn something that calls this function, you will encounter the following error message:

  1. error: future cannot be sent between threads safely
  2. --> src/lib.rs:13:5
  3. |
  4. 13 | tokio::spawn(async move {
  5. | ^^^^^^^^^^^^ future created by async block is not `Send`
  6. |
  7. ::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.21/src/task/spawn.rs:127:21
  8. |
  9. 127 | T: Future + Send + 'static,
  10. | ---- required by this bound in `tokio::task::spawn::spawn`
  11. |
  12. = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, i32>`
  13. note: future is not `Send` as this value is used across an await
  14. --> src/lib.rs:7:5
  15. |
  16. 4 | let mut lock = mutex.lock().unwrap();
  17. | -------- has type `std::sync::MutexGuard<'_, i32>` which is not `Send`
  18. ...
  19. 7 | do_something_async().await;
  20. | ^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `mut lock` maybe used later
  21. 8 | }
  22. | - `mut lock` is later dropped here

This happens because the std::sync::MutexGuard type is not Send. This means that you can’t send a mutex lock to another thread, and the error happens because the Tokio runtime can move a task between threads at every .await. To avoid this, you should restructure your code such that the mutex lock’s destructor runs before the .await.

  1. // This works!
  2. async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
  3. {
  4. let mut lock = mutex.lock().unwrap();
  5. *lock += 1;
  6. } // lock goes out of scope here
  7. do_something_async().await;
  8. }

Note that this does not work:

  1. use std::sync::Mutex;
  2. // This fails too.
  3. async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
  4. let mut lock = mutex.lock().unwrap();
  5. *lock += 1;
  6. drop(lock);
  7. do_something_async().await;
  8. }

This is because the compiler currently calculates whether a future is Send based on scope information only. The compiler will hopefully be updated to support explicitly dropping it in the future, but for now, you must explicitly use a scope.

Note that the error discussed here is also discussed in the Send bound section from the spawning chapter.

You should not try to circumvent this issue by spawning the task in a way that does not require it to be Send, because if Tokio suspends your task at an .await while the task is holding the lock, some other task may be scheduled to run on the same thread, and this other task may also try to lock that mutex, which would result in a deadlock as the task waiting to lock the mutex would prevent the task holding the mutex from releasing the mutex.

We will discuss some approaches to fix the error message below:

Restructure your code to not hold the lock across an .await

We have already seen one example of this in the snippet above, but there are some more robust ways to do this. For example, you can wrap the mutex in a struct, and only ever lock the mutex inside non-async methods on that struct.

  1. use std::sync::Mutex;
  2. struct CanIncrement {
  3. mutex: Mutex<i32>,
  4. }
  5. impl CanIncrement {
  6. // This function is not marked async.
  7. fn increment(&self) {
  8. let mut lock = self.mutex.lock().unwrap();
  9. *lock += 1;
  10. }
  11. }
  12. async fn increment_and_do_stuff(can_incr: &CanIncrement) {
  13. can_incr.increment();
  14. do_something_async().await;
  15. }

This pattern guarantees that you wont run into the Send error, because the mutex guard does not appear anywhere in an async function.

Spawn a task to manage the state and use message passing to operate on it

This is the second approach mentioned in the start of this chapter, and is often used when the shared resource is an I/O resource. See the next chapter for more details.

Use Tokio’s asynchronous mutex

The tokio::sync::Mutex type provided by Tokio can also be used. The primary feature of the Tokio mutex is that it can be held across an .await without any issues. That said, an asynchronous mutex is more expensive than an ordinary mutex, and it is typically better to use one of the two other approaches.

  1. use tokio::sync::Mutex; // note! This uses the Tokio mutex
  2. // This compiles!
  3. // (but restructuring the code would be better in this case)
  4. async fn increment_and_do_stuff(mutex: &Mutex<i32>) {
  5. let mut lock = mutex.lock().await;
  6. *lock += 1;
  7. do_something_async().await;
  8. } // lock goes out of scope here