diff --git a/redisson/src/main/java/org/redisson/RedissonLock.java b/redisson/src/main/java/org/redisson/RedissonLock.java index 35896d012..b1167ec06 100644 --- a/redisson/src/main/java/org/redisson/RedissonLock.java +++ b/redisson/src/main/java/org/redisson/RedissonLock.java @@ -570,8 +570,9 @@ public class RedissonLock extends RedissonExpirable implements RLock { RFuture future = unlockInnerAsync(threadId); future.onComplete((opStatus, e) -> { + cancelExpirationRenewal(threadId); + if (e != null) { - cancelExpirationRenewal(threadId); result.tryFailure(e); return; } @@ -582,8 +583,7 @@ public class RedissonLock extends RedissonExpirable implements RLock { result.tryFailure(cause); return; } - - cancelExpirationRenewal(threadId); + result.trySuccess(null); }); diff --git a/redisson/src/test/java/org/redisson/RedissonLockExpirationRenewalTest.java b/redisson/src/test/java/org/redisson/RedissonLockExpirationRenewalTest.java new file mode 100644 index 000000000..5dcc38f34 --- /dev/null +++ b/redisson/src/test/java/org/redisson/RedissonLockExpirationRenewalTest.java @@ -0,0 +1,78 @@ +package org.redisson; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class RedissonLockExpirationRenewalTest { + + private static final String LOCK_KEY = "LOCK_KEY"; + public static final long LOCK_WATCHDOG_TIMEOUT = 1_000L; + + private RedissonClient redisson; + + @Before + public void before() throws IOException, InterruptedException { + RedisRunner.startDefaultRedisServerInstance(); + redisson = createInstance(); + } + + @After + public void after() throws InterruptedException { + redisson.shutdown(); + RedisRunner.shutDownDefaultRedisServerInstance(); + } + + @Test + public void testExpirationRenewalIsWorkingAfterTimeout() throws IOException, InterruptedException { + { + RLock lock = redisson.getLock(LOCK_KEY); + lock.lock(); + try { + // force expiration renewal error + restartRedisServer(); + // wait for timeout + Thread.sleep(LOCK_WATCHDOG_TIMEOUT * 2); + } finally { + assertThatThrownBy(lock::unlock).isInstanceOf(IllegalMonitorStateException.class); + } + } + + { + RLock lock = redisson.getLock(LOCK_KEY); + lock.lock(); + try { + // wait for timeout + Thread.sleep(LOCK_WATCHDOG_TIMEOUT * 2); + } finally { + lock.unlock(); + } + } + } + + private void restartRedisServer() throws InterruptedException, IOException { + int currentPort = RedisRunner.defaultRedisInstance.getRedisServerPort(); + RedisRunner.shutDownDefaultRedisServerInstance(); + RedisRunner.defaultRedisInstance = new RedisRunner().nosave().randomDir().port(currentPort).run(); + } + + public static Config createConfig() { + Config config = new Config(); + config.useSingleServer() + .setAddress(RedisRunner.getDefaultRedisServerBindAddressAndPort()); + config.setLockWatchdogTimeout(LOCK_WATCHDOG_TIMEOUT); + return config; + } + + public static RedissonClient createInstance() { + Config config = createConfig(); + return Redisson.create(config); + } +}