Search code examples
asynchronousrustfuturedeadlock

Getting deadlock inside match of async function


I'm getting a deadlock on the following example:

use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use futures::lock::Mutex;
use std::sync::Arc;

struct A{
    
}

impl A {
    pub async fn do_something(&self) -> std::result::Result<(), ()>{
        Err(())
    }
}

async fn lock_and_use(a: Arc<Mutex<A>>) {
    match a.clone().lock().await.do_something().await {
        Ok(()) => {
            
        },
        Err(()) => {
            //try again on error
            println!("trying again");
            a.clone().lock().await.do_something().await.unwrap();
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("begin");
    let a = Arc::new(Mutex::new(A{}));
    lock_and_use(a.clone()).await;
    println!("end");
    Ok(())
}

Playground

If I did this:

a.clone().lock().await.do_something().await;
a.clone().lock().await.do_something().await;

there would be no problem, the lock() dies on the same line it's created. I thought this principle would be the same for match. If you think about match, it locks the value, calls do_something, awaits on it, and then compares the value. It's true that do_something returns a future which capture the self, but when we await on it, it should discard the self. Why it still holds self? How can I solve this without cloning the result?


Solution

  • Yes:

    Temporaries live for the entire statement, never shorter.

    Cause code could be:

    {
        match self.cache.read() { // <-- direct pattern matching
            Ok(ref data) => Some(data)
            _ => None,
        }
    }.map(|data| {
        // use `data` - the lock better be held
    })
    

    Read this issue for more detail.

    So you need to lock outside your match statement:

    let x = a.clone().lock().await.do_something().await;
    match x {
        Ok(()) => {}
        Err(()) => {
            a.clone().lock().await.do_something().await.unwrap();
        }
    }