Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancing unlock() Method of RedisLock with Atomic Redis Operation #8699

Closed
EddieChoCho opened this issue Aug 12, 2023 · 1 comment
Closed

Comments

@EddieChoCho
Copy link
Contributor

EddieChoCho commented Aug 12, 2023

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:

  1. isAcquiredInThisProcess() method executes a GET operation to verify if the lock is owned by the process.
  2. removeLockKey() method executes UNLINK/DEL operation to remove the lock.

Context

The current implementation of the unlock() method involves two distinct Redis operations. However, I'm wonder if this approach may lead to potential issues? particularly in edge cases where the lock expires and is subsequently obtained by another process before the original process attempts to delete it:

  1. Process A performs a GET 'key' operation to check if it owns the lock.
  2. 'key' expired.
  3. Process B acquires the lock by executing OBTAIN_LOCK_REDIS_SCRIPT with 'key' and clientId of process B.
  4. Process A proceeds to execute a UNLINK/DEL operation to remove the 'key'.

To address this, it is proposed that the unlock method be enhanced to employ a single atomic Lua script. This script will both verify ownership of the lock and remove it. This approach ensures that the process of unlocking remains atomic and immune to race conditions or interleaved operations, thus maintaining the integrity of the distributed lock mechanism.

@EddieChoCho EddieChoCho added status: waiting-for-triage The issue need to be evaluated and its future decided type: enhancement labels Aug 12, 2023
EddieChoCho added a commit to EddieChoCho/spring-integration that referenced this issue Aug 12, 2023
EddieChoCho added a commit to EddieChoCho/spring-integration that referenced this issue Aug 12, 2023
EddieChoCho added a commit to EddieChoCho/spring-integration that referenced this issue Aug 12, 2023
@artembilan artembilan added in: redis backport 6.0.x (EOL) for: backport-to-6.1.x and removed status: waiting-for-triage The issue need to be evaluated and its future decided labels Aug 14, 2023
@artembilan artembilan added this to the 6.2.0-M2 milestone Aug 14, 2023
artembilan pushed a commit that referenced this issue Aug 14, 2023
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`**
artembilan pushed a commit that referenced this issue Aug 14, 2023
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`**

# Conflicts:
#	spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java
artembilan pushed a commit that referenced this issue Aug 14, 2023
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`**

# Conflicts:
#	spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java
@artembilan
Copy link
Member

Fixed via 0450a32

EddieChoCho added a commit to EddieChoCho/spring-integration that referenced this issue Aug 15, 2023
IllegalStateException should not be thrown from try block
EddieChoCho added a commit to EddieChoCho/spring-integration that referenced this issue Aug 15, 2023
Add a comment and fix checkstyle violations
artembilan pushed a commit that referenced this issue Aug 15, 2023
In the current code, an `IllegalStateException` might be thrown from the try block while invoking the `removeLockKeyInnerUnlink()` method, especially when caused by key expiration (resulting in `unlinkResult == false`).

This triggers the check block, which incorrectly sets the `unlinkAvailable` flag to `false`, even if the Redis server supports the unlink operation.

As a consequence, the subsequent `removeLockKeyInnerDelete()` method is invoked when it should not be.

* `IllegalStateException` should not be thrown from try block
* Add a comment and fix Checkstyle violations

**Cherry-pick to `6.1.x` & `6.0.x`**
artembilan pushed a commit that referenced this issue Aug 15, 2023
In the current code, an `IllegalStateException` might be thrown from the try block while invoking the `removeLockKeyInnerUnlink()` method, especially when caused by key expiration (resulting in `unlinkResult == false`).

This triggers the check block, which incorrectly sets the `unlinkAvailable` flag to `false`, even if the Redis server supports the unlink operation.

As a consequence, the subsequent `removeLockKeyInnerDelete()` method is invoked when it should not be.

* `IllegalStateException` should not be thrown from try block
* Add a comment and fix Checkstyle violations

**Cherry-pick to `6.1.x` & `6.0.x`**

(cherry picked from commit ba6d35d)
artembilan pushed a commit that referenced this issue Aug 15, 2023
In the current code, an `IllegalStateException` might be thrown from the try block while invoking the `removeLockKeyInnerUnlink()` method, especially when caused by key expiration (resulting in `unlinkResult == false`).

This triggers the check block, which incorrectly sets the `unlinkAvailable` flag to `false`, even if the Redis server supports the unlink operation.

As a consequence, the subsequent `removeLockKeyInnerDelete()` method is invoked when it should not be.

* `IllegalStateException` should not be thrown from try block
* Add a comment and fix Checkstyle violations

**Cherry-pick to `6.1.x` & `6.0.x`**

(cherry picked from commit ba6d35d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants