Skip to content

Commit 3e4e289

Browse files
cppwfsartembilan
authored andcommitted
GH-11044: Revise cache in RedisLockRegistry
- Use a DefaultLockRegistry to manage local lock instances. - Document the internal registry changes Fixes: #11044 **Auto-cherry-pick to `6.5.x`**
1 parent dd5ff8a commit 3e4e289

3 files changed

Lines changed: 60 additions & 4 deletions

File tree

spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springframework.data.redis.listener.PatternTopic;
5858
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
5959
import org.springframework.data.redis.listener.Topic;
60+
import org.springframework.integration.support.locks.DefaultLockRegistry;
6061
import org.springframework.integration.support.locks.DistributedLock;
6162
import org.springframework.integration.support.locks.ExpirableLockRegistry;
6263
import org.springframework.integration.support.locks.RenewableLockRegistry;
@@ -99,6 +100,7 @@
99100
* @author Youbin Wu
100101
* @author Michal Domagala
101102
* @author Severin Kistler
103+
* @author Glenn Renfro
102104
*
103105
* @since 4.0
104106
*
@@ -110,14 +112,16 @@ public final class RedisLockRegistry
110112

111113
private static final long DEFAULT_EXPIRE_AFTER = 60000L;
112114

113-
private static final int DEFAULT_CAPACITY = 100_000;
115+
private static final int DEFAULT_CAPACITY = 256;
114116

115117
private static final int DEFAULT_IDLE = 100;
116118

117119
private final Lock lock = new ReentrantLock();
118120

119121
private Duration idleBetweenTries = Duration.ofMillis(DEFAULT_IDLE);
120122

123+
private DefaultLockRegistry defaultLockRegistry = new DefaultLockRegistry();
124+
121125
private final Map<String, RedisLock> locks =
122126
new LinkedHashMap<>(16, 0.75F, true) {
123127

@@ -243,11 +247,14 @@ public void setRenewalTaskScheduler(TaskScheduler renewalTaskScheduler) {
243247

244248
/**
245249
* Set the capacity of cached locks.
246-
* @param cacheCapacity The capacity of cached lock, (default 100_000).
250+
* @param cacheCapacity The capacity of cached lock, (default 256 locks).
247251
* @since 5.5.6
248252
*/
249253
public void setCacheCapacity(int cacheCapacity) {
250254
this.cacheCapacity = cacheCapacity;
255+
// Find the highest power of 2 for (n + 1), then subtract 1 to get the mask
256+
int mask = Integer.highestOneBit(cacheCapacity + 1) - 1;
257+
this.defaultLockRegistry = new DefaultLockRegistry(mask);
251258
}
252259

253260
/**
@@ -403,14 +410,15 @@ private abstract class RedisLock implements DistributedLock {
403410

404411
protected final String lockKey;
405412

406-
private final ReentrantLock localLock = new ReentrantLock();
413+
private final ReentrantLock localLock;
407414

408415
private volatile long lockedAt;
409416

410417
private volatile @Nullable ScheduledFuture<?> renewFuture;
411418

412419
private RedisLock(String path) {
413420
this.lockKey = constructLockKey(path);
421+
this.localLock = (ReentrantLock) RedisLockRegistry.this.defaultLockRegistry.obtain(this.lockKey);
414422
}
415423

416424
private String constructLockKey(String path) {

spring-integration-redis/src/test/java/org/springframework/integration/redis/util/RedisLockRegistryTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,50 @@ void testInitialiseWithCustomExecutor() {
10181018
assertThatNoException().isThrownBy(() -> redisLockRegistry.setExecutor(mock()));
10191019
}
10201020

1021+
@Test
1022+
void noSecondLockOnEviction() throws InterruptedException {
1023+
RedisLockRegistry registry = new RedisLockRegistry(redisConnectionFactory, this.registryKey);
1024+
registry.setRedisLockType(testRedisLockType);
1025+
registry.setCacheCapacity(2);
1026+
1027+
CountDownLatch lock1Latch = new CountDownLatch(1);
1028+
CountDownLatch furtherLocksLatch = new CountDownLatch(1);
1029+
1030+
Executors.newSingleThreadExecutor()
1031+
.execute(() -> {
1032+
DistributedLock lock1 = registry.obtain("lock1");
1033+
lock1.lock();
1034+
try {
1035+
furtherLocksLatch.countDown();
1036+
lock1Latch.await(10, TimeUnit.SECONDS);
1037+
}
1038+
catch (InterruptedException e) {
1039+
Thread.currentThread().interrupt();
1040+
}
1041+
finally {
1042+
lock1.unlock();
1043+
}
1044+
});
1045+
1046+
assertThat(furtherLocksLatch.await(10, TimeUnit.SECONDS)).isTrue();
1047+
// Two new locks to trigger cache eviction for the 'lock1'
1048+
registry.obtain("lock2");
1049+
registry.obtain("lock3");
1050+
1051+
// Request 'lock1' again: will trigger new RedisLock instance
1052+
DistributedLock lock1 = registry.obtain("lock1");
1053+
1054+
// Cannot lock because 'lock1' is still locked by another thread
1055+
assertThat(lock1.tryLock(1, TimeUnit.SECONDS)).isFalse();
1056+
1057+
lock1Latch.countDown();
1058+
1059+
assertThatNoException().isThrownBy(() -> {
1060+
lock1.lock();
1061+
lock1.unlock();
1062+
});
1063+
}
1064+
10211065
private Long getExpire(RedisLockRegistry registry, String lockKey) {
10221066
StringRedisTemplate template = createTemplate();
10231067
String registryKey = TestUtils.getPropertyValue(registry, "registryKey", String.class);

src/reference/antora/modules/ROOT/pages/redis.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,11 @@ The expiry should be set at a large enough value to prevent this condition, but
846846
Starting with version 5.0, the `RedisLockRegistry` implements `ExpirableLockRegistry`, which removes locks last acquired more than `age` ago and that are not currently locked.
847847

848848
Starting with version 5.5.6, the `RedisLockRegistry` is support automatically clean up cache for redisLocks in `RedisLockRegistry.locks` via `RedisLockRegistry.setCacheCapacity()`.
849+
The default value is `256`.
849850
See its JavaDocs for more information.
851+
This capacity number is also propagated into the `DefaultLockRegistry` used internally in the `RedisLockRegistry` for a pool of shared `ReentrantLock` instances.
852+
So, if higher concurrent access (the number of unique lock keys used in parallel) is expected (or allowed) for the application, the `cacheCapacity` should be increased respectively, with the closest power of 2 in mind (essentially `Integer.highestOneBit(cacheCapacity + 1)`).
853+
850854

851855
Starting with version 5.5.13, the `RedisLockRegistry` exposes a `setRedisLockType(RedisLockType)` option to determine in which mode a Redis lock acquisition should happen:
852856

@@ -887,4 +891,4 @@ RedisLockRegistry lockRegistry = new RedisLockRegistry("my-lock-key{choose_your_
887891
lockRegistry.lock();
888892
# critical section
889893
lockRegistry.unlock();
890-
----
894+
----

0 commit comments

Comments
 (0)