Add Redis lock periodic renewal

Although `RenewableLockRegistry` provides a renew interface, it is inconvenient for users.
Developers hope to have a lock that can be automatically renewed.
On the one hand, it can avoid subsequent failures caused by locks that will not expire when abnormal exits,
and on the other hand, it can avoid unlock failures caused by lock expired.

* Add `RenewableLockRegistry.setRenewalTaskScheduler()` and when it is set, schedule a `renew()` script periodically
when lock is acquired  from Redis with `1/3` of `expireAfter`
* Test and document the feature
This commit is contained in:
NaccOll
2024-10-18 02:21:32 +08:00
committed by Artem Bilan
parent 1de81b6717
commit 4d08e11903
5 changed files with 142 additions and 6 deletions

View File

@@ -51,8 +51,10 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.integration.redis.RedisContainerTest;
import org.springframework.integration.redis.util.RedisLockRegistry.RedisLockType;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
@@ -66,6 +68,7 @@ import static org.mockito.Mockito.mock;
* @author Artem Vozhdayenko
* @author Anton Gabov
* @author Eddie Cho
* @author Youbin Wu
*
* @since 4.0
*
@@ -427,6 +430,20 @@ class RedisLockRegistryTests implements RedisContainerTest {
registry.destroy();
}
@ParameterizedTest
@EnumSource(RedisLockType.class)
void testRenewalOnExpire(RedisLockType redisLockType) throws Exception {
long expireAfter = 300L;
RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey, expireAfter);
registry.setRenewalTaskScheduler(new SimpleAsyncTaskScheduler());
registry.setRedisLockType(redisLockType);
Lock lock1 = registry.obtain("foo");
assertThat(lock1.tryLock()).isTrue();
Thread.sleep(expireAfter * 2);
lock1.unlock();
registry.destroy();
}
@ParameterizedTest
@EnumSource(RedisLockType.class)
void testEquals(RedisLockType testRedisLockType) {
@@ -900,6 +917,33 @@ class RedisLockRegistryTests implements RedisContainerTest {
registry.destroy();
}
@ParameterizedTest
@EnumSource(RedisLockType.class)
void testLockRenew(RedisLockType redisLockType) {
final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey);
registry.setRedisLockType(redisLockType);
final Lock lock = registry.obtain("foo");
assertThat(lock.tryLock()).isTrue();
try {
registry.renewLock("foo");
}
finally {
lock.unlock();
}
}
@ParameterizedTest
@EnumSource(RedisLockType.class)
void testLockRenewLockNotOwned(RedisLockType redisLockType) {
final RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey);
registry.setRedisLockType(redisLockType);
registry.obtain("foo");
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> registry.renewLock("foo"));
}
@Test
void testInitialiseWithCustomExecutor() {
RedisLockRegistry redisLockRegistry = new RedisLockRegistry(redisConnectionFactory, "registryKey");