parking_lot: A write rwlock deadlocks when there are two shared read locks present on another thread

This program deadlocks:

use parking_lot::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

fn main() {
    let lock = Arc::new(RwLock::new(()));
    let running = Arc::new(AtomicBool::new(true));

    let l = lock.clone();
    let r = running.clone();
    let t0 = std::thread::spawn(move || {
        while r.load(Ordering::SeqCst) {
            let _a = l.read();
            let _b = l.read();
        }
    });

    let l = lock.clone();
    let r = running.clone();
    let t1 = std::thread::spawn(move || {
        while r.load(Ordering::SeqCst) {
            let _a = l.write();
        }
    });

    std::thread::sleep(std::time::Duration::from_millis(1000));

    running.store(false, Ordering::SeqCst);
    t0.join().unwrap();
    t1.join().unwrap();
}

If you comment out let _b = l.read(); then everything is fine.

Exactly the same program which uses RwLock from std doesn’t deadlock:

use std::sync::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

fn main() {
    let lock = Arc::new(RwLock::new(()));
    let running = Arc::new(AtomicBool::new(true));

    let l = lock.clone();
    let r = running.clone();
    let t0 = std::thread::spawn(move || {
        while r.load(Ordering::SeqCst) {
            let _a = l.read().unwrap();
            let _b = l.read().unwrap();
        }
    });

    let l = lock.clone();
    let r = running.clone();
    let t1 = std::thread::spawn(move || {
        while r.load(Ordering::SeqCst) {
            let _a = l.write().unwrap();
        }
    });

    std::thread::sleep(std::time::Duration::from_millis(1000));

    running.store(false, Ordering::SeqCst);
    t0.join().unwrap();
    t1.join().unwrap();
}

Stack traces of the deadlock:

#0  0x00007f152040ce9d in syscall () from /usr/lib/libc.so.6
#1  0x0000561815ecab20 in parking_lot_core::thread_parker::imp::ThreadParker::futex_wait (self=0x7f151fd90660, ts=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/thread_parker/linux.rs:111
#2  0x0000561815eca84a in <parking_lot_core::thread_parker::imp::ThreadParker as parking_lot_core::thread_parker::ThreadParkerT>::park (self=0x7f151fd90660)
    at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/thread_parker/linux.rs:65
#3  0x0000561815ec8d3d in parking_lot_core::parking_lot::park::{{closure}} (thread_data=0x7f151fd90640) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/parking_lot.rs:611
#4  0x0000561815ec89b4 in parking_lot_core::parking_lot::with_thread_data (f=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/parking_lot.rs:183
#5  parking_lot_core::parking_lot::park (key=94661465940480, validate=..., before_sleep=..., timed_out=..., park_token=..., timeout=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/parking_lot.rs:576
#6  0x0000561815ec1341 in parking_lot::raw_rwlock::RawRwLock::lock_common (self=0x5618170d2200, timeout=..., token=..., try_lock=..., validate_flags=8)
    at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/src/raw_rwlock.rs:1103
#7  0x0000561815ec00f4 in parking_lot::raw_rwlock::RawRwLock::lock_shared_slow (self=0x5618170d2200, recursive=false, timeout=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/src/raw_rwlock.rs:707
#8  0x0000561815ebaf52 in <parking_lot::raw_rwlock::RawRwLock as lock_api::rwlock::RawRwLock>::lock_shared (self=0x5618170d2200) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/src/raw_rwlock.rs:109
#9  0x0000561815ebb6d6 in lock_api::rwlock::RwLock<R,T>::read (self=0x5618170d2200) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/lock_api/src/rwlock.rs:337
#10 0x0000561815ebf8e1 in tst2::main::{{closure}} () at src/main.rs:14
#0  0x00007f152040ce9d in syscall () from /usr/lib/libc.so.6
#1  0x0000561815ecab20 in parking_lot_core::thread_parker::imp::ThreadParker::futex_wait (self=0x7f151fb8f660, ts=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/thread_parker/linux.rs:111
#2  0x0000561815eca84a in <parking_lot_core::thread_parker::imp::ThreadParker as parking_lot_core::thread_parker::ThreadParkerT>::park (self=0x7f151fb8f660)
    at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/thread_parker/linux.rs:65
#3  0x0000561815ec9d99 in parking_lot_core::parking_lot::park::{{closure}} (thread_data=0x7f151fb8f640) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/parking_lot.rs:611
#4  0x0000561815ec87ff in parking_lot_core::parking_lot::with_thread_data (f=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/parking_lot.rs:183
#5  parking_lot_core::parking_lot::park (key=94661465940481, validate=..., before_sleep=..., timed_out=..., park_token=..., timeout=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/core/src/parking_lot.rs:576
#6  0x0000561815ec0b3a in parking_lot::raw_rwlock::RawRwLock::wait_for_readers (self=0x5618170d2200, timeout=..., prev_value=0) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/src/raw_rwlock.rs:1001
#7  0x0000561815ebfe3d in parking_lot::raw_rwlock::RawRwLock::lock_exclusive_slow (self=0x5618170d2200, timeout=...) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/src/raw_rwlock.rs:632
#8  0x0000561815ebb09d in <parking_lot::raw_rwlock::RawRwLock as lock_api::rwlock::RawRwLock>::lock_exclusive (self=0x5618170d2200) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/src/raw_rwlock.rs:73
#9  0x0000561815ebb706 in lock_api::rwlock::RwLock<R,T>::write (self=0x5618170d2200) at /home/kou/.cargo/git/checkouts/parking_lot-10afde9c7055db59/fa294cd/lock_api/src/rwlock.rs:369
#10 0x0000561815ebf9c2 in tst2::main::{{closure}} () at src/main.rs:22

Originally I was investigating why my upgradable rwlocks deadlock, and I wanted to create an issue with the following snippet which also deadlocks:

use parking_lot::lock_api::RwLockUpgradableReadGuard;
use parking_lot::RwLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

fn main() {
    let lock = Arc::new(RwLock::new(()));
    let running = Arc::new(AtomicBool::new(true));

    let l = lock.clone();
    let r = running.clone();
    let t0 = std::thread::spawn(move || {
        while r.load(Ordering::SeqCst) {
            let _a = l.read();
            let _b = l.read();
        }
    });

    let l = lock.clone();
    let r = running.clone();
    let t1 = std::thread::spawn(move || {
        while r.load(Ordering::SeqCst) {
            let a = l.upgradable_read();
            let _b = RwLockUpgradableReadGuard::upgrade(a);
        }
    });

    std::thread::sleep(std::time::Duration::from_millis(1000));

    running.store(false, Ordering::SeqCst);
    t0.join().unwrap();
    t1.join().unwrap();
}

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 1
  • Comments: 18 (12 by maintainers)

Most upvoted comments

Does the deadlock_detection feature flag detect this case?

You could implement this yourself as a wrapper around RwLock that keeps a thread-local HashSet of all the read locks owned by the current thread. That would allow you to panic if you detect an attempt to acquire a lock that is already owned.

Unfortunately it does have overhead, which is why it is disabled by default.

The way to use the deadlock detector (I realize this isn’t actually documented, this should be fixed…) is to create a thread that loops between calling parking_lot_core::deadlock::check_deadlock and sleeping. If a deadlock has occurred then this function will return a list of threads that are stuck on a deadlock and backtraces for each thread.

However this will only catch a deadlock at runtime after it has actually happened, so this is probably not the solution you are looking for.

One thing you could try is to replace all RwLock with Mutex, which should force a deadlock for any case where you have a recursive read lock.