JDK-6571733 : ReentrantReadWriteLock blocks obtaining read lock after write lock times out
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2007-06-20
  • Updated: 2011-02-16
  • Resolved: 2007-06-20
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_01"
Java(TM) SE Runtime Environment (build 1.6.0_01-b06)
Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
ReentrantReadWriteLock blocks obtaining a read lock after trying to obtain a write lock and timing out.

So if one thread has a read lock, then a second thread does writeLock().tryLock(...), the tryLock will fail & return false.  If that second thread then
does readLock().lock() it will block.  This should not happen, it should be able to obtain the read lock because there are no write locks held.

A ReentrantReadWriteLock with "FAIR" policy does not show this behaviour

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- Create a ReentrantReadWriteLock (rwlock)
- Thread 1 calls
         rwlock.readLock().lock()
- Thread 2 calls
         rwlock.writeLock().tryLock(...)   // fails
         rwlock.readLock().lock().   // blocks

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Thread 2 obtains read lock and continues
ACTUAL -
Thread 2 is blocked trying to get read lock

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
// Code to reproduce
import java.util.concurrent.locks.*;
import java.util.concurrent.*;

/**
 * Test ReentrantReadWrite Lock.
 * This code shows how we cannot regain a read lock if a write lock times out.
 */
public class TestTryWriteInRead extends Thread {

    public void run() {
        log("Try For Write");
        try {
            boolean gotWrite = lock.writeLock().tryLock(20,TimeUnit.MILLISECONDS);
            if( gotWrite ) {
                 log("SURPRISE - got write... releasing it");
                 lock.writeLock().unlock();
            } else {
                 log("Write timed out");
            }
        }catch(InterruptedException e) {
            log("Interrupted");
        }

        log("Obtain Read");
        lock.readLock().lock();

        log("Release Read");
        lock.readLock().unlock();
        
        log("EXIT Try");
    }

    void log(String s) {
        System.out.println(getName() + ": " + s);
    }

    public TestTryWriteInRead (String name, ReadWriteLock lock) {
        super(name);
        this.lock = lock;
    }

    ReadWriteLock   lock;
    long count;
    static long stoptime;

    public static void main(String[] args) {
        // run this for approximately 10 seconds
        stoptime = System.currentTimeMillis() + 10000;

        ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(false);
        // obtain read lock
        rwlock.readLock().lock();
	
        // start 1 threads
        for (int i = 1; i <= 1; i++) {
            new Thread(new TestTryWriteInRead ("TryWrite"+ i, rwlock)).start();
        }
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Setting fair mode makes it work correctly.

Comments
EVALUATION OpenJDK now includes a regression test for this bug. test/java/util/concurrent/locks/ReentrantReadWriteLock/Bug6571733.java It bugged me that I didn't know exactly how the fix for 6460501 caused this bug to be fixed, so I investigated further. 6460501 does a better job of maintaining the "next" links in AQS nodes. In particular, 6460501 guarantees that if the current thread is first in the sync queue, then head.next is correctly pointing to this node, which means that tests like isFirst and apparentlyFirstQueuedIsExclusive are completely reliable when the current thread is first in queue. In particular, the pre-6460501 completely failed to update "next" links when cancelled nodes were bypassed in shouldParkAfterFailedAcquire. It is non-obvious that tryAcquire(Shared) is called in different contexts with different requirements on whether failure (i.e. returning false or negative) is permitted when the synchronizer is available for acquisition. This is possible before enqueuing, but not afterwards, when the thread is first in queue. This dual set of requirements on the same method is one of the reasons that tryAcquire(Shared) is confusing and prone to deadlocks in concrete AQS implementations. I propose tryAcquire(Shared)(int arg, boolean enqueued)
29-06-2007

EVALUATION This bug is reproducible on earlier releases/builds of jdk6 and jdk7, but not on current releases. It appears to be fixed as of jdk 6u2 and jdk 7 build 06, apparently because of the fix for 6460501: Synchronizer timed acquire still leaks memory (although I am not sure of the exact mechanism) Therefore I am closing this as a dup of 6460501. This bug seems not to be reproducible on any release of jdk5. Here's a slightly cleaner testcase: // Code to reproduce import java.util.concurrent.locks.*; import java.util.concurrent.*; /** * Test ReentrantReadWrite Lock. * This code shows how we cannot regain a read lock if a write lock times out. */ public class Bug implements Runnable { public void run() { log("Try For Write"); try { boolean gotWrite = lock.writeLock().tryLock(20,TimeUnit.MILLISECONDS); if (gotWrite) { log("SURPRISE - got write... releasing it"); lock.writeLock().unlock(); } else { log("Write timed out"); } } catch (InterruptedException e) { log("Interrupted"); } log("Obtain Read"); lock.readLock().lock(); log("Release Read"); lock.readLock().unlock(); log("EXIT Try"); } void log(String s) { System.out.printf("%s: %s%n", Thread.currentThread().getName(), s); } public Bug (ReadWriteLock lock) { this.lock = lock; } final ReadWriteLock lock; public static void main(String[] args) { ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(false); // obtain read lock rwlock.readLock().lock(); // start 1 threads for (int i = 0; i < 1; i++) new Thread(new Bug(rwlock), "TryWrite" + i).start(); } }
20-06-2007