Skip to content

Commit

Permalink
[#extend_lock_ttl] implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
0exp committed Mar 30, 2024
1 parent 10fac53 commit 543314a
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [0.0.39] - 2024-03-31
### Added
- Logging: added new log `[redis_queued_locks.fail_fast_or_limits_reached__dequeue]`;
- `#extend_lock_ttl` implementation;
### Changed
- Removed `RadisQueuedLocks::Debugger.debug(...)` injections;
- Instrumentation events: `:at` payload field of `"redis_queued_locks.explicit_lock_release"` and
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,31 @@ Return:

#### #extend_lock_ttl

- soon
- Extend the lock's TTL (in milliseconds);
- returns `{ ok: true, result: :ttl_extended }` when ttl is extended;
- returns `{ ok: false, result: :async_expire_or_no_lock }` when lock not found or lock is expired during
some steps of invocation (see **Important** section below);
- **Important**:
- the method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension;
- the method consists of two commands:
- (1) read current pttl;
- (2) set new ttl that is calculated as "current pttl + additional milliseconds";
- what can happen during these steps:
- lock is expired between commands or before the first command;
- lock is expired before the second command;
- lock is expired AND newly acquired by another process (so you will extend the
totally new lock with fresh PTTL);
- use it at your own risk and consider the async nature when calling this method;

```ruby
rql.extend_lock_ttl("my_lock", 5_000) # NOTE: add 5_000 milliseconds

# => `ok` case
{ ok: true, result: :ttl_extended }

# => `failed` case
{ ok: false, result: :async_expire_or_no_lock }
```

---

Expand Down
25 changes: 21 additions & 4 deletions lib/redis_queued_locks/acquier/extend_lock_ttl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@
# @api private
# @since 0.1.0
module RedisQueuedLocks::Acquier::ExtendLockTTL
# @return [String]
#
# @api private
# @since 0.1.0
EXTEND_LOCK_PTTL = <<~LUA_SCRIPT.strip.tr("\n", "")
local new_lock_pttl = redis.call("PTTL", KEYS[1]) + ARGV[1];
return redis.call("PEXPIRE", KEYS[1], new_lock_pttl);
LUA_SCRIPT

class << self
# @param redis_client [RedisClient]
# @param lock_name [String]
# @param milliseconds [Integer]
# @param logger [::Logger,#debug]
# @return [?]
# @return [Hash<Symbol,Boolean|Symbol>]
#
# @api private
# @since 0.1.0
def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
# TODO: realize
def extend_lock_ttl(redis_client, lock_name, milliseconds)
lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)

# NOTE: EVAL signature -> <lua script>, (keys number), *(keys), *(arguments)
result = redis_client.call('EVAL', EXTEND_LOCK_PTTL, 1, lock_key, milliseconds)

if result == 1
RedisQueuedLocks::Data[ok: true, result: :ttl_extended]
else
RedisQueuedLocks::Data[ok: false, result: :async_expire_or_no_lock]
end
end
end
end
18 changes: 15 additions & 3 deletions lib/redis_queued_locks/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -249,18 +249,30 @@ def queue_info(lock_name)
RedisQueuedLocks::Acquier::QueueInfo.queue_info(redis_client, lock_name)
end

# This method is non-atomic cuz redis does not provide an atomic function for TTL/PTTL extension.
# So the methid is spliited into the two commands:
# (1) read current pttl
# (2) set new ttl that is calculated as "current pttl + additional milliseconds"
# What can happen during these steps
# - lock is expired between commands or before the first command;
# - lock is expired before the second command;
# - lock is expired AND newly acquired by another process (so you will extend the
# totally new lock with fresh PTTL);
# Use it at your own risk and consider async nature when calling this method.
#
# @param lock_name [String]
# @param milliseconds [Integer] How many milliseconds should be added.
# @return [?]
# @return [Hash<Symbol,Boolean|Symbol>]
# - { ok: true, result: :ttl_extended }
# - { ok: false, result: :async_expire_or_no_lock }
#
# @api public
# @since 0.1.0
def extend_lock_ttl(lock_name, milliseconds)
RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
redis_client,
lock_name,
milliseconds,
config[:logger]
milliseconds
)
end

Expand Down

0 comments on commit 543314a

Please sign in to comment.