Reentrant Deadlock Issue with Read Locks

I have identified a reentrant deadlock issue with read locks. When a read lock needs to be reentered and another thread attempts to acquire a write lock during the outer read lock holding, it leads to a deadlock. The code illustrating this problem is as follows:

fn main() {
    let lock = Arc::new(RwLock::new(1));

    let new_lock = lock.clone();
    std::thread::spawn(move || {
        for _ in 0..100 {
            println!("write");
            let a = new_lock.write().unwrap(); // #1
            println!("write ok");
        }
    });

    let outer = lock.read().unwrap();
    for _ in 0..100 {
        println!("read");
        let inner = lock.read().unwrap(); // #2
        println!("read ok");
    }
    drop(outer);

    println!("{lock:?}");
}

Running the above program results in a deadlock. After inspecting the implementation of RwLock, I found that it prioritizes write waiters over read waiters. Consequently, when attempting to acquire a write lock at #1 while holding a read lock, it waits. Subsequently, at #2, attempting to acquire a read lock also waits due to the presence of a waiting write lock. As a result, the outer read lock is never released, causing a deadlock.

I would like to understand the rationale behind prioritizing write waiters over read waiters. I have tested similar scenarios in C and Go without encountering this issue. Could this be considered a "bug"?

No, you just didn't read the docs. Std locks aren't reentrant:

You are simply not allowed to do this, period.

According to the docs:

The priority policy of the lock is dependent on the underlying operating system’s implementation, and this type does not guarantee that any particular policy will be used. In particular, a writer which is waiting to acquire the lock in write might or might not block concurrent calls to read

I agree that this is not a bug, since the design decision is documented. If you need a lock implementation with a fair policy, consider parking_lot.

Thank you! I will reconsider the design issues.

Fair policy is exactly that's creating OP's problem. From parking_lot's documentation (emphasis mine):

This lock uses a task-fair locking policy which avoids both reader and writer starvation. This means that readers trying to acquire the lock will block even if the lock is unlocked when there are writers waiting to acquire the lock. Because of this, attempts to recursively acquire a read lock within a single thread may result in a deadlock.

2 Likes

It seemed to not deadlock with parking_lot when I tested it. But also the use of "may" in that quotation is probably key.