I stumbled upon a deadlock condition when using Tokio:
use tokio::time::{delay_for, Duration};
use std::sync::Mutex;
#[tokio::main]
async fn main() {
let mtx = Mutex::new(0);
tokio::join!(work(&mtx), work(&mtx));
println!("{}", *mtx.lock().unwrap());
}
async fn work(mtx: &Mutex<i32>) {
println!("lock");
{
let mut v = mtx.lock().unwrap();
println!("locked");
// slow redis network request
delay_for(Duration::from_millis(100)).await;
*v += 1;
}
println!("unlock")
}
Produces the following output, then hangs forever.
lock
locked
lock
According to the Tokio docs, using std::sync::Mutex
is ok:
Contrary to popular belief, it is ok and often preferred to use the ordinary Mutex from the standard library in asynchronous code.
However, replacing the Mutex
with a tokio::sync::Mutex
will not trigger the deadlock, and everything works "as intended", but only in the example case listed above. In a real world scenario, where the delay is caused by some Redis request, it will still fail.
I think it might be because I am actually not spawning threads at all, and therefore, even though executed "in parallel", I will lock on the same thread as await just yields execution.
What is the Rustacean way to achieve what I want without spawning a separate thread?