redis_queued_locks 1.20.0 → 1.21.0
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/.claude/project-overview.md +35 -13
- data/.claude/rules/acquirer.md +10 -9
- data/.claude/rules/logic.md +28 -5
- data/.claude/rules/swarm.md +2 -2
- data/.claude/rules/tests.md +6 -3
- data/.claude/rules/visitors.md +9 -10
- data/.claude/skills/rql-code-review/SKILL.md +24 -10
- data/CHANGELOG.md +126 -0
- data/CLAUDE.md +2 -1
- data/README.md +245 -70
- data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +42 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +45 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +12 -1
- data/lib/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rb +2 -2
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +1 -0
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +5 -5
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +111 -24
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +86 -8
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +122 -12
- data/lib/redis_queued_locks/acquirer/is_locked.rb +0 -1
- data/lib/redis_queued_locks/acquirer/is_queued.rb +0 -1
- data/lib/redis_queued_locks/acquirer/keys.rb +10 -1
- data/lib/redis_queued_locks/acquirer/lock_info.rb +31 -39
- data/lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb +3 -2
- data/lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb +5 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +55 -15
- data/lib/redis_queued_locks/acquirer/locks.rb +26 -9
- data/lib/redis_queued_locks/acquirer/queue_info.rb +0 -1
- data/lib/redis_queued_locks/acquirer/queues.rb +10 -1
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +117 -4
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +31 -4
- data/lib/redis_queued_locks/acquirer/release_lock.rb +33 -7
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +39 -4
- data/lib/redis_queued_locks/acquirer/release_read_lock.rb +22 -3
- data/lib/redis_queued_locks/client.rb +82 -40
- data/lib/redis_queued_locks/config.rb +12 -13
- data/lib/redis_queued_locks/errors.rb +34 -4
- data/lib/redis_queued_locks/instrument/sampler.rb +4 -7
- data/lib/redis_queued_locks/logging/sampler.rb +4 -8
- data/lib/redis_queued_locks/resource.rb +14 -4
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +1 -0
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +8 -4
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +3 -2
- data/lib/redis_queued_locks/swarm.rb +86 -5
- data/lib/redis_queued_locks/utilities.rb +1 -1
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +14 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +13 -0
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +9 -0
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +15 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +22 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -2
- data/sig/redis_queued_locks/acquirer/release_read_lock.rbs +1 -1
- data/sig/redis_queued_locks/client.rbs +14 -3
- data/sig/redis_queued_locks/errors.rbs +5 -2
- data/sig/redis_queued_locks/instrument/sampler.rbs +0 -2
- data/sig/redis_queued_locks/logging/sampler.rbs +0 -2
- data/sig/redis_queued_locks/resource.rbs +1 -1
- data/sig/vendor/redis_client.rbs +1 -0
- metadata +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api public
|
|
4
4
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.21.0
|
|
6
6
|
# rubocop:disable Metrics/ClassLength
|
|
7
7
|
class RedisQueuedLocks::Client
|
|
8
8
|
# @return [RedisClient]
|
|
@@ -79,6 +79,7 @@ class RedisQueuedLocks::Client
|
|
|
79
79
|
#
|
|
80
80
|
# @api public
|
|
81
81
|
# @since 1.9.0
|
|
82
|
+
# @version 1.21.0
|
|
82
83
|
def swarm_info(zombie_ttl: config['swarm.flush_zombies.zombie_ttl'])
|
|
83
84
|
swarm.swarm_info(zombie_ttl:)
|
|
84
85
|
end
|
|
@@ -107,6 +108,7 @@ class RedisQueuedLocks::Client
|
|
|
107
108
|
#
|
|
108
109
|
# @api public
|
|
109
110
|
# @since 1.9.0
|
|
111
|
+
# @version 1.21.0
|
|
110
112
|
def flush_zombies(
|
|
111
113
|
zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
|
|
112
114
|
lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size'],
|
|
@@ -121,6 +123,7 @@ class RedisQueuedLocks::Client
|
|
|
121
123
|
#
|
|
122
124
|
# @api public
|
|
123
125
|
# @since 1.9.0
|
|
126
|
+
# @version 1.21.0
|
|
124
127
|
def zombie_locks(
|
|
125
128
|
zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
|
|
126
129
|
lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size']
|
|
@@ -132,8 +135,9 @@ class RedisQueuedLocks::Client
|
|
|
132
135
|
# @option lock_scan_size [Integer]
|
|
133
136
|
# @return [Set<String>]
|
|
134
137
|
#
|
|
135
|
-
# @api
|
|
138
|
+
# @api public
|
|
136
139
|
# @since 1.9.0
|
|
140
|
+
# @version 1.21.0
|
|
137
141
|
def zombie_acquirers(
|
|
138
142
|
zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
|
|
139
143
|
lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size']
|
|
@@ -146,6 +150,7 @@ class RedisQueuedLocks::Client
|
|
|
146
150
|
#
|
|
147
151
|
# @api public
|
|
148
152
|
# @since 1.9.0
|
|
153
|
+
# @version 1.21.0
|
|
149
154
|
def zombie_hosts(zombie_ttl: config['swarm.flush_zombies.zombie_ttl'])
|
|
150
155
|
swarm.zombie_hosts(zombie_ttl:)
|
|
151
156
|
end
|
|
@@ -159,7 +164,7 @@ class RedisQueuedLocks::Client
|
|
|
159
164
|
#
|
|
160
165
|
# @api public
|
|
161
166
|
# @since 1.9.0
|
|
162
|
-
# @version 1.
|
|
167
|
+
# @version 1.21.0
|
|
163
168
|
def zombies_info(
|
|
164
169
|
zombie_ttl: config['swarm.flush_zombies.zombie_ttl'],
|
|
165
170
|
lock_scan_size: config['swarm.flush_zombies.zombie_lock_scan_size']
|
|
@@ -171,7 +176,9 @@ class RedisQueuedLocks::Client
|
|
|
171
176
|
# @param lock_name [String]
|
|
172
177
|
# Lock name to be obtained.
|
|
173
178
|
# @option ttl [Integer]
|
|
174
|
-
# Lock's time to live (in milliseconds)
|
|
179
|
+
# - Lock's time to live (in milliseconds);
|
|
180
|
+
# - a positive Integer;
|
|
181
|
+
# - pre-configured in `config['default_lock_ttl']`;
|
|
175
182
|
# @option queue_ttl [Integer]
|
|
176
183
|
# Lifetime of the acuier's lock request. In seconds.
|
|
177
184
|
# @option lock_index_grace_period [Integer]
|
|
@@ -183,7 +190,8 @@ class RedisQueuedLocks::Client
|
|
|
183
190
|
# mode: the lock index is maintained by all lock acquirements of the client);
|
|
184
191
|
# - pre-configured in `config['lock_index_grace_period']`;
|
|
185
192
|
# @option timeout [Integer,NilClass]
|
|
186
|
-
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout"
|
|
193
|
+
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout"
|
|
194
|
+
# (`0` is the same as `nil`).
|
|
187
195
|
# @option timed [Boolean]
|
|
188
196
|
# Limit the invocation time period of the passed block of code by the lock's TTL.
|
|
189
197
|
# @option retry_count [Integer,NilClass]
|
|
@@ -294,7 +302,7 @@ class RedisQueuedLocks::Client
|
|
|
294
302
|
# - works in tandem with <config['log_sampling_percent'] and <config['log_sampler']>;
|
|
295
303
|
# @option log_sampling_percent [Integer]
|
|
296
304
|
# - the percent of cases that should be logged;
|
|
297
|
-
# - take an effect when <config['
|
|
305
|
+
# - take an effect when <config['log_sampling_enabled']> is true;
|
|
298
306
|
# - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
|
|
299
307
|
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
|
300
308
|
# - percent-based log sampler that decides should be RQL case logged or not;
|
|
@@ -315,7 +323,7 @@ class RedisQueuedLocks::Client
|
|
|
315
323
|
# - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
|
|
316
324
|
# @option instr_sampling_percent [Integer]
|
|
317
325
|
# - the percent of cases that should be instrumented;
|
|
318
|
-
# - take an effect when <config['
|
|
326
|
+
# - take an effect when <config['instr_sampling_enabled']> is true;
|
|
319
327
|
# - works in tandem with <config['instr_sampling_enabled']>
|
|
320
328
|
# and <config['instr_sampler']> configs;
|
|
321
329
|
# @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
|
|
@@ -339,7 +347,7 @@ class RedisQueuedLocks::Client
|
|
|
339
347
|
#
|
|
340
348
|
# @api public
|
|
341
349
|
# @since 1.0.0
|
|
342
|
-
# @version 1.
|
|
350
|
+
# @version 1.21.0
|
|
343
351
|
# rubocop:disable Metrics/MethodLength
|
|
344
352
|
def lock(
|
|
345
353
|
lock_name,
|
|
@@ -418,7 +426,7 @@ class RedisQueuedLocks::Client
|
|
|
418
426
|
# rubocop:disable all
|
|
419
427
|
# @api public
|
|
420
428
|
# @since 1.16.0
|
|
421
|
-
# @version 1.
|
|
429
|
+
# @version 1.21.0
|
|
422
430
|
def lock_series( # steep:ignore
|
|
423
431
|
*lock_names,
|
|
424
432
|
detailed_result: false,
|
|
@@ -498,7 +506,7 @@ class RedisQueuedLocks::Client
|
|
|
498
506
|
# rubocop:disable all
|
|
499
507
|
# @api public
|
|
500
508
|
# @since 1.16.0
|
|
501
|
-
# @version 1.
|
|
509
|
+
# @version 1.21.0
|
|
502
510
|
def lock_series!( # steep:ignore
|
|
503
511
|
*lock_names,
|
|
504
512
|
detailed_result: false,
|
|
@@ -570,7 +578,7 @@ class RedisQueuedLocks::Client
|
|
|
570
578
|
#
|
|
571
579
|
# @api public
|
|
572
580
|
# @since 1.0.0
|
|
573
|
-
# @version 1.
|
|
581
|
+
# @version 1.21.0
|
|
574
582
|
def lock!(
|
|
575
583
|
lock_name,
|
|
576
584
|
ttl: config['default_lock_ttl'],
|
|
@@ -665,7 +673,7 @@ class RedisQueuedLocks::Client
|
|
|
665
673
|
#
|
|
666
674
|
# @api public
|
|
667
675
|
# @since 1.0.0
|
|
668
|
-
# @version 1.
|
|
676
|
+
# @version 1.21.0
|
|
669
677
|
def unlock(
|
|
670
678
|
lock_name,
|
|
671
679
|
logger: config['logger'],
|
|
@@ -684,8 +692,9 @@ class RedisQueuedLocks::Client
|
|
|
684
692
|
redis_client,
|
|
685
693
|
lock_name,
|
|
686
694
|
config['lock_index_enabled'],
|
|
687
|
-
instrumenter,
|
|
688
695
|
logger,
|
|
696
|
+
instrumenter,
|
|
697
|
+
instrument,
|
|
689
698
|
log_sampling_enabled,
|
|
690
699
|
log_sampling_percent,
|
|
691
700
|
log_sampler,
|
|
@@ -742,7 +751,7 @@ class RedisQueuedLocks::Client
|
|
|
742
751
|
#
|
|
743
752
|
# @api public
|
|
744
753
|
# @since 1.18.0
|
|
745
|
-
# @version 1.
|
|
754
|
+
# @version 1.21.0
|
|
746
755
|
def unlock_read(
|
|
747
756
|
lock_name,
|
|
748
757
|
acquirer_id: nil,
|
|
@@ -763,8 +772,8 @@ class RedisQueuedLocks::Client
|
|
|
763
772
|
redis_client,
|
|
764
773
|
lock_name,
|
|
765
774
|
acquirer_id || current_acquirer_id(identity:),
|
|
766
|
-
instrumenter,
|
|
767
775
|
logger,
|
|
776
|
+
instrumenter,
|
|
768
777
|
instrument,
|
|
769
778
|
log_sampling_enabled,
|
|
770
779
|
log_sampling_percent,
|
|
@@ -902,19 +911,14 @@ class RedisQueuedLocks::Client
|
|
|
902
911
|
RedisQueuedLocks::Resource.possible_host_identifiers(identity)
|
|
903
912
|
end
|
|
904
913
|
|
|
905
|
-
#
|
|
906
|
-
#
|
|
907
|
-
#
|
|
908
|
-
# (2) set new ttl that is calculated as "current pttl + additional milliseconds"
|
|
909
|
-
# What can happen during these steps
|
|
910
|
-
# - lock is expired between commands or before the first command;
|
|
911
|
-
# - lock is expired before the second command;
|
|
912
|
-
# - lock is expired AND newly acquired by another process (so you will extend the
|
|
913
|
-
# totally new lock with fresh PTTL);
|
|
914
|
+
# The write lock TTL is extended atomically by one Lua script (`PTTL` + `PEXPIRE` with
|
|
915
|
+
# "current pttl + additional milliseconds") without the lock owner check: when your lock is
|
|
916
|
+
# expired and newly obtained by another acquirer, the lock of that acquirer is extended.
|
|
914
917
|
# Use it at your own risk and consider async nature when calling this method.
|
|
915
918
|
#
|
|
916
919
|
# @param lock_name [String]
|
|
917
|
-
# @param milliseconds [Integer]
|
|
920
|
+
# @param milliseconds [Integer]
|
|
921
|
+
# How many milliseconds should be added: a positive Integer.
|
|
918
922
|
# @option read_write_mode [Symbol]
|
|
919
923
|
# - `:write` (default) - extends the write lock (the method description above);
|
|
920
924
|
# - `:read` - extends the read lock of the current acquirer only (an expired read lock or
|
|
@@ -944,7 +948,7 @@ class RedisQueuedLocks::Client
|
|
|
944
948
|
#
|
|
945
949
|
# @api public
|
|
946
950
|
# @since 1.0.0
|
|
947
|
-
# @version 1.
|
|
951
|
+
# @version 1.21.0
|
|
948
952
|
def extend_lock_ttl(
|
|
949
953
|
lock_name,
|
|
950
954
|
milliseconds,
|
|
@@ -1008,7 +1012,7 @@ class RedisQueuedLocks::Client
|
|
|
1008
1012
|
#
|
|
1009
1013
|
# @api public
|
|
1010
1014
|
# @since 1.0.0
|
|
1011
|
-
# @version 1.
|
|
1015
|
+
# @version 1.21.0
|
|
1012
1016
|
def clear_locks(
|
|
1013
1017
|
batch_size: config['lock_release_batch_size'],
|
|
1014
1018
|
logger: config['logger'],
|
|
@@ -1097,12 +1101,12 @@ class RedisQueuedLocks::Client
|
|
|
1097
1101
|
#
|
|
1098
1102
|
# @api public
|
|
1099
1103
|
# @since 1.14.0
|
|
1100
|
-
# @version 1.
|
|
1104
|
+
# @version 1.21.0
|
|
1101
1105
|
def clear_locks_of(
|
|
1102
1106
|
host_id:,
|
|
1103
1107
|
acquirer_id:,
|
|
1104
1108
|
lock_scan_size: config['clear_locks_of__lock_scan_size'],
|
|
1105
|
-
queue_scan_size: config['clear_locks_of__queue_scan_size'],
|
|
1109
|
+
queue_scan_size: config['clear_locks_of__queue_scan_size'],
|
|
1106
1110
|
logger: config['logger'],
|
|
1107
1111
|
instrumenter: config['instrumenter'],
|
|
1108
1112
|
instrument: nil,
|
|
@@ -1140,7 +1144,8 @@ class RedisQueuedLocks::Client
|
|
|
1140
1144
|
# Release all locks of the current acquirer/host and
|
|
1141
1145
|
# remove the current acquirer/host from all queues;
|
|
1142
1146
|
#
|
|
1143
|
-
# @option
|
|
1147
|
+
# @option lock_scan_size [Integer]
|
|
1148
|
+
# @option queue_scan_size [Integer]
|
|
1144
1149
|
# @option logger [::Logger,#debug]
|
|
1145
1150
|
# @option instrumenter [#notify] See `config['instrumenter']` docs for details.
|
|
1146
1151
|
# @option instrument [NilClass,Any]
|
|
@@ -1159,9 +1164,10 @@ class RedisQueuedLocks::Client
|
|
|
1159
1164
|
#
|
|
1160
1165
|
# @api public
|
|
1161
1166
|
# @since 1.14.0
|
|
1167
|
+
# @version 1.21.0
|
|
1162
1168
|
def clear_current_locks(
|
|
1163
1169
|
lock_scan_size: config['clear_locks_of__lock_scan_size'],
|
|
1164
|
-
queue_scan_size: config['clear_locks_of__queue_scan_size'],
|
|
1170
|
+
queue_scan_size: config['clear_locks_of__queue_scan_size'],
|
|
1165
1171
|
logger: config['logger'],
|
|
1166
1172
|
instrumenter: config['instrumenter'],
|
|
1167
1173
|
instrument: nil,
|
|
@@ -1216,7 +1222,7 @@ class RedisQueuedLocks::Client
|
|
|
1216
1222
|
#
|
|
1217
1223
|
# @api public
|
|
1218
1224
|
# @since 1.0.0
|
|
1219
|
-
# @version 1.
|
|
1225
|
+
# @version 1.21.0
|
|
1220
1226
|
def locks(scan_size: config['key_extraction_batch_size'], with_info: false)
|
|
1221
1227
|
RedisQueuedLocks::Acquirer::Locks.locks(
|
|
1222
1228
|
redis_client, scan_size:, with_info:, lock_index_enabled: config['lock_index_enabled']
|
|
@@ -1230,8 +1236,9 @@ class RedisQueuedLocks::Client
|
|
|
1230
1236
|
#
|
|
1231
1237
|
# @api public
|
|
1232
1238
|
# @since 1.0.0
|
|
1233
|
-
|
|
1234
|
-
|
|
1239
|
+
# @version 1.21.0
|
|
1240
|
+
def locks_info(scan_size: config['key_extraction_batch_size'])
|
|
1241
|
+
locks(scan_size:, with_info: true) #: RedisQueuedLocks::Acquirer::Locks::locksInfo
|
|
1235
1242
|
end
|
|
1236
1243
|
|
|
1237
1244
|
# @option scan_size [Integer]
|
|
@@ -1253,7 +1260,7 @@ class RedisQueuedLocks::Client
|
|
|
1253
1260
|
#
|
|
1254
1261
|
# @api public
|
|
1255
1262
|
# @since 1.0.0
|
|
1256
|
-
# @version 1.
|
|
1263
|
+
# @version 1.21.0
|
|
1257
1264
|
def queues(scan_size: config['key_extraction_batch_size'], with_info: false)
|
|
1258
1265
|
RedisQueuedLocks::Acquirer::Queues.queues(
|
|
1259
1266
|
redis_client, scan_size:, with_info:, lock_index_enabled: config['lock_index_enabled']
|
|
@@ -1267,8 +1274,9 @@ class RedisQueuedLocks::Client
|
|
|
1267
1274
|
#
|
|
1268
1275
|
# @api public
|
|
1269
1276
|
# @since 1.0.0
|
|
1277
|
+
# @version 1.21.0
|
|
1270
1278
|
def queues_info(scan_size: config['key_extraction_batch_size'])
|
|
1271
|
-
queues(scan_size:, with_info: true)
|
|
1279
|
+
queues(scan_size:, with_info: true) #: RedisQueuedLocks::Acquirer::Queues::queuesInfo
|
|
1272
1280
|
end
|
|
1273
1281
|
|
|
1274
1282
|
# @option scan_size [Integer]
|
|
@@ -1279,7 +1287,7 @@ class RedisQueuedLocks::Client
|
|
|
1279
1287
|
#
|
|
1280
1288
|
# @api public
|
|
1281
1289
|
# @since 1.0.0
|
|
1282
|
-
# @version 1.
|
|
1290
|
+
# @version 1.21.0
|
|
1283
1291
|
def keys(scan_size: config['key_extraction_batch_size'])
|
|
1284
1292
|
RedisQueuedLocks::Acquirer::Keys.keys(
|
|
1285
1293
|
redis_client, scan_size:, lock_index_enabled: config['lock_index_enabled']
|
|
@@ -1310,7 +1318,7 @@ class RedisQueuedLocks::Client
|
|
|
1310
1318
|
#
|
|
1311
1319
|
# @api public
|
|
1312
1320
|
# @since 1.0.0
|
|
1313
|
-
# @version 1.
|
|
1321
|
+
# @version 1.21.0
|
|
1314
1322
|
def clear_dead_requests(
|
|
1315
1323
|
dead_ttl: config['dead_request_ttl'],
|
|
1316
1324
|
scan_size: config['lock_release_batch_size'],
|
|
@@ -1365,21 +1373,55 @@ class RedisQueuedLocks::Client
|
|
|
1365
1373
|
# @option lock_index_grace_period [Integer]
|
|
1366
1374
|
# - the lock index score margin over the key lifetime (in milliseconds);
|
|
1367
1375
|
# - `config['lock_index_grace_period']` is used by default;
|
|
1376
|
+
# @option logger [::Logger,#debug]
|
|
1377
|
+
# @option instrumenter [#notify]
|
|
1378
|
+
# @option instrument [NilClass,Any]
|
|
1379
|
+
# @option log_sampling_enabled [Boolean]
|
|
1380
|
+
# @option log_sampling_percent [Integer]
|
|
1381
|
+
# @option log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
|
|
1382
|
+
# @option log_sample_this [Boolean]
|
|
1383
|
+
# @option instr_sampling_enabled [Boolean]
|
|
1384
|
+
# @option instr_sampling_percent [Integer]
|
|
1385
|
+
# @option instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
|
|
1386
|
+
# @option instr_sample_this [Boolean]
|
|
1368
1387
|
# @return [Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
|
|
1369
1388
|
# Format: { ok: true, result: { idx_key_cnt: Integer } } (the number of indexed keys)
|
|
1370
1389
|
#
|
|
1371
1390
|
# @api public
|
|
1372
1391
|
# @since 1.20.0
|
|
1392
|
+
# @version 1.21.0
|
|
1373
1393
|
def reindex_locks(
|
|
1374
1394
|
scan_size: config['lock_release_batch_size'],
|
|
1375
1395
|
queue_ttl: config['default_queue_ttl'],
|
|
1376
|
-
lock_index_grace_period: config['lock_index_grace_period']
|
|
1396
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
1397
|
+
logger: config['logger'],
|
|
1398
|
+
instrumenter: config['instrumenter'],
|
|
1399
|
+
instrument: nil,
|
|
1400
|
+
log_sampling_enabled: config['log_sampling_enabled'],
|
|
1401
|
+
log_sampling_percent: config['log_sampling_percent'],
|
|
1402
|
+
log_sampler: config['log_sampler'],
|
|
1403
|
+
log_sample_this: false,
|
|
1404
|
+
instr_sampling_enabled: config['instr_sampling_enabled'],
|
|
1405
|
+
instr_sampling_percent: config['instr_sampling_percent'],
|
|
1406
|
+
instr_sampler: config['instr_sampler'],
|
|
1407
|
+
instr_sample_this: false
|
|
1377
1408
|
)
|
|
1378
1409
|
RedisQueuedLocks::Acquirer::ReindexLocks.reindex_locks(
|
|
1379
1410
|
redis_client,
|
|
1380
1411
|
scan_size,
|
|
1381
1412
|
queue_ttl,
|
|
1382
|
-
lock_index_grace_period
|
|
1413
|
+
lock_index_grace_period,
|
|
1414
|
+
logger,
|
|
1415
|
+
instrumenter,
|
|
1416
|
+
instrument,
|
|
1417
|
+
log_sampling_enabled,
|
|
1418
|
+
log_sampling_percent,
|
|
1419
|
+
log_sampler,
|
|
1420
|
+
log_sample_this,
|
|
1421
|
+
instr_sampling_enabled,
|
|
1422
|
+
instr_sampling_percent,
|
|
1423
|
+
instr_sampler,
|
|
1424
|
+
instr_sample_this
|
|
1383
1425
|
)
|
|
1384
1426
|
end
|
|
1385
1427
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.13.0
|
|
5
|
-
# @version 1.20.0
|
|
6
5
|
# rubocop:disable Metrics/ClassLength
|
|
7
6
|
class RedisQueuedLocks::Config
|
|
8
7
|
require_relative 'config/dsl'
|
|
@@ -57,36 +56,36 @@ class RedisQueuedLocks::Config
|
|
|
57
56
|
setting('swarm.flush_zombies.redis_config.pool_config', {})
|
|
58
57
|
|
|
59
58
|
validate('swarm.auto_swarm') { |val| val == true || val == false }
|
|
60
|
-
validate('swarm.supervisor.liveness_probing_period') { |val| val.is_a?(Integer) }
|
|
59
|
+
validate('swarm.supervisor.liveness_probing_period') { |val| val.is_a?(Integer) && val > 0 }
|
|
61
60
|
|
|
62
61
|
validate('swarm.probe_hosts.enabled_for_swarm') { |val| val == true || val == false }
|
|
63
62
|
validate('swarm.probe_hosts.redis_config.sentinel') { |val| val == true || val == false }
|
|
64
63
|
validate('swarm.probe_hosts.redis_config.pooled') { |val| val == true || val == false }
|
|
65
64
|
validate('swarm.probe_hosts.redis_config.config') { |val| val.is_a?(Hash) }
|
|
66
65
|
validate('swarm.probe_hosts.redis_config.pool_config') { |val| val.is_a?(Hash) }
|
|
67
|
-
validate('swarm.probe_hosts.probe_period') { |val| val.is_a?(Integer) }
|
|
66
|
+
validate('swarm.probe_hosts.probe_period') { |val| val.is_a?(Integer) && val > 0 }
|
|
68
67
|
|
|
69
68
|
validate('swarm.flush_zombies.enabled_for_swarm') { |val| val == true || val == false }
|
|
70
69
|
validate('swarm.flush_zombies.redis_config.sentinel') { |val| val == true || val == false }
|
|
71
70
|
validate('swarm.flush_zombies.redis_config.pooled') { |val| val == true || val == false }
|
|
72
71
|
validate('swarm.flush_zombies.redis_config.config') { |val| val.is_a?(Hash) }
|
|
73
72
|
validate('swarm.flush_zombies.redis_config.pool_config') { |val| val.is_a?(Hash) }
|
|
74
|
-
validate('swarm.flush_zombies.zombie_ttl') { |val| val.is_a?(Integer) }
|
|
75
|
-
validate('swarm.flush_zombies.zombie_lock_scan_size') { |val| val.is_a?(Integer) }
|
|
76
|
-
validate('swarm.flush_zombies.zombie_queue_scan_size') { |val| val.is_a?(Integer) }
|
|
77
|
-
validate('swarm.flush_zombies.zombie_flush_period') { |val| val.is_a?(Integer) }
|
|
73
|
+
validate('swarm.flush_zombies.zombie_ttl') { |val| val.is_a?(Integer) && val > 0 }
|
|
74
|
+
validate('swarm.flush_zombies.zombie_lock_scan_size') { |val| val.is_a?(Integer) && val > 0 }
|
|
75
|
+
validate('swarm.flush_zombies.zombie_queue_scan_size') { |val| val.is_a?(Integer) && val > 0 }
|
|
76
|
+
validate('swarm.flush_zombies.zombie_flush_period') { |val| val.is_a?(Integer) && val > 0 }
|
|
78
77
|
|
|
79
78
|
validate('retry_count') { |val| val == nil || (val.is_a?(Integer) && val >= 0) }
|
|
80
79
|
validate('retry_delay') { |val| val.is_a?(Integer) && val >= 0 }
|
|
81
80
|
validate('retry_jitter') { |val| val.is_a?(Integer) && val >= 0 }
|
|
82
81
|
validate('try_to_lock_timeout') { |val| val == nil || (val.is_a?(Integer) && val >= 0) }
|
|
83
|
-
validate('default_lock_ttl') { |val| val.is_a?(Integer) }
|
|
84
|
-
validate('default_queue_ttl') { |val| val.is_a?(Integer) }
|
|
82
|
+
validate('default_lock_ttl') { |val| val.is_a?(Integer) && val > 0 }
|
|
83
|
+
validate('default_queue_ttl') { |val| val.is_a?(Integer) && val > 0 }
|
|
85
84
|
validate('detailed_acq_timeout_error') { |val| val == true || val == false }
|
|
86
|
-
validate('lock_release_batch_size') { |val| val.is_a?(Integer) }
|
|
87
|
-
validate('clear_locks_of__lock_scan_size') { |val| val.is_a?(Integer) }
|
|
88
|
-
validate('clear_locks_of__queue_scan_size') { |val| val.is_a?(Integer) }
|
|
89
|
-
validate('key_extraction_batch_size') { |val| val.is_a?(Integer) }
|
|
85
|
+
validate('lock_release_batch_size') { |val| val.is_a?(Integer) && val > 0 }
|
|
86
|
+
validate('clear_locks_of__lock_scan_size') { |val| val.is_a?(Integer) && val > 0 }
|
|
87
|
+
validate('clear_locks_of__queue_scan_size') { |val| val.is_a?(Integer) && val > 0 }
|
|
88
|
+
validate('key_extraction_batch_size') { |val| val.is_a?(Integer) && val > 0 }
|
|
90
89
|
validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
|
|
91
90
|
validate('uniq_identifier') { |val| val.is_a?(Proc) }
|
|
92
91
|
validate('logger') { |val| RedisQueuedLocks::Logging.valid_interface?(val) }
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
# the one-line definition style (like "Error = Class.new(::StandardError)")
|
|
5
5
|
# is not used cuz that style can not be correctly defined via RBS types: we can not
|
|
6
6
|
# identify the superclass of our class for a constant;
|
|
7
|
+
# NOTE: (error hierarchy) each error inherits from the global base error of its branch:
|
|
8
|
+
# - `Error` (`::StandardError`) - the main hierarchy;
|
|
9
|
+
# - `ArgumentError` (`::ArgumentError`) - invalid arguments (the Ruby core branch that can not be
|
|
10
|
+
# a part of the main hierarchy);
|
|
11
|
+
# - `Exception` (`::Exception`) - super exceptional cases only: each error of this branch
|
|
12
|
+
# describes why it is not a `StandardError`;
|
|
7
13
|
module RedisQueuedLocks
|
|
8
14
|
# @api public
|
|
9
15
|
# @since 1.0.0
|
|
@@ -13,13 +19,29 @@ module RedisQueuedLocks
|
|
|
13
19
|
# @since 1.0.0
|
|
14
20
|
class ArgumentError < ::ArgumentError; end
|
|
15
21
|
|
|
22
|
+
# The base of the errors that should not be rescued by the code they interrupt
|
|
23
|
+
# (`rescue => e`, `rescue StandardError`, `rescue nil`): like `Timeout::ExitException`,
|
|
24
|
+
# they are rescued only by `rescue Exception` (and by the library itself).
|
|
25
|
+
# NOTE: inside `module RedisQueuedLocks` a bare `Exception` refers to this class;
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
# @since 1.21.0
|
|
29
|
+
class Exception < ::Exception; end # rubocop:disable Lint/InheritException
|
|
30
|
+
|
|
16
31
|
# @api public
|
|
17
32
|
# @since 1.0.0
|
|
18
33
|
class LockAlreadyObtainedError < Error; end
|
|
19
34
|
|
|
35
|
+
# Interrupts the lock acquirement after the `timeout` (`Timeout.timeout`); rescued by
|
|
36
|
+
# `WithAcqTimeout` (=> `LockAcquirementTimeoutError` / `:timeout_reached`).
|
|
37
|
+
# NOTE: (why `Exception`) the acquirement calls the logger and the instrumenter: a `StandardError`
|
|
38
|
+
# that interrupts them is swallowed by `rescue nil` of the log/instrumentation events, and the
|
|
39
|
+
# acquirement continues without the timeout (with `retry_count: nil` - without any limit);
|
|
40
|
+
#
|
|
20
41
|
# @api private
|
|
21
42
|
# @since 1.11.0
|
|
22
|
-
|
|
43
|
+
# @version 1.21.0
|
|
44
|
+
class LockAcquirementIntermediateTimeoutError < Exception; end
|
|
23
45
|
|
|
24
46
|
# @api public
|
|
25
47
|
# @since 1.0.0
|
|
@@ -29,11 +51,19 @@ module RedisQueuedLocks
|
|
|
29
51
|
# @since 1.0.0
|
|
30
52
|
class LockAcquirementRetryLimitError < Error; end
|
|
31
53
|
|
|
54
|
+
# Interrupts the `timed: true` block after the lock ttl (`Timeout.timeout`); rescued by
|
|
55
|
+
# `YieldExpire` (=> `TimedLockTimeoutError`).
|
|
56
|
+
# NOTE: (why `Exception`) a `StandardError` is swallowed by `rescue => e` / `rescue nil` of
|
|
57
|
+
# the user block: the block continues after the lock ttl (without the mutual exclusion:
|
|
58
|
+
# another acquirer can obtain the expired lock, and the write lock release after the block
|
|
59
|
+
# deletes its lock);
|
|
60
|
+
#
|
|
32
61
|
# @api private
|
|
33
62
|
# @since 1.12.0
|
|
34
|
-
|
|
63
|
+
# @version 1.21.0
|
|
64
|
+
class TimedLockIntermediateTimeoutError < Exception; end
|
|
35
65
|
|
|
36
|
-
# @api
|
|
66
|
+
# @api public
|
|
37
67
|
# @since 1.0.0
|
|
38
68
|
class TimedLockTimeoutError < Error; end
|
|
39
69
|
|
|
@@ -57,7 +87,7 @@ module RedisQueuedLocks
|
|
|
57
87
|
# @since 1.13.0
|
|
58
88
|
class ConfigNotFoundError < ConfigError; end
|
|
59
89
|
|
|
60
|
-
# @api
|
|
90
|
+
# @api public
|
|
61
91
|
# @since 1.13.0
|
|
62
92
|
class ConfigValidationError < ConfigError; end
|
|
63
93
|
end
|
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
# @api public
|
|
4
4
|
# @since 1.6.0
|
|
5
5
|
module RedisQueuedLocks::Instrument::Sampler
|
|
6
|
-
# @return [Range]
|
|
7
|
-
#
|
|
8
|
-
# @api private
|
|
9
|
-
# @since 1.6.0
|
|
10
|
-
SAMPLING_PERCENT_RANGE = (0..100)
|
|
11
|
-
|
|
12
6
|
class << self
|
|
13
7
|
# Super simple probalistic function based on the `weight` of <true>/<false> values.
|
|
14
8
|
# Requires the <percent> parameter as the required percent of <true> values sampled.
|
|
@@ -20,8 +14,11 @@ module RedisQueuedLocks::Instrument::Sampler
|
|
|
20
14
|
#
|
|
21
15
|
# @api public
|
|
22
16
|
# @since 1.6.0
|
|
17
|
+
# @version 1.21.0
|
|
23
18
|
def sampling_happened?(sampling_percent)
|
|
24
|
-
|
|
19
|
+
# NOTE: one of 100 equally probable values (0..99): `sampling_percent` of them are lower
|
|
20
|
+
# (0% => never, 100% => always);
|
|
21
|
+
SecureRandom.rand(100) < sampling_percent
|
|
25
22
|
end
|
|
26
23
|
end
|
|
27
24
|
end
|
|
@@ -3,12 +3,6 @@
|
|
|
3
3
|
# @api public
|
|
4
4
|
# @since 1.5.0
|
|
5
5
|
module RedisQueuedLocks::Logging::Sampler
|
|
6
|
-
# @return [Range]
|
|
7
|
-
#
|
|
8
|
-
# @api private
|
|
9
|
-
# @since 1.6.0
|
|
10
|
-
SAMPLING_PERCENT_RANGE = (0..100)
|
|
11
|
-
|
|
12
6
|
class << self
|
|
13
7
|
# Super simple probalistic function based on the `weight` of <true>/<false> values.
|
|
14
8
|
# Requires the <percent> parameter as the required percent of <true> values sampled.
|
|
@@ -20,9 +14,11 @@ module RedisQueuedLocks::Logging::Sampler
|
|
|
20
14
|
#
|
|
21
15
|
# @api public
|
|
22
16
|
# @since 1.5.0
|
|
23
|
-
# @version 1.
|
|
17
|
+
# @version 1.21.0
|
|
24
18
|
def sampling_happened?(sampling_percent)
|
|
25
|
-
|
|
19
|
+
# NOTE: one of 100 equally probable values (0..99): `sampling_percent` of them are lower
|
|
20
|
+
# (0% => never, 100% => always);
|
|
21
|
+
SecureRandom.rand(100) < sampling_percent
|
|
26
22
|
end
|
|
27
23
|
end
|
|
28
24
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.0.0
|
|
5
|
+
# @version 1.20.0
|
|
5
6
|
# rubocop:disable Metrics/ModuleLength
|
|
6
7
|
module RedisQueuedLocks::Resource
|
|
7
8
|
# @return [String]
|
|
@@ -366,14 +367,23 @@ module RedisQueuedLocks::Resource
|
|
|
366
367
|
(acquirer_position + queue_ttl) < Time.now.to_f
|
|
367
368
|
end
|
|
368
369
|
|
|
369
|
-
#
|
|
370
|
-
#
|
|
370
|
+
# NOTE: (debug) not used by the library: a manual runtime check (in a console) of the lock that
|
|
371
|
+
# a queue belongs to (e.g. `Resource.lock_key_from_queue(client.queues.first)`);
|
|
372
|
+
#
|
|
373
|
+
# @param lock_queue [String] `rql:lock_queue:<name>` or `rql:lock_read_queue:<name>`.
|
|
374
|
+
# @return [String,NilClass]
|
|
375
|
+
# - `rql:lock:<name>` for the write lock request queue and for the read lock request queue;
|
|
376
|
+
# - `nil` for any other key;
|
|
371
377
|
#
|
|
372
378
|
# @api private
|
|
373
379
|
# @since 1.0.0
|
|
380
|
+
# @version 1.21.0
|
|
374
381
|
def lock_key_from_queue(lock_queue)
|
|
375
|
-
|
|
376
|
-
|
|
382
|
+
key_family = lock_key_family(lock_queue)
|
|
383
|
+
return unless key_family == :lock_queue || key_family == :read_lock_queue
|
|
384
|
+
|
|
385
|
+
lock_name = lock_name_from_key(lock_queue, key_family) #: String
|
|
386
|
+
prepare_lock_key(lock_name)
|
|
377
387
|
end
|
|
378
388
|
|
|
379
389
|
# @return [Integer]
|
|
@@ -7,19 +7,23 @@ module RedisQueuedLocks::Swarm::RedisClientBuilder
|
|
|
7
7
|
# @option pooled [Boolean]
|
|
8
8
|
# @option sentinel [Boolean]
|
|
9
9
|
# @option config [Hash]
|
|
10
|
+
# @option pool_config [Hash]
|
|
10
11
|
# @return [RedisClient|RedisClient::Pooled]
|
|
11
12
|
#
|
|
12
13
|
# @api private
|
|
13
14
|
# @since 1.9.0
|
|
15
|
+
# @version 1.21.0
|
|
14
16
|
# rubocop:disable Style/RedundantAssignment
|
|
15
17
|
def build(pooled: false, sentinel: false, config: {}, pool_config: {})
|
|
16
|
-
config
|
|
17
|
-
|
|
18
|
+
# NOTE: the passed hashes are not mutated: they are the values of the client config (they
|
|
19
|
+
# can be frozen and are shared with the zombie flusher Ractor when they are shareable);
|
|
20
|
+
redis_options = config.transform_keys(&:to_sym)
|
|
21
|
+
pool_options = pool_config.transform_keys(&:to_sym)
|
|
18
22
|
|
|
19
23
|
redis_config =
|
|
20
|
-
sentinel ? sentinel_config(
|
|
24
|
+
sentinel ? sentinel_config(redis_options) : non_sentinel_config(redis_options)
|
|
21
25
|
redis_client =
|
|
22
|
-
pooled ? pooled_client(redis_config,
|
|
26
|
+
pooled ? pooled_client(redis_config, pool_options) : non_pooled_client(redis_config)
|
|
23
27
|
|
|
24
28
|
redis_client
|
|
25
29
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.9.0
|
|
5
|
+
# @version 1.17.0
|
|
5
6
|
# rubocop:disable Metrics/ClassLength
|
|
6
7
|
class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
7
8
|
# @since 1.9.0
|
|
@@ -78,7 +79,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
|
78
79
|
# @return [void]
|
|
79
80
|
#
|
|
80
81
|
# @api private
|
|
81
|
-
# @since
|
|
82
|
+
# @since 1.9.0
|
|
82
83
|
def reswarm_if_dead!
|
|
83
84
|
return unless enabled?
|
|
84
85
|
|
|
@@ -154,7 +155,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
|
|
154
155
|
|
|
155
156
|
# Swarm element lifecycle have the following flow:
|
|
156
157
|
# => 1) init (swarm!): create a thread, main loop is not started;
|
|
157
|
-
# => 2) start (swarm_loop__start): run the main
|
|
158
|
+
# => 2) start (swarm_loop__start): run the main loop inside the created thread;
|
|
158
159
|
# => 3) stop (swarm_loop__stop): stop the main loop inside the created thread;
|
|
159
160
|
# => 4) terminate (swarm_element__termiante): kill the created thread;
|
|
160
161
|
# In addition you should implement `#spawn_main_loop!` method that will be the main logic
|