From a59045a008a661332525fcc0d4e170fe75235603 Mon Sep 17 00:00:00 2001 From: JingZhang Chen Date: Fri, 16 Aug 2024 11:39:34 +0800 Subject: [PATCH] fix:deadlock when reentrant exclusive lock #2905 --- .../io/lettuce/core/protocol/SharedLock.java | 11 ++++- .../lettuce/core/protocol/SharedLockTest.java | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/lettuce/core/protocol/SharedLockTest.java diff --git a/src/main/java/io/lettuce/core/protocol/SharedLock.java b/src/main/java/io/lettuce/core/protocol/SharedLock.java index 13a9cb8cfe..a99f153d0e 100644 --- a/src/main/java/io/lettuce/core/protocol/SharedLock.java +++ b/src/main/java/io/lettuce/core/protocol/SharedLock.java @@ -26,6 +26,8 @@ class SharedLock { private final Lock lock = new ReentrantLock(); + private final ThreadLocal sharedCnt = ThreadLocal.withInitial(() -> 0); + private volatile long writers = 0; private volatile Thread exclusiveLockOwner; @@ -45,6 +47,7 @@ void incrementWriters() { if (WRITERS.get(this) >= 0) { WRITERS.incrementAndGet(this); + sharedCnt.set(sharedCnt.get() + 1); return; } } @@ -63,6 +66,7 @@ void decrementWriters() { } WRITERS.decrementAndGet(this); + sharedCnt.set(sharedCnt.get() - 1); } /** @@ -125,6 +129,11 @@ private void lockWritersExclusive() { exclusiveLockOwner = Thread.currentThread(); return; } + // reentrant exclusive lock + if (WRITERS.compareAndSet(this, sharedCnt.get(), -1)) { + exclusiveLockOwner = Thread.currentThread(); + return; + } } } finally { lock.unlock(); @@ -137,7 +146,7 @@ private void lockWritersExclusive() { private void unlockWritersExclusive() { if (exclusiveLockOwner == Thread.currentThread()) { - if (WRITERS.incrementAndGet(this) == 0) { + if (WRITERS.compareAndSet(this, -1, sharedCnt.get())) { exclusiveLockOwner = null; } } diff --git a/src/test/java/io/lettuce/core/protocol/SharedLockTest.java b/src/test/java/io/lettuce/core/protocol/SharedLockTest.java new file mode 100644 index 0000000000..3dda418e66 --- /dev/null +++ b/src/test/java/io/lettuce/core/protocol/SharedLockTest.java @@ -0,0 +1,42 @@ +package io.lettuce.core.protocol; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class SharedLockTest { + + @Test + public void safety_on_reentrant_lock_exclusive_on_writers() throws InterruptedException { + final SharedLock sharedLock = new SharedLock(); + CountDownLatch cnt = new CountDownLatch(1); + try { + sharedLock.incrementWriters(); + + String result = sharedLock.doExclusive(() -> { + return sharedLock.doExclusive(() -> { + return "ok"; + }); + }); + if ("ok".equals(result)) { + cnt.countDown(); + } + } finally { + sharedLock.decrementWriters(); + } + + cnt.await(1, TimeUnit.SECONDS); + + // verify writers won't be negative after finally decrementWriters + String result = sharedLock.doExclusive(() -> { + return sharedLock.doExclusive(() -> { + return "ok"; + }); + }); + + Assertions.assertEquals("ok", result); + } + +}