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

Deadlock with Rwlock and Thread with version 1.62 and 1.63 #101194

Closed
vireshk opened this issue Aug 30, 2022 · 7 comments
Closed

Deadlock with Rwlock and Thread with version 1.62 and 1.63 #101194

vireshk opened this issue Aug 30, 2022 · 7 comments
Labels
C-bug Category: This is a bug.

Comments

@vireshk
Copy link

vireshk commented Aug 30, 2022

I tried this code:

use std::sync::{Arc, RwLock};
use std::thread::spawn;

fn main() {
    let count = 2;
    let data = Arc::new(RwLock::new(vec![Some(true); count]));

    for i in 0..count {
        let data = data.clone();

        Some(spawn(move || {
            data.write().unwrap()[i] = None;
        }));
    }

    // This hangs a lot, since 1.62.0 (1.61 works fine)
    while data.read().unwrap()[0].is_some()
        || data.read().unwrap()[1].is_some()
    {}

    // This always passes
    while data.read().unwrap()[0].is_some() {}
    while data.read().unwrap()[1].is_some() {}

    dbg!();
}

I expected to see this happen: print [src/main.rs:25]

Instead, this happened: but it hangs

Meta

rustc --version --verbose:

rustc 1.63.0 (4b91a6ea7 2022-08-08)
binary: rustc
commit-hash: 4b91a6ea7258a947e59c6522cd5898e7c0a6a88f
commit-date: 2022-08-08
host: x86_64-unknown-linux-gnu
release: 1.63.0
LLVM version: 14.0.5
@m-ou-se
Copy link
Member

m-ou-se commented Aug 30, 2022

That's because your first while loop attempts to acquire two read locks at the same time, which can result in a deadlock. There is no guarantee that you can always acquire a second read lock if you already have one. Many RwLock implementations block new readers when there's a writer waiting.

(It's perhaps a bit surprising that || keeps the temporaries on the left side alive while evaluating the right one, since it only needs the bool result.)

@vireshk
Copy link
Author

vireshk commented Aug 30, 2022

I was expecting here that the first lock gets dropped as soon as we reach || and so the second one will be a fresh attempt.

@m-ou-se
Copy link
Member

m-ou-se commented Aug 30, 2022

That's a very reasonable expectation, but unfortunately not what happens. Temporaries stay around until the end of the whole expression. It sometimes results in confusing situations with lock guards, just like in your snippet. It'd be nice if we can do something about this, but it's very tricky to change the language rules without breaking any existing code, or accidentally making the rules more confusing. Perhaps we just need a lint (in Clippy, or Rustc) that warns about confusing situations like this.

vireshk added a commit to rust-vmm/vhost-device that referenced this issue Aug 30, 2022
The "test_gpio_process_events_multi_success" test currently hangs with
an update to a newer version of Rust. The code here tries to read the
value, locked, for each GPIO one by one. The values are updated in
another thread with the help of write lock.

More discussion around this issue can be found here.

rust-lang/rust#101194

Breaking the while loop into three, one for each GPIO fixes it for now.
This is the simplest solution possible right now.

Signed-off-by: Viresh Kumar <[email protected]>
@m-ou-se
Copy link
Member

m-ou-se commented Aug 30, 2022

FWIW, the 'right' way to write that while loop would be something like this, by only locking once:

    while {
        let d = data.read().unwrap();
        d[0].is_some() || d[1].is_some()
    } {}

@vireshk
Copy link
Author

vireshk commented Aug 30, 2022

From a common programmer's point of view, looks like something is broken somewhere since the compiler didn't complain at all. But I do understand what you are saying @m-ou-se . Thanks for quick response, really appreciate it.

Lets see if someone can come up with a solution to this.

@vireshk
Copy link
Author

vireshk commented Aug 30, 2022

    while {
        let d = data.read().unwrap();
        d[0].is_some() || d[1].is_some()
    } {

Hmm, I wasn't aware that we can write while's statement this way, like body i.e.. Interesting.

vireshk added a commit to rust-vmm/vhost-device that referenced this issue Aug 30, 2022
The "test_gpio_process_events_multi_success" test currently hangs with
an update to a newer version of Rust. The code here tries to read the
value, locked, for each GPIO one by one. The values are updated in
another thread with the help of write lock.

More discussion around this issue can be found here.

rust-lang/rust#101194

Taking the lock only once fixes it for now.

Signed-off-by: Viresh Kumar <[email protected]>
@Noratrieb
Copy link
Member

Closing this as not a bug. If you think the drop order of || statements is confusing, feel free to discuss that further on zulip or internals.rust-lang.org (or by opening a new issue).

@Noratrieb Noratrieb closed this as not planned Won't fix, can't repro, duplicate, stale Mar 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug.
Projects
None yet
Development

No branches or pull requests

3 participants