Why did moving variables out of HashMap's insert method scope solve a deadlock?

Viewed 45

OB_HM is Arc<Mutex<HashMap<...>>>

This caused the deadlock at very first iteration:

let mut OB_HM__:HashMap<u8, (f64, f64, f64, f64)> = HashMap::new();
    for i in &p {
        OB_HM__.insert(
            *i, 
            (
                OB_HM.lock().unwrap()[i].0, OB_HM.lock().unwrap()[i].1 
                R[&i].0,
                R[&i].1
            )
        );
    };
    let mut OB_HM = OB_HM__;

And this solved it:

let mut OB_HM__:HashMap<u8, (f64, f64, f64, f64)> = HashMap::new();
    for i in &p {
        let x = OB_HM.lock().unwrap()[i].0;
        let y = OB_HM.lock().unwrap()[i].1;
        OB_HM__.insert(
            *i, 
            (
                x, y, 
                R[&i].0,
                R[&i].1
            )
        );
    };
    let mut OB_HM = OB_HM__;

Why so? I had mere intuition but I need to understand the internals behind it. I'm guessing it has to do with how Rust creates tuples or how the method insert works?

And a side question about insert - why does it require ; EOL when it's the only instruction inside a loop?

1 Answers

This caused the deadlock at very first iteration [...] Why so?

Shortly, because of temporary lifetime extension.

Generally1, OB_HM.lock() create a new object, a mutex guard. This is a temporary object and it implements Deref trait (allowing autoderef).

De-referencing the mutex guard creates a borrow of the inner object (i.e., the HashMap) that extends the lifetime of the guard itself.

Therefore, the mutex guard is not dropped until the end of entire function call (i.e., insert) and so the Mutex is not unlocked. Since you have two locks in your function call expression, that generates a deadlock.

Maybe a toy-example might help to understand: example.

From the above,

foo(mutex.lock().value(),  // <--- first lock
    mutex.lock().value()); // <--- second lock

produces a deadlock because both locks are released only after the entire expression foo (the function call) has been evaluated.

Indeed the output:

MutexLock!       // <--- First lock
MutexLock!       // <--- Second lock
 > !DEADLOCK! <  // <--- Two interleaved locks! Deadlock!
foo: Call!       
                 // <--- Now the function ends
MutexUnlock      // <--- And only at the end the mutex are unlocked!
MutexUnlock

Note that, this is independent from tuples.

The "working" solution works because:

let x = OB_HM.lock().unwrap()[i].0;
let y = OB_HM.lock().unwrap()[i].1;

the temporary expression of lock lasts for the entire statement. So after x has been assigned, the temporary lock is released. Allowing y to acquire it without deadlocks.


And a side question about insert - why does it require ; EOL

Because of rust syntax I guess.

From here: loop syntax

loop BlockExpression

where BlockExpression is defined as zero or more statements.

Expression are accepted only as expression-statements (that is, with ;).


  1. It might depend on your Mutex type.
Related