question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Locking Problem with Lazy Striped ReadWriteLocks

See original GitHub issue

We’re seeing a behaviour of a Striped.lazyReadWriteLock that seem to break the documented behaviour that for the same key the same lock will be returned. Is there something obvious we’re missing or misunderstood about the lazy Striped Locks? When using eager Locks, everything works as expected.

The following test reproduces the behaviour.

public class MyTest {

    private final Striped<ReadWriteLock> stripedLock = Striped.lazyWeakReadWriteLock(64);
    private final AtomicBoolean writeLocked = new AtomicBoolean(false);
    private final Set<String> someSet = new HashSet<>();
    private final Random random = new Random();

    private final String key = "anyKey";

    @Test
    public void test() throws Exception {
        final ExecutorService executorService = Executors.newCachedThreadPool();
        final AtomicBoolean testFailed = new AtomicBoolean(false);

        for (int i = 0; i < 1000000; i++) {
            someSet.add("" + i);
        }

        for (int i = 0; i < 10; i++) {

            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (!testFailed.get()) {
                            if (random.nextBoolean()) {
                                writeLockedMethod();
                            } else {
                                readLockedMethod();
                            }
                        }
                    } catch (AssertionError e) {
                        e.printStackTrace();
                        testFailed.set(true);
                    }
                }
            });
        }

        while (!testFailed.get()) {
            Thread.sleep(1000);
        }
        fail();
    }

    private void readLockedMethod() {
        final Lock readLock = stripedLock.get(key).readLock();
        readLock.lock();
        assertFalse(writeLocked.get());
        try {
            someSet.contains("" + random.nextInt(1000000)); //Just do something.
        } finally {
            readLock.unlock();
        }
    }

    private void writeLockedMethod() {
        final Lock writeLock = stripedLock.get(key).writeLock();
        writeLock.lock();
        //Since the atomic boolean is set inside the write lock, for the same key, this should never be true.
        assertFalse(Thread.currentThread().getName(),writeLocked.get());
        writeLocked.getAndSet(true);
        try {
            someSet.contains("" + random.nextInt(1000000)); //Just do something.
        } finally {
            writeLocked.getAndSet(false);
            writeLock.unlock();
        }
    }
}

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:4
  • Comments:12 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
lowassercommented, Oct 16, 2017

As surprising as this behavior is, I think it’s…technically…spec-compliant. Took me a couple minutes to figure it out. That doesn’t mean it’s not unacceptably surprising, or fixable, though.

The issue isn’t the laziness, it’s the weak part. The lock is weakly referenced by the Striped; it says so in the “weak” part of the method name.

The issue is that those two methods of yours don’t hold on to the ReadWriteLock itself. They hold on to the read lock and to the write lock. Separately. And those don’t hold references back to the ReadWriteLock. So the ReadWriteLock is eligible for garbage collection in the middle of the method, and a new one gets generated the next time someone asks for it, and since it’s not the same lock they interoperate at the same time.

That said: that’s an artifact of the Java implementation of ReentrantReadWriteLock, that the read and write locks don’t actually hold references back to the shared lock object they came from. And it makes lazyWeakReadWriteLock almost useless. So I think the proper thing to do is, in fact, to return not a ReentrantReadWriteLock but a thin wrapper that makes sure the read and write locks hold references to the ReadWriteLock they came from.

Read more comments on GitHub >

github_iconTop Results From Across the Web

java - Attempt to unlock read lock, not locked by current thread
Looks like there is a bug in Striped.lazyWeakReadWriteLock(). By changing the initialization from Striped.lazyWeakReadWriteLock to Striped.
Read more >
Introduction to Lock Striping - Baeldung
Lock striping is a technique where the locking occurs on several buckets or stripes, meaning that accessing a bucket only locks that bucket...
Read more >
Cache<Long, BlockingDeque<Peer>> combined with Striped ...
From a concurrency stand-point, I don't see any issue with the code: ... getPeers(long sessionId) { Lock lock = stripes.get(sessionId).
Read more >
To Lock or Not to Lock. I never really enjoy solving… - Medium
This sounds like a text book problem for shared locks, right? ... SmallLazyStriped class uses a AtomicReferenceArray to store locks.
Read more >
Striped (Guava: Google Core Libraries for Java 19.0 API)
Creates a Striped<ReadWriteLock> with lazily initialized, weakly referenced read-write locks. static Striped<Semaphore>, lazyWeakSemaphore(int stripes, int ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found