redis_queued_locks 0.0.27 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|