GH-8699: Atomic Redis script for unlock()
Expected Behavior
Using a single Lua script to verify ownership of the lock and remove it.
Current Behavior
`unlock()` method of `RedisLock` uses two separate Redis operations:
* `isAcquiredInThisProcess()`` method executes a `GET` operation to verify if the lock is owned by the process.
* `removeLockKey()`` method executes `UNLINK/DEL` operation to remove the lock.
* The `removeLockKeyInnerUnlink()`, and `removeLockKeyInnerDelete()`
methods will execute a script both verify ownership of the lock and remove it.
**Cherry-pick to `6.1.x` & `6.0.x`**
This commit is contained in:
@@ -83,6 +83,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Unseok Kim
|
||||
* @author Anton Gabov
|
||||
* @author Christian Tzolov
|
||||
* @author Eddie Cho
|
||||
*
|
||||
* @since 4.0
|
||||
*
|
||||
@@ -98,7 +99,7 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
private final Map<String, RedisLock> locks =
|
||||
new LinkedHashMap<String, RedisLock>(16, 0.75F, true) {
|
||||
new LinkedHashMap<>(16, 0.75F, true) {
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Entry<String, RedisLock> eldest) {
|
||||
@@ -343,12 +344,12 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
/**
|
||||
* Unlock the lock using the unlink method in redis.
|
||||
*/
|
||||
protected abstract void removeLockKeyInnerUnlink();
|
||||
protected abstract boolean removeLockKeyInnerUnlink();
|
||||
|
||||
/**
|
||||
* Unlock the lock using the delete method in redis.
|
||||
*/
|
||||
protected abstract void removeLockKeyInnerDelete();
|
||||
protected abstract boolean removeLockKeyInnerDelete();
|
||||
|
||||
@Override
|
||||
public final void lock() {
|
||||
@@ -454,11 +455,6 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!isAcquiredInThisProcess()) {
|
||||
throw new IllegalStateException("Lock was released in the store due to expiration. " +
|
||||
"The integrity of data protected by this lock may have been compromised.");
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
RedisLockRegistry.this.executor.execute(this::removeLockKey);
|
||||
}
|
||||
@@ -481,7 +477,11 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
private void removeLockKey() {
|
||||
if (RedisLockRegistry.this.unlinkAvailable) {
|
||||
try {
|
||||
removeLockKeyInnerUnlink();
|
||||
boolean unlinkResult = removeLockKeyInnerUnlink();
|
||||
if (!unlinkResult) {
|
||||
throw new IllegalStateException("Lock was released in the store due to expiration. " +
|
||||
"The integrity of data protected by this lock may have been compromised.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -496,7 +496,10 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
}
|
||||
}
|
||||
}
|
||||
removeLockKeyInnerDelete();
|
||||
if (!removeLockKeyInnerDelete()) {
|
||||
throw new IllegalStateException("Lock was released in the store due to expiration. " +
|
||||
"The integrity of data protected by this lock may have been compromised.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -559,19 +562,23 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
|
||||
private final class RedisPubSubLock extends RedisLock {
|
||||
|
||||
private static final String UNLINK_UNLOCK_SCRIPT =
|
||||
"if (redis.call('unlink', KEYS[1]) == 1) then " +
|
||||
"redis.call('publish', ARGV[1], KEYS[1]) " +
|
||||
"return true " +
|
||||
"end " +
|
||||
"return false";
|
||||
private static final String UNLINK_UNLOCK_SCRIPT = """
|
||||
local lockClientId = redis.call('GET', KEYS[1])
|
||||
if (lockClientId == ARGV[1] and redis.call('UNLINK', KEYS[1]) == 1) then
|
||||
redis.call('PUBLISH', ARGV[2], KEYS[1])
|
||||
return true
|
||||
end
|
||||
return false
|
||||
""";
|
||||
|
||||
private static final String DELETE_UNLOCK_SCRIPT =
|
||||
"if (redis.call('del', KEYS[1]) == 1) then " +
|
||||
"redis.call('publish', ARGV[1], KEYS[1]) " +
|
||||
"return true " +
|
||||
"end " +
|
||||
"return false";
|
||||
private static final String DELETE_UNLOCK_SCRIPT = """
|
||||
local lockClientId = redis.call('GET', KEYS[1])
|
||||
if (lockClientId == ARGV[1] and redis.call('DEL', KEYS[1]) == 1) then
|
||||
redis.call('PUBLISH', ARGV[2], KEYS[1])
|
||||
return true
|
||||
end
|
||||
return false
|
||||
""";
|
||||
|
||||
private static final RedisScript<Boolean>
|
||||
UNLINK_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(UNLINK_UNLOCK_SCRIPT, Boolean.class);
|
||||
@@ -589,18 +596,19 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeLockKeyInnerUnlink() {
|
||||
RedisLockRegistry.this.redisTemplate.execute(
|
||||
UNLINK_UNLOCK_REDIS_SCRIPT, Collections.singletonList(this.lockKey),
|
||||
RedisLockRegistry.this.unLockChannelKey);
|
||||
protected boolean removeLockKeyInnerUnlink() {
|
||||
return removeLockKeyWithScript(UNLINK_UNLOCK_REDIS_SCRIPT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeLockKeyInnerDelete() {
|
||||
RedisLockRegistry.this.redisTemplate.execute(
|
||||
DELETE_UNLOCK_REDIS_SCRIPT, Collections.singletonList(this.lockKey),
|
||||
RedisLockRegistry.this.unLockChannelKey);
|
||||
protected boolean removeLockKeyInnerDelete() {
|
||||
return removeLockKeyWithScript(DELETE_UNLOCK_REDIS_SCRIPT);
|
||||
}
|
||||
|
||||
private boolean removeLockKeyWithScript(RedisScript<Boolean> redisScript) {
|
||||
return Boolean.TRUE.equals(RedisLockRegistry.this.redisTemplate.execute(
|
||||
redisScript, Collections.singletonList(this.lockKey),
|
||||
RedisLockRegistry.this.clientId, RedisLockRegistry.this.unLockChannelKey));
|
||||
}
|
||||
|
||||
private boolean subscribeLock(long time) throws ExecutionException, InterruptedException {
|
||||
@@ -694,6 +702,30 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
|
||||
private final class RedisSpinLock extends RedisLock {
|
||||
|
||||
private static final String UNLINK_UNLOCK_SCRIPT = """
|
||||
local lockClientId = redis.call('GET', KEYS[1])
|
||||
if lockClientId == ARGV[1] then
|
||||
redis.call('UNLINK', KEYS[1])
|
||||
return true
|
||||
end
|
||||
return false
|
||||
""";
|
||||
|
||||
private static final String DELETE_UNLOCK_SCRIPT = """
|
||||
local lockClientId = redis.call('GET', KEYS[1])
|
||||
if lockClientId == ARGV[1] then
|
||||
redis.call('DEL', KEYS[1])
|
||||
return true
|
||||
end
|
||||
return false
|
||||
""";
|
||||
|
||||
private static final RedisScript<Boolean>
|
||||
UNLINK_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(UNLINK_UNLOCK_SCRIPT, Boolean.class);
|
||||
|
||||
private static final RedisScript<Boolean>
|
||||
DELETE_UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(DELETE_UNLOCK_SCRIPT, Boolean.class);
|
||||
|
||||
private RedisSpinLock(String path) {
|
||||
super(path);
|
||||
}
|
||||
@@ -718,13 +750,19 @@ public final class RedisLockRegistry implements ExpirableLockRegistry, Disposabl
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeLockKeyInnerUnlink() {
|
||||
RedisLockRegistry.this.redisTemplate.unlink(this.lockKey);
|
||||
protected boolean removeLockKeyInnerUnlink() {
|
||||
return removeLockKeyWithScript(UNLINK_UNLOCK_REDIS_SCRIPT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeLockKeyInnerDelete() {
|
||||
RedisLockRegistry.this.redisTemplate.delete(this.lockKey);
|
||||
protected boolean removeLockKeyInnerDelete() {
|
||||
return removeLockKeyWithScript(DELETE_UNLOCK_REDIS_SCRIPT);
|
||||
}
|
||||
|
||||
private boolean removeLockKeyWithScript(RedisScript<Boolean> redisScript) {
|
||||
return Boolean.TRUE.equals(RedisLockRegistry.this.redisTemplate.execute(
|
||||
redisScript, Collections.singletonList(this.lockKey),
|
||||
RedisLockRegistry.this.clientId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user