Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

while let doesn't work as expected with mutex #78390

Closed
hicqu opened this issue Oct 26, 2020 · 9 comments
Closed

while let doesn't work as expected with mutex #78390

hicqu opened this issue Oct 26, 2020 · 9 comments

Comments

@hicqu
Copy link

hicqu commented Oct 26, 2020

I tried this code:

use std::sync::Mutex;

fn main() {
    let lock = Mutex::new(vec![1; 10]);
    while let Some(x) = lock.lock().unwrap().pop() {
        lock.lock().unwrap().push(x);
        println!("the lock works as expected");
    }
}

I expected to see this happen: It can print the message, which means lock it again successfully

Instead, this happened: Nothing is print, which means dead lock

Meta

rustc --version --verbose:

rustc 1.49.0-nightly (b1496c6e6 2020-10-18)
binary: rustc
commit-hash: b1496c6e606dd908dd651ac2cce89815e10d7fc5
commit-date: 2020-10-18
host: x86_64-unknown-linux-gnu
release: 1.49.0-nightly
LLVM version: 11.0
Backtrace

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0x00007f3cac6e954d in __lll_lock_wait () from /lib64/libpthread.so.0

Thread 1 (Thread 0x7f3cacf2c800 (LWP 6625)):
#0  0x00007f3cac6e954d in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f3cac6e4e9b in _L_lock_883 () from /lib64/libpthread.so.0
#2  0x00007f3cac6e4d68 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000561e98eb4457 in rw_dead_test::main::hf73ccab8a36ff1e0 ()
#4  0x0000561e98eb4743 in std::sys_common::backtrace::__rust_begin_short_backtrace::h2355815e7a8047a2 ()
#5  0x0000561e98eb4759 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h5b53088bf8366de1 ()
#6  0x0000561e98ec9d87 in call_once<(),Fn<()>> () at /rustc/b1496c6e606dd908dd651ac2cce89815e10d7fc5/library/core/src/ops/function.rs:259
#7  do_call<&Fn<()>,i32> () at library/std/src/panicking.rs:381
#8  try<i32,&Fn<()>> () at library/std/src/panicking.rs:345
#9  catch_unwind<&Fn<()>,i32> () at library/std/src/panic.rs:382
#10 std::rt::lang_start_internal::h2c157bf657c6892a () at library/std/src/rt.rs:51
#11 0x0000561e98eb4732 in main ()
[Inferior 1 (process 6625) detached]

@hicqu hicqu added the C-bug Category: This is a bug. label Oct 26, 2020
@hicqu hicqu changed the title while let doesn't work as expectly with mutex while let doesn't work as expected with mutex Oct 26, 2020
@tesuji
Copy link
Contributor

tesuji commented Oct 26, 2020

This is an infinite loop, not a Rust compiler bug.
Equivalent code without mutex. It will hang playground stdout so be careful.

fn main() {
    let mut lock = vec![1; 10];
    while let Some(x) = lock.pop() {
        lock.push(x);
        println!("the lock works as expected");
    }
}

@rustbot rustbot removed the C-bug Category: This is a bug. label Oct 26, 2020
@hicqu
Copy link
Author

hicqu commented Oct 26, 2020

It still doesn't work as expected:

use std::sync::Mutex;

fn main() {
    let lock = Mutex::new(vec![1; 1]);
    while let Some(x) = lock.lock().unwrap().pop() {
        if x != 1 {
            break;
        }
        lock.lock().unwrap().push(x + 1);
        println!("the lock works as expected");
    }
}

@hicqu
Copy link
Author

hicqu commented Oct 26, 2020

And, please pay attation to the backtrace in the issue description, it's a dead lock exactly.

@tesuji
Copy link
Contributor

tesuji commented Oct 26, 2020

Yeah, but still it is a programming bug rather than a compiler bug. Rust makes no promise about deadlock.
The problem is:

  • in line 5, you lock the mutex in white let, the lock is held until end of while loop.
  • in line 10, you relock the mutex, which is still held, so the current thread is blocked.

Quoting the doc:

The exact behavior on locking a mutex in the thread which already holds the lock is left unspecified. However, this function will not return on the second call (it might panic or deadlock, for example).

The working code is:

use std::sync::Mutex;

fn main() {
    let lock = Mutex::new(vec![1; 10]);
    let mut inner = lock.lock().unwrap();
    while let Some(x) = inner.pop() {
        if x != 1 {
            break;
        }
        inner.push(x + 1);
        println!("the lock works as expected");
    }
}

@tesuji
Copy link
Contributor

tesuji commented Oct 26, 2020

You could also use try_lock in the second call: lock.try_lock().unwrap().push(x + 1);.
This will make the code panic instead of dead lock.

@jonas-schievink
Copy link
Contributor

Temporaries created in a while let expression are dropped only at the end of the whole while let, so closing as not-a-bug.

@hicqu
Copy link
Author

hicqu commented Oct 27, 2020

created in a while let expression are dropped only at the end of the whole while let

I can understand this but my question is expression is different from value. If we write this

while let Some(value) = value { /* some code */ }

the value's lifetime should be equal to the whole loop.

However lock.lock().unwrap().pop() is an expression, and after the expression get be evaluated into a value, the lock should be already released. It's a defect of while let expression.

Maybe it's hard to fix, but at least a clippy warning is helpful.

@tesuji
Copy link
Contributor

tesuji commented Oct 27, 2020

I can understand this but my question is expression is different from value.

In Rust, we call them place expressions or value expressions.

lock.lock().unwrap().pop() is an scrutinee of a while let expr, and it is also a place expression.
The result of it will be drop at the end of the whole while let.

@hicqu
Copy link
Author

hicqu commented Oct 27, 2020

@lzutao I got it. I didn't know place expressions and value expressions. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants