redis_queued_locks 0.0.27 → 0.0.29
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +23 -22
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +69 -5
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_with_expire.rb +7 -2
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +2 -2
- data/lib/redis_queued_locks/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3137184f5a89b895fe347d8080957e08a57974e91d5a68189d0eb157afd7c3eb
|
4
|
+
data.tar.gz: 7b059f5de5cde2af1634eed3aeae540afd7edfc5ba564781789649098e53e0a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4821b6c0fc142d90fae314aa11c92069be9223cc4a61be077ff6e19278fae97f920d31b590c5688f3614159a2f5c9d9d1792d8a6aee50f51d7b24f83ea1a9298
|
7
|
+
data.tar.gz: 80a0de485f263459c52d230cc0e604d0c742db235a6a9b57053cc84001e9e146cfc35b9c31aff5e1ef5a2796692559f95fe94db39350720277fb219ef95a2685
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.29] - 2024-03-23
|
4
|
+
### Added
|
5
|
+
- Logging: more detailed logs to `RedisQueuedLocks::Acquier::AcquireLock::TryToLock`;
|
6
|
+
|
7
|
+
## [0.0.28] - 2024-03-21
|
8
|
+
### Added
|
9
|
+
- Logging: added `acq_id` to every log message;
|
10
|
+
- Logging: updated documentation;
|
11
|
+
|
3
12
|
## [0.0.27] - 2024-03-21
|
4
13
|
### Changed
|
5
14
|
- Better acquier position accuracy: acquier position in lock queue
|
data/README.md
CHANGED
@@ -139,15 +139,18 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
|
|
139
139
|
# - the logger object;
|
140
140
|
# - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
|
141
141
|
# - at this moment the only debug logs are realised in 3 cases:
|
142
|
-
# - start of lock obtaining: "[redis_queud_locks.start_lock_obtaining] lock_key => 'rql:lock:your_lock'"
|
143
|
-
# - finish of the lock obtaining: "[redis_queued_locks.lock_obtained] lock_key => 'rql:lock:your_lock' acq_time => 123.456 (ms)"
|
144
|
-
# - start of the lock expiration after `yield`: "[redis_queud_locks.expire_lock] lock_key => 'rql:lock:your_lock'"
|
142
|
+
# - start of lock obtaining: "[redis_queud_locks.start_lock_obtaining] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
|
143
|
+
# - finish of the lock obtaining: "[redis_queued_locks.lock_obtained] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93' acq_time => 123.456 (ms)"
|
144
|
+
# - start of the lock expiration after `yield`: "[redis_queud_locks.expire_lock] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
|
145
145
|
# - by default uses VoidLogger that does nothing;
|
146
146
|
config.logger = RedisQueuedLocks::Logging::VoidLogger
|
147
147
|
|
148
148
|
# (default: false)
|
149
|
-
# - should be logged the each try
|
150
|
-
# -
|
149
|
+
# - should be logged the each internal try-retry lock acquire (a lot of logs can be generated depending on your retry configurations);
|
150
|
+
# - it adds 2 cases to the log in addition to the existing 3:
|
151
|
+
# - start of a try: "[redis_queud_locks.try_lock_start] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
|
152
|
+
# - redis connection is feteched from the pool after "start of a try": "[redis_queued_locks.try_lock_rconn_fetched] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
|
153
|
+
# - if logger is not configured this option does not lead to any effect;
|
151
154
|
config.log_lock_try = false
|
152
155
|
end
|
153
156
|
```
|
@@ -597,23 +600,21 @@ Detalized event semantics and payload structure:
|
|
597
600
|
|
598
601
|
## Roadmap
|
599
602
|
|
600
|
-
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
- support for `Dragonfly` DB backend;
|
616
|
-
- support for `Garnet` DB backend;
|
603
|
+
- Semantic Error objects for unexpected Redis errors;
|
604
|
+
- `100%` test coverage;
|
605
|
+
- per-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
|
606
|
+
the acquired lock for long-running blocks of code (that invoked "under" the lock
|
607
|
+
whose ttl may expire before the block execution completes);
|
608
|
+
- an ability to add custom metadata to the lock and an ability to read this data;
|
609
|
+
- lock prioritization;
|
610
|
+
- support for LIFO strategy;
|
611
|
+
- structured logging;
|
612
|
+
- GitHub Actions CI;
|
613
|
+
- `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
|
614
|
+
- better code stylization and interesting refactorings;
|
615
|
+
- dead queue cleanup;
|
616
|
+
- support for `Dragonfly` DB backend;
|
617
|
+
- support for `Garnet` DB backend;
|
617
618
|
|
618
619
|
---
|
619
620
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# @api private
|
4
4
|
# @since 0.1.0
|
5
|
-
# rubocop:disable Metrics/ModuleLength
|
5
|
+
# rubocop:disable Metrics/ModuleLength, Metrics/BlockNesting
|
6
6
|
module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
7
7
|
# @since 0.1.0
|
8
8
|
extend RedisQueuedLocks::Utilities
|
@@ -41,7 +41,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
41
41
|
if log_lock_try
|
42
42
|
run_non_critical do
|
43
43
|
logger.debug(
|
44
|
-
"[redis_queued_locks.
|
44
|
+
"[redis_queued_locks.try_lock.start] " \
|
45
45
|
"lock_key => '#{lock_key}' " \
|
46
46
|
"acq_id => '#{acquier_id}'"
|
47
47
|
)
|
@@ -53,7 +53,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
53
53
|
if log_lock_try
|
54
54
|
run_non_critical do
|
55
55
|
logger.debug(
|
56
|
-
"[redis_queued_locks.
|
56
|
+
"[redis_queued_locks.try_lock.rconn_fetched] " \
|
57
57
|
"lock_key => '#{lock_key}' " \
|
58
58
|
"acq_id => '#{acquier_id}'"
|
59
59
|
)
|
@@ -63,12 +63,22 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
63
63
|
rconn.multi(watch: [lock_key]) do |transact|
|
64
64
|
# Fast-Step X0: fail-fast check
|
65
65
|
if fail_fast && rconn.call('HGET', lock_key, 'acq_id')
|
66
|
-
# Fast-Step X1: is lock already obtained. fail fast
|
66
|
+
# Fast-Step X1: is lock already obtained. fail fast leads to "no try".
|
67
67
|
inter_result = :fail_fast_no_try
|
68
68
|
else
|
69
69
|
# Step 1: add an acquier to the lock acquirement queue
|
70
70
|
res = rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
71
71
|
|
72
|
+
if log_lock_try
|
73
|
+
run_non_critical do
|
74
|
+
logger.debug(
|
75
|
+
"[redis_queued_locks.try_lock.acq_added_to_queue] " \
|
76
|
+
"lock_key => '#{lock_key}' " \
|
77
|
+
"acq_id => '#{acquier_id}'"
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
72
82
|
RedisQueuedLocks.debug(
|
73
83
|
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
74
84
|
)
|
@@ -81,6 +91,16 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
81
91
|
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
82
92
|
)
|
83
93
|
|
94
|
+
if log_lock_try
|
95
|
+
run_non_critical do
|
96
|
+
logger.debug(
|
97
|
+
"[redis_queued_locks.try_lock.remove_expired_acqs] " \
|
98
|
+
"lock_key => '#{lock_key}' " \
|
99
|
+
"acq_id => '#{acquier_id}'"
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
84
104
|
RedisQueuedLocks.debug(
|
85
105
|
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
86
106
|
)
|
@@ -88,6 +108,17 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
88
108
|
# Step 3: get the actual acquier waiting in the queue
|
89
109
|
waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
|
90
110
|
|
111
|
+
if log_lock_try
|
112
|
+
run_non_critical do
|
113
|
+
logger.debug(
|
114
|
+
"[redis_queued_locks.try_lock.get_first_from_queue] " \
|
115
|
+
"lock_key => '#{lock_key}' " \
|
116
|
+
"acq_id => '#{acquier_id}' " \
|
117
|
+
"first_acq_id_in_queue => '#{waiting_acquier}'"
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
91
122
|
RedisQueuedLocks.debug(
|
92
123
|
"Step №3: какой процесс в очереди сейчас ждет. " \
|
93
124
|
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
@@ -97,6 +128,17 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
97
128
|
unless waiting_acquier == acquier_id
|
98
129
|
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
99
130
|
|
131
|
+
if log_lock_try
|
132
|
+
run_non_critical do
|
133
|
+
logger.debug(
|
134
|
+
"[redis_queued_locks.try_lock.exit__no_first] " \
|
135
|
+
"lock_key => '#{lock_key}' " \
|
136
|
+
"acq_id => '#{acquier_id}' " \
|
137
|
+
"first_acq_id_in_queue => '#{waiting_acquier}'"
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
100
142
|
RedisQueuedLocks.debug(
|
101
143
|
"Step ROLLBACK №1: не одинаковые ключи. выходим. " \
|
102
144
|
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
@@ -120,6 +162,18 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
120
162
|
if locked_by_acquier
|
121
163
|
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
122
164
|
|
165
|
+
if log_lock_try
|
166
|
+
run_non_critical do
|
167
|
+
logger.debug(
|
168
|
+
"[redis_queued_locks.try_lock.exit__still_obtained] " \
|
169
|
+
"lock_key => '#{lock_key}' " \
|
170
|
+
"acq_id => '#{acquier_id}' " \
|
171
|
+
"first_acq_id_in_queue => '#{waiting_acquier}' " \
|
172
|
+
"locked_by_acq_id => '#{locked_by_acquier}'"
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
123
177
|
RedisQueuedLocks.debug(
|
124
178
|
"Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
|
125
179
|
"[Занят процессом: #{locked_by_acquier}]"
|
@@ -153,6 +207,16 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
153
207
|
|
154
208
|
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
155
209
|
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
210
|
+
|
211
|
+
if log_lock_try
|
212
|
+
run_non_critical do
|
213
|
+
logger.debug(
|
214
|
+
"[redis_queued_locks.try_lock.run__free_to_acquire] " \
|
215
|
+
"lock_key => '#{lock_key}' " \
|
216
|
+
"acq_id => '#{acquier_id}'"
|
217
|
+
)
|
218
|
+
end
|
219
|
+
end
|
156
220
|
end
|
157
221
|
end
|
158
222
|
end
|
@@ -210,4 +274,4 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
210
274
|
RedisQueuedLocks::Data[ok: true, result: result]
|
211
275
|
end
|
212
276
|
end
|
213
|
-
# rubocop:enable Metrics/ModuleLength
|
277
|
+
# rubocop:enable Metrics/ModuleLength, Metrics/BlockNesting
|
@@ -9,6 +9,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
9
9
|
# @param redis [RedisClient] Redis connection manager.
|
10
10
|
# @param logger [::Logger,#debug] Logger object.
|
11
11
|
# @param lock_key [String] Lock key to be expired.
|
12
|
+
# @param acquier_id [String] Acquier identifier.
|
12
13
|
# @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
|
13
14
|
# @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
|
14
15
|
# @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
|
@@ -17,7 +18,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
17
18
|
#
|
18
19
|
# @api private
|
19
20
|
# @since 0.1.0
|
20
|
-
def yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
|
21
|
+
def yield_with_expire(redis, logger, lock_key, acquier_id, timed, ttl_shift, ttl, &block)
|
21
22
|
if block_given?
|
22
23
|
if timed && ttl != nil
|
23
24
|
timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
|
@@ -28,7 +29,11 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
|
|
28
29
|
end
|
29
30
|
ensure
|
30
31
|
run_non_critical do
|
31
|
-
logger.debug(
|
32
|
+
logger.debug(
|
33
|
+
"[redis_queued_locks.expire_lock] " \
|
34
|
+
"lock_key => '#{lock_key}' " \
|
35
|
+
"acq_id => '#{acquier_id}'"
|
36
|
+
)
|
32
37
|
end
|
33
38
|
redis.call('EXPIRE', lock_key, '0')
|
34
39
|
end
|
@@ -176,7 +176,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
176
176
|
logger.debug(
|
177
177
|
"[redis_queued_locks.lock_obtained] " \
|
178
178
|
"lock_key => '#{result[:lock_key]}' " \
|
179
|
-
"acq_id
|
179
|
+
"acq_id => '#{acquier_id}' " \
|
180
180
|
"acq_time => #{acq_time} (ms)"
|
181
181
|
)
|
182
182
|
end
|
@@ -255,7 +255,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
|
|
255
255
|
begin
|
256
256
|
yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
257
257
|
ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
|
258
|
-
yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
|
258
|
+
yield_with_expire(redis, logger, lock_key, acquier_id, timed, ttl_shift, ttl, &block)
|
259
259
|
ensure
|
260
260
|
acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
261
261
|
acq_process[:hold_time] = (
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_queued_locks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.29
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rustam Ibragimov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|