redis_queued_locks 1.18.0 → 1.20.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 +65 -13
- data/.claude/rules/acquirer.md +29 -11
- data/.claude/rules/arguments.md +3 -2
- data/.claude/rules/git.md +3 -2
- data/.claude/rules/logic.md +4 -3
- data/.claude/rules/swarm.md +12 -7
- data/.claude/rules/tests.md +6 -0
- data/.claude/rules/type-checking.md +2 -1
- data/.claude/rules/visitors.md +1 -1
- data/.claude/skills/rql-code-review/SKILL.md +372 -0
- data/.claude/skills/rql-code-review/templates/report.en.md +31 -0
- data/.claude/skills/rql-code-review/templates/report.ru.md +31 -0
- data/CHANGELOG.md +74 -0
- data/CLAUDE.md +4 -2
- data/README.md +338 -24
- data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rb +684 -128
- data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire.rb +3 -1
- data/lib/redis_queued_locks/acquirer/acquire_lock.rb +24 -3
- data/lib/redis_queued_locks/acquirer/clear_dead_requests.rb +145 -18
- data/lib/redis_queued_locks/acquirer/extend_lock_ttl.rb +71 -10
- data/lib/redis_queued_locks/acquirer/keys.rb +142 -7
- data/lib/redis_queued_locks/acquirer/lock_info.rb +10 -4
- data/lib/redis_queued_locks/acquirer/lock_series_poc.rb +26 -7
- data/lib/redis_queued_locks/acquirer/locks.rb +195 -83
- data/lib/redis_queued_locks/acquirer/queue_info.rb +12 -18
- data/lib/redis_queued_locks/acquirer/queues.rb +114 -50
- data/lib/redis_queued_locks/acquirer/reindex_locks.rb +140 -0
- data/lib/redis_queued_locks/acquirer/release_all_locks.rb +147 -58
- data/lib/redis_queued_locks/acquirer/release_lock.rb +52 -19
- data/lib/redis_queued_locks/acquirer/release_locks_of.rb +235 -66
- data/lib/redis_queued_locks/acquirer.rb +1 -0
- data/lib/redis_queued_locks/client.rb +116 -16
- data/lib/redis_queued_locks/config.rb +5 -1
- data/lib/redis_queued_locks/resource.rb +99 -2
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +332 -61
- data/lib/redis_queued_locks/swarm/zombie_info.rb +203 -53
- data/lib/redis_queued_locks/swarm.rb +15 -4
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +107 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +1 -1
- data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +2 -0
- data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +10 -1
- data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +5 -2
- data/sig/redis_queued_locks/acquirer/keys.rbs +11 -1
- data/sig/redis_queued_locks/acquirer/locks.rbs +15 -6
- data/sig/redis_queued_locks/acquirer/queues.rbs +9 -2
- data/sig/redis_queued_locks/acquirer/reindex_locks.rbs +31 -0
- data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +8 -1
- data/sig/redis_queued_locks/acquirer/release_lock.rbs +3 -1
- data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +20 -1
- data/sig/redis_queued_locks/client.rbs +9 -0
- data/sig/redis_queued_locks/resource.rbs +6 -0
- data/sig/redis_queued_locks/swarm/flush_zombies.rbs +51 -1
- data/sig/redis_queued_locks/swarm/zombie_info.rbs +58 -7
- data/sig/vendor/redis_client.rbs +1 -0
- metadata +6 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api public
|
|
4
4
|
# @since 1.0.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.20.0
|
|
6
6
|
# rubocop:disable Metrics/ClassLength
|
|
7
7
|
class RedisQueuedLocks::Client
|
|
8
8
|
# @return [RedisClient]
|
|
@@ -174,6 +174,14 @@ class RedisQueuedLocks::Client
|
|
|
174
174
|
# Lock's time to live (in milliseconds).
|
|
175
175
|
# @option queue_ttl [Integer]
|
|
176
176
|
# Lifetime of the acuier's lock request. In seconds.
|
|
177
|
+
# @option lock_index_grace_period [Integer]
|
|
178
|
+
# - The lock index score margin over the lock key lifetime (in milliseconds): the lock name
|
|
179
|
+
# stays in the lock index (`rql:index:locks`) for this period after the expiration of
|
|
180
|
+
# the lock keys;
|
|
181
|
+
# - should be greater than the client clock skew between your hosts plus the network latency;
|
|
182
|
+
# - used only with the enabled lock index (`config['lock_index_enabled']`, the client-wide
|
|
183
|
+
# mode: the lock index is maintained by all lock acquirements of the client);
|
|
184
|
+
# - pre-configured in `config['lock_index_grace_period']`;
|
|
177
185
|
# @option timeout [Integer,NilClass]
|
|
178
186
|
# Time period whe should try to acquire the lock (in seconds). Nil means "without timeout".
|
|
179
187
|
# @option timed [Boolean]
|
|
@@ -331,12 +339,13 @@ class RedisQueuedLocks::Client
|
|
|
331
339
|
#
|
|
332
340
|
# @api public
|
|
333
341
|
# @since 1.0.0
|
|
334
|
-
# @version 1.
|
|
342
|
+
# @version 1.20.0
|
|
335
343
|
# rubocop:disable Metrics/MethodLength
|
|
336
344
|
def lock(
|
|
337
345
|
lock_name,
|
|
338
346
|
ttl: config['default_lock_ttl'],
|
|
339
347
|
queue_ttl: config['default_queue_ttl'],
|
|
348
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
340
349
|
timeout: config['try_to_lock_timeout'],
|
|
341
350
|
timed: config['is_timed_by_default'],
|
|
342
351
|
retry_count: config['retry_count'],
|
|
@@ -373,6 +382,8 @@ class RedisQueuedLocks::Client
|
|
|
373
382
|
ractor_id: RedisQueuedLocks::Resource.get_ractor_id,
|
|
374
383
|
ttl:,
|
|
375
384
|
queue_ttl:,
|
|
385
|
+
lock_index_enabled: config['lock_index_enabled'],
|
|
386
|
+
lock_index_grace_period:,
|
|
376
387
|
timeout:,
|
|
377
388
|
timed:,
|
|
378
389
|
retry_count:,
|
|
@@ -407,11 +418,13 @@ class RedisQueuedLocks::Client
|
|
|
407
418
|
# rubocop:disable all
|
|
408
419
|
# @api public
|
|
409
420
|
# @since 1.16.0
|
|
421
|
+
# @version 1.20.0
|
|
410
422
|
def lock_series( # steep:ignore
|
|
411
423
|
*lock_names,
|
|
412
424
|
detailed_result: false,
|
|
413
425
|
ttl: config['default_lock_ttl'],
|
|
414
426
|
queue_ttl: config['default_queue_ttl'],
|
|
427
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
415
428
|
timeout: config['try_to_lock_timeout'],
|
|
416
429
|
timed: config['is_timed_by_default'],
|
|
417
430
|
retry_count: config['retry_count'],
|
|
@@ -449,6 +462,8 @@ class RedisQueuedLocks::Client
|
|
|
449
462
|
ractor_id: RedisQueuedLocks::Resource.get_ractor_id,
|
|
450
463
|
ttl:,
|
|
451
464
|
queue_ttl:,
|
|
465
|
+
lock_index_enabled: config['lock_index_enabled'],
|
|
466
|
+
lock_index_grace_period:,
|
|
452
467
|
timeout:,
|
|
453
468
|
timed:,
|
|
454
469
|
retry_count:,
|
|
@@ -483,11 +498,13 @@ class RedisQueuedLocks::Client
|
|
|
483
498
|
# rubocop:disable all
|
|
484
499
|
# @api public
|
|
485
500
|
# @since 1.16.0
|
|
501
|
+
# @version 1.20.0
|
|
486
502
|
def lock_series!( # steep:ignore
|
|
487
503
|
*lock_names,
|
|
488
504
|
detailed_result: false,
|
|
489
505
|
ttl: config['default_lock_ttl'],
|
|
490
506
|
queue_ttl: config['default_queue_ttl'],
|
|
507
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
491
508
|
timeout: config['try_to_lock_timeout'],
|
|
492
509
|
timed: config['is_timed_by_default'],
|
|
493
510
|
retry_count: config['retry_count'],
|
|
@@ -519,6 +536,7 @@ class RedisQueuedLocks::Client
|
|
|
519
536
|
detailed_result:,
|
|
520
537
|
ttl:,
|
|
521
538
|
queue_ttl:,
|
|
539
|
+
lock_index_grace_period:,
|
|
522
540
|
timeout:,
|
|
523
541
|
timed:,
|
|
524
542
|
retry_count:,
|
|
@@ -552,11 +570,12 @@ class RedisQueuedLocks::Client
|
|
|
552
570
|
#
|
|
553
571
|
# @api public
|
|
554
572
|
# @since 1.0.0
|
|
555
|
-
# @version 1.
|
|
573
|
+
# @version 1.20.0
|
|
556
574
|
def lock!(
|
|
557
575
|
lock_name,
|
|
558
576
|
ttl: config['default_lock_ttl'],
|
|
559
577
|
queue_ttl: config['default_queue_ttl'],
|
|
578
|
+
lock_index_grace_period: config['lock_index_grace_period'],
|
|
560
579
|
timeout: config['try_to_lock_timeout'],
|
|
561
580
|
timed: config['is_timed_by_default'],
|
|
562
581
|
retry_count: config['retry_count'],
|
|
@@ -587,6 +606,7 @@ class RedisQueuedLocks::Client
|
|
|
587
606
|
lock_name,
|
|
588
607
|
ttl:,
|
|
589
608
|
queue_ttl:,
|
|
609
|
+
lock_index_grace_period:,
|
|
590
610
|
timeout:,
|
|
591
611
|
timed:,
|
|
592
612
|
retry_count:,
|
|
@@ -645,7 +665,7 @@ class RedisQueuedLocks::Client
|
|
|
645
665
|
#
|
|
646
666
|
# @api public
|
|
647
667
|
# @since 1.0.0
|
|
648
|
-
# @version 1.
|
|
668
|
+
# @version 1.20.0
|
|
649
669
|
def unlock(
|
|
650
670
|
lock_name,
|
|
651
671
|
logger: config['logger'],
|
|
@@ -663,6 +683,7 @@ class RedisQueuedLocks::Client
|
|
|
663
683
|
RedisQueuedLocks::Acquirer::ReleaseLock.release_lock(
|
|
664
684
|
redis_client,
|
|
665
685
|
lock_name,
|
|
686
|
+
config['lock_index_enabled'],
|
|
666
687
|
instrumenter,
|
|
667
688
|
logger,
|
|
668
689
|
log_sampling_enabled,
|
|
@@ -677,14 +698,20 @@ class RedisQueuedLocks::Client
|
|
|
677
698
|
end
|
|
678
699
|
alias_method :release_lock, :unlock
|
|
679
700
|
|
|
680
|
-
# Releases the read lock of the current acquirer
|
|
681
|
-
#
|
|
682
|
-
# read locks obtained without
|
|
701
|
+
# Releases the read lock of the current acquirer (or of the custom acquirer passed via
|
|
702
|
+
# `acquirer_id:`) only: other read locks, the write lock and lock requests are not affected
|
|
703
|
+
# (`#unlock` releases the lock entirely). It is suitable for read locks obtained without
|
|
704
|
+
# a block of code.
|
|
683
705
|
#
|
|
684
706
|
# @param lock_name [String] The lock name whose read lock should be released.
|
|
707
|
+
# @option acquirer_id [String,NilClass]
|
|
708
|
+
# - The acquirer whose read lock should be released (`nil` by default);
|
|
709
|
+
# - `nil` => the current acquirer is used (calculated from `identity:`, see `#current_acquirer_id`);
|
|
710
|
+
# - can be extracted from the lock data (`#lock_info`, `#locks_info`, `#queue_info`, `#queues_info`);
|
|
685
711
|
# @option identity [String]
|
|
686
712
|
# Unique acquirer identity (the same as for `#lock`) that is used to calculate the current
|
|
687
|
-
# acquirer id (see `#current_acquirer_id`).
|
|
713
|
+
# acquirer id (see `#current_acquirer_id`). Ignored when `acquirer_id:` is passed: the acquirer id
|
|
714
|
+
# already contains its own identity.
|
|
688
715
|
# @option logger [::Logger,#debug]
|
|
689
716
|
# @option instrumenter [#notify]
|
|
690
717
|
# @option instrument [NilClass,Any]
|
|
@@ -707,10 +734,18 @@ class RedisQueuedLocks::Client
|
|
|
707
734
|
# }
|
|
708
735
|
# }
|
|
709
736
|
#
|
|
737
|
+
# @example Release the read lock of the current acquirer:
|
|
738
|
+
# client.unlock_read('report')
|
|
739
|
+
#
|
|
740
|
+
# @example Release the read lock of a different acquirer:
|
|
741
|
+
# client.unlock_read('report', acquirer_id: "rql:acq:62681/2016/2024/2032/b30ec5e4bea10512")
|
|
742
|
+
#
|
|
710
743
|
# @api public
|
|
711
744
|
# @since 1.18.0
|
|
745
|
+
# @version 1.19.0
|
|
712
746
|
def unlock_read(
|
|
713
747
|
lock_name,
|
|
748
|
+
acquirer_id: nil,
|
|
714
749
|
identity: uniq_identity,
|
|
715
750
|
logger: config['logger'],
|
|
716
751
|
instrumenter: config['instrumenter'],
|
|
@@ -727,7 +762,7 @@ class RedisQueuedLocks::Client
|
|
|
727
762
|
RedisQueuedLocks::Acquirer::ReleaseReadLock.release_read_lock(
|
|
728
763
|
redis_client,
|
|
729
764
|
lock_name,
|
|
730
|
-
current_acquirer_id(identity:),
|
|
765
|
+
acquirer_id || current_acquirer_id(identity:),
|
|
731
766
|
instrumenter,
|
|
732
767
|
logger,
|
|
733
768
|
instrument,
|
|
@@ -909,7 +944,7 @@ class RedisQueuedLocks::Client
|
|
|
909
944
|
#
|
|
910
945
|
# @api public
|
|
911
946
|
# @since 1.0.0
|
|
912
|
-
# @version 1.
|
|
947
|
+
# @version 1.20.0
|
|
913
948
|
def extend_lock_ttl(
|
|
914
949
|
lock_name,
|
|
915
950
|
milliseconds,
|
|
@@ -935,6 +970,7 @@ class RedisQueuedLocks::Client
|
|
|
935
970
|
read_write_mode,
|
|
936
971
|
all_read_locks,
|
|
937
972
|
current_acquirer_id(identity:),
|
|
973
|
+
config['lock_index_enabled'],
|
|
938
974
|
logger,
|
|
939
975
|
instrumenter,
|
|
940
976
|
instrument,
|
|
@@ -953,6 +989,7 @@ class RedisQueuedLocks::Client
|
|
|
953
989
|
# Returns:
|
|
954
990
|
# - :rel_time - (milliseconds) - time spent to release all locks and queues;
|
|
955
991
|
# - :rel_key_cnt - (integer) - the number of released redis keys (queus+locks);
|
|
992
|
+
# Locks are found via the keyspace SCAN or via the lock index (`config['lock_index_enabled']`).
|
|
956
993
|
#
|
|
957
994
|
# @option batch_size [Integer]
|
|
958
995
|
# @option logger [::Logger,#debug]
|
|
@@ -971,7 +1008,7 @@ class RedisQueuedLocks::Client
|
|
|
971
1008
|
#
|
|
972
1009
|
# @api public
|
|
973
1010
|
# @since 1.0.0
|
|
974
|
-
# @version 1.
|
|
1011
|
+
# @version 1.20.0
|
|
975
1012
|
def clear_locks(
|
|
976
1013
|
batch_size: config['lock_release_batch_size'],
|
|
977
1014
|
logger: config['logger'],
|
|
@@ -989,6 +1026,7 @@ class RedisQueuedLocks::Client
|
|
|
989
1026
|
RedisQueuedLocks::Acquirer::ReleaseAllLocks.release_all_locks(
|
|
990
1027
|
redis_client,
|
|
991
1028
|
batch_size,
|
|
1029
|
+
config['lock_index_enabled'],
|
|
992
1030
|
logger,
|
|
993
1031
|
instrumenter,
|
|
994
1032
|
instrument,
|
|
@@ -1010,6 +1048,8 @@ class RedisQueuedLocks::Client
|
|
|
1010
1048
|
# current puma request thread is killed by Rack::Timeout and you need to cleanup all zombie RQL
|
|
1011
1049
|
# locks and lock reuqests obtained during the request processing).
|
|
1012
1050
|
#
|
|
1051
|
+
# Locks are found via the keyspace SCAN or via the lock index (`config['lock_index_enabled']`).
|
|
1052
|
+
#
|
|
1013
1053
|
# Identifiers can be extracted via:
|
|
1014
1054
|
# - `#current_host_id`
|
|
1015
1055
|
# - `#current_acquirer_id`
|
|
@@ -1057,6 +1097,7 @@ class RedisQueuedLocks::Client
|
|
|
1057
1097
|
#
|
|
1058
1098
|
# @api public
|
|
1059
1099
|
# @since 1.14.0
|
|
1100
|
+
# @version 1.20.0
|
|
1060
1101
|
def clear_locks_of(
|
|
1061
1102
|
host_id:,
|
|
1062
1103
|
acquirer_id:,
|
|
@@ -1080,6 +1121,7 @@ class RedisQueuedLocks::Client
|
|
|
1080
1121
|
redis_client,
|
|
1081
1122
|
lock_scan_size,
|
|
1082
1123
|
queue_scan_size,
|
|
1124
|
+
config['lock_index_enabled'],
|
|
1083
1125
|
logger,
|
|
1084
1126
|
instrumenter,
|
|
1085
1127
|
instrument,
|
|
@@ -1169,10 +1211,16 @@ class RedisQueuedLocks::Client
|
|
|
1169
1211
|
# at the moment of Redis'es SCAN;
|
|
1170
1212
|
# @return [Set<String>,Set<Hash<Symbol,Any>>]
|
|
1171
1213
|
#
|
|
1214
|
+
# NOTE: locks are found via the keyspace SCAN or via the lock index (ZSCAN, the enabled
|
|
1215
|
+
# `config['lock_index_enabled']`: `scan_size` is the number of locks in one batch);
|
|
1216
|
+
#
|
|
1172
1217
|
# @api public
|
|
1173
1218
|
# @since 1.0.0
|
|
1219
|
+
# @version 1.20.0
|
|
1174
1220
|
def locks(scan_size: config['key_extraction_batch_size'], with_info: false)
|
|
1175
|
-
RedisQueuedLocks::Acquirer::Locks.locks(
|
|
1221
|
+
RedisQueuedLocks::Acquirer::Locks.locks(
|
|
1222
|
+
redis_client, scan_size:, with_info:, lock_index_enabled: config['lock_index_enabled']
|
|
1223
|
+
)
|
|
1176
1224
|
end
|
|
1177
1225
|
|
|
1178
1226
|
# Extracts lock keys with their info. See #locks(with_info: true) for details.
|
|
@@ -1200,10 +1248,16 @@ class RedisQueuedLocks::Client
|
|
|
1200
1248
|
# at the moment of Redis'es SCAN;
|
|
1201
1249
|
# @return [Set<String>,String<Hash<Symbol,Any>>]
|
|
1202
1250
|
#
|
|
1251
|
+
# NOTE: lock queues are found via the keyspace SCAN or via the lock index (ZSCAN, the enabled
|
|
1252
|
+
# `config['lock_index_enabled']`: `scan_size` is the number of locks in one batch);
|
|
1253
|
+
#
|
|
1203
1254
|
# @api public
|
|
1204
1255
|
# @since 1.0.0
|
|
1256
|
+
# @version 1.20.0
|
|
1205
1257
|
def queues(scan_size: config['key_extraction_batch_size'], with_info: false)
|
|
1206
|
-
RedisQueuedLocks::Acquirer::Queues.queues(
|
|
1258
|
+
RedisQueuedLocks::Acquirer::Queues.queues(
|
|
1259
|
+
redis_client, scan_size:, with_info:, lock_index_enabled: config['lock_index_enabled']
|
|
1260
|
+
)
|
|
1207
1261
|
end
|
|
1208
1262
|
|
|
1209
1263
|
# Extracts lock queues with their info. See #queues(with_info: true) for details.
|
|
@@ -1219,11 +1273,17 @@ class RedisQueuedLocks::Client
|
|
|
1219
1273
|
|
|
1220
1274
|
# @option scan_size [Integer]
|
|
1221
1275
|
# @return [Set<String>]
|
|
1276
|
+
# - the disabled lock index: all the RQL keys (`rql:lock*`) found via the keyspace SCAN;
|
|
1277
|
+
# - the enabled lock index (`config['lock_index_enabled']`): the existing keys of the locks of
|
|
1278
|
+
# the lock index (ZSCAN, `scan_size` is the number of locks in one batch);
|
|
1222
1279
|
#
|
|
1223
1280
|
# @api public
|
|
1224
1281
|
# @since 1.0.0
|
|
1282
|
+
# @version 1.20.0
|
|
1225
1283
|
def keys(scan_size: config['key_extraction_batch_size'])
|
|
1226
|
-
RedisQueuedLocks::Acquirer::Keys.keys(
|
|
1284
|
+
RedisQueuedLocks::Acquirer::Keys.keys(
|
|
1285
|
+
redis_client, scan_size:, lock_index_enabled: config['lock_index_enabled']
|
|
1286
|
+
)
|
|
1227
1287
|
end
|
|
1228
1288
|
|
|
1229
1289
|
# @option dead_ttl [Integer]
|
|
@@ -1231,7 +1291,8 @@ class RedisQueuedLocks::Client
|
|
|
1231
1291
|
# considered as dead;
|
|
1232
1292
|
# - `config['dead_request_ttl']` is used by default;
|
|
1233
1293
|
# @option scan_size [Integer]
|
|
1234
|
-
# - the batch of scanned keys for Redis'es SCAN command
|
|
1294
|
+
# - the batch of scanned keys for Redis'es SCAN command (or the number of locks in one batch of
|
|
1295
|
+
# the lock index ZSCAN pass: the enabled `config['lock_index_enabled']`);
|
|
1235
1296
|
# - `config['lock_release_batch_size']` is used by default;
|
|
1236
1297
|
# @option logger [::Logger,#debug]
|
|
1237
1298
|
# @option instrumenter [#notify]
|
|
@@ -1249,7 +1310,7 @@ class RedisQueuedLocks::Client
|
|
|
1249
1310
|
#
|
|
1250
1311
|
# @api public
|
|
1251
1312
|
# @since 1.0.0
|
|
1252
|
-
# @version 1.
|
|
1313
|
+
# @version 1.20.0
|
|
1253
1314
|
def clear_dead_requests(
|
|
1254
1315
|
dead_ttl: config['dead_request_ttl'],
|
|
1255
1316
|
scan_size: config['lock_release_batch_size'],
|
|
@@ -1269,6 +1330,7 @@ class RedisQueuedLocks::Client
|
|
|
1269
1330
|
redis_client,
|
|
1270
1331
|
scan_size,
|
|
1271
1332
|
dead_ttl,
|
|
1333
|
+
config['lock_index_enabled'],
|
|
1272
1334
|
logger,
|
|
1273
1335
|
instrumenter,
|
|
1274
1336
|
instrument,
|
|
@@ -1282,5 +1344,43 @@ class RedisQueuedLocks::Client
|
|
|
1282
1344
|
instr_sample_this
|
|
1283
1345
|
)
|
|
1284
1346
|
end
|
|
1347
|
+
|
|
1348
|
+
# Adds the lock keys without the lock index entry (keys of older RQL versions, keys created while
|
|
1349
|
+
# the lock index was disabled) to the lock index (`rql:index:locks`). Invoke it once after the lock
|
|
1350
|
+
# index is enabled (`config['lock_index_enabled']`) on all your processes: lock-iterating
|
|
1351
|
+
# operations (the swarm, `#locks`, `#queues`, `#keys`, `#clear_locks`, ...) find locks via
|
|
1352
|
+
# the lock index only.
|
|
1353
|
+
# - works regardless of `config['lock_index_enabled']` (the lock index is not used and not
|
|
1354
|
+
# maintained while it is disabled);
|
|
1355
|
+
# - one SCAN pass over the RQL keys;
|
|
1356
|
+
# - lock queues without TTL (older RQL versions) get `queue_ttl` as their TTL;
|
|
1357
|
+
# - idempotent: the lock index scores are never decreased, already indexed keys stay as is;
|
|
1358
|
+
#
|
|
1359
|
+
# @option scan_size [Integer]
|
|
1360
|
+
# - the batch of scanned keys for Redis'es SCAN command;
|
|
1361
|
+
# - `config['lock_release_batch_size']` is used by default;
|
|
1362
|
+
# @option queue_ttl [Integer]
|
|
1363
|
+
# - TTL of the lock queues without TTL (in seconds);
|
|
1364
|
+
# - `config['default_queue_ttl']` is used by default;
|
|
1365
|
+
# @option lock_index_grace_period [Integer]
|
|
1366
|
+
# - the lock index score margin over the key lifetime (in milliseconds);
|
|
1367
|
+
# - `config['lock_index_grace_period']` is used by default;
|
|
1368
|
+
# @return [Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
|
|
1369
|
+
# Format: { ok: true, result: { idx_key_cnt: Integer } } (the number of indexed keys)
|
|
1370
|
+
#
|
|
1371
|
+
# @api public
|
|
1372
|
+
# @since 1.20.0
|
|
1373
|
+
def reindex_locks(
|
|
1374
|
+
scan_size: config['lock_release_batch_size'],
|
|
1375
|
+
queue_ttl: config['default_queue_ttl'],
|
|
1376
|
+
lock_index_grace_period: config['lock_index_grace_period']
|
|
1377
|
+
)
|
|
1378
|
+
RedisQueuedLocks::Acquirer::ReindexLocks.reindex_locks(
|
|
1379
|
+
redis_client,
|
|
1380
|
+
scan_size,
|
|
1381
|
+
queue_ttl,
|
|
1382
|
+
lock_index_grace_period
|
|
1383
|
+
)
|
|
1384
|
+
end
|
|
1285
1385
|
end
|
|
1286
1386
|
# rubocop:enable Metrics/ClassLength
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @api private
|
|
4
4
|
# @since 1.13.0
|
|
5
|
-
# @version 1.
|
|
5
|
+
# @version 1.20.0
|
|
6
6
|
# rubocop:disable Metrics/ClassLength
|
|
7
7
|
class RedisQueuedLocks::Config
|
|
8
8
|
require_relative 'config/dsl'
|
|
@@ -27,6 +27,8 @@ class RedisQueuedLocks::Config
|
|
|
27
27
|
setting('logger', RedisQueuedLocks::Logging::VoidLogger)
|
|
28
28
|
setting('log_lock_try', false)
|
|
29
29
|
setting('dead_request_ttl', 1 * 24 * 60 * 60 * 1000) # NOTE: 1 day in milliseconds)
|
|
30
|
+
setting('lock_index_enabled', false)
|
|
31
|
+
setting('lock_index_grace_period', 60_000) # NOTE: in milliseconds)
|
|
30
32
|
setting('is_timed_by_default', false)
|
|
31
33
|
setting('default_conflict_strategy', :wait_for_lock)
|
|
32
34
|
setting('default_access_strategy', :queued)
|
|
@@ -90,6 +92,8 @@ class RedisQueuedLocks::Config
|
|
|
90
92
|
validate('logger') { |val| RedisQueuedLocks::Logging.valid_interface?(val) }
|
|
91
93
|
validate('log_lock_try') { |val| val == true || val == false }
|
|
92
94
|
validate('dead_request_ttl') { |val| val.is_a?(Integer) && val > 0 }
|
|
95
|
+
validate('lock_index_enabled') { |val| val == true || val == false }
|
|
96
|
+
validate('lock_index_grace_period') { |val| val.is_a?(Integer) && val >= 0 }
|
|
93
97
|
validate('is_timed_by_default') { |val| val == true || val == false }
|
|
94
98
|
validate('log_sampler') { |val| RedisQueuedLocks::Logging.valid_sampler?(val) }
|
|
95
99
|
validate('log_sampling_enabled') { |val| val == true || val == false }
|
|
@@ -47,18 +47,49 @@ module RedisQueuedLocks::Resource
|
|
|
47
47
|
# @since 1.18.0
|
|
48
48
|
READ_LOCK_PATTERN = 'rql:lock_reader:*'
|
|
49
49
|
|
|
50
|
+
# Write and read lock queues in one SCAN pass (the keyspace lookup: the disabled lock index).
|
|
51
|
+
# NOTE:
|
|
52
|
+
# - the pattern matches other keys too when the lock name contains `queue:` (for example,
|
|
53
|
+
# `rql:lock_readers:my_queue:a`), so callers check the key family (`lock_key_family`);
|
|
54
|
+
# @return [String]
|
|
55
|
+
#
|
|
56
|
+
# @api private
|
|
57
|
+
# @since 1.20.0
|
|
58
|
+
ALL_LOCK_QUEUES_PATTERN = 'rql:lock_*queue:*'
|
|
59
|
+
|
|
50
60
|
# @return [String]
|
|
51
61
|
#
|
|
52
62
|
# @api private
|
|
53
63
|
# @since 1.9.0
|
|
54
64
|
SWARM_KEY = 'rql:swarm:hsts'
|
|
55
65
|
|
|
66
|
+
# Lock index: a sorted set of lock names (members) where the score is the upper bound of the
|
|
67
|
+
# lifetime of all keys of the lock (client time, epoch milliseconds + the index grace period).
|
|
68
|
+
# It replaces the full keyspace SCAN for the operations that iterate over locks (lock names are
|
|
69
|
+
# taken from the index, lock keys are derived from the lock names).
|
|
70
|
+
# NOTE:
|
|
71
|
+
# - it is used and maintained only with the enabled lock index (`config['lock_index_enabled']`,
|
|
72
|
+
# disabled by default: lock-iterating operations scan the keyspace (`KEY_PATTERN`) then);
|
|
73
|
+
# - the key is not matched by `KEY_PATTERN` (`rql:index:` vs `rql:lock`) and can not collide
|
|
74
|
+
# with lock keys (`rql:lock:<name>`, `rql:lock_<kind>:<name>`);
|
|
75
|
+
# - the index is a superset of the existing locks: a lock name is added (or its score is
|
|
76
|
+
# increased) by every command that creates or extends a lock key (in the same pipeline or
|
|
77
|
+
# transaction, after the key command) and it is removed only when its score is reached
|
|
78
|
+
# (`ZREMRANGEBYSCORE`: all the keys of the lock are expired by that time) or in the same
|
|
79
|
+
# transaction with all the keys of the lock (`#unlock`, `#clear_locks`);
|
|
80
|
+
# @return [String]
|
|
81
|
+
#
|
|
82
|
+
# @api private
|
|
83
|
+
# @since 1.20.0
|
|
84
|
+
LOCK_INDEX_KEY = 'rql:index:locks'
|
|
85
|
+
|
|
56
86
|
# @return [Integer] Redis time error (in milliseconds).
|
|
57
87
|
#
|
|
58
88
|
# @api private
|
|
59
89
|
# @since 1.3.0
|
|
60
90
|
REDIS_TIMESHIFT_ERROR = 2
|
|
61
91
|
|
|
92
|
+
# rubocop:disable Metrics/ClassLength
|
|
62
93
|
class << self
|
|
63
94
|
# Returns 16-byte unique identifier. It is used for uniquely
|
|
64
95
|
# identify current process between different nodes/pods of your application
|
|
@@ -143,8 +174,9 @@ module RedisQueuedLocks::Resource
|
|
|
143
174
|
end
|
|
144
175
|
|
|
145
176
|
# Read lock data of the concrete acquirer: a hash with the same structure as the write lock
|
|
146
|
-
# (
|
|
147
|
-
# the readers registry is the source of
|
|
177
|
+
# (timestamp, ttl, meta, reentrant lock data; acquirer and host are derived from the acquirer
|
|
178
|
+
# id and are not stored). It is a data carrier only: the readers registry is the source of
|
|
179
|
+
# truth for the read lock existence.
|
|
148
180
|
#
|
|
149
181
|
# @param lock_name [String]
|
|
150
182
|
# @param acquirer_id [String]
|
|
@@ -179,6 +211,70 @@ module RedisQueuedLocks::Resource
|
|
|
179
211
|
end
|
|
180
212
|
end
|
|
181
213
|
|
|
214
|
+
# Family of the given RQL key: one SCAN pass over `KEY_PATTERN` (or `ALL_LOCK_QUEUES_PATTERN`)
|
|
215
|
+
# returns keys of several families, so callers keep the keys of the families they need.
|
|
216
|
+
#
|
|
217
|
+
# @param key [String]
|
|
218
|
+
# @return [Symbol,NilClass]
|
|
219
|
+
# - `:lock` - write lock (`rql:lock:*`);
|
|
220
|
+
# - `:lock_queue` - write lock requests (`rql:lock_queue:*`);
|
|
221
|
+
# - `:lock_readers` - readers registry (`rql:lock_readers:*`);
|
|
222
|
+
# - `:read_lock_queue` - read lock requests (`rql:lock_read_queue:*`);
|
|
223
|
+
# - `:read_lock` - read lock data (`rql:lock_reader:*`);
|
|
224
|
+
# - `nil` - any other key (for example, `rql:lockX` matched by `KEY_PATTERN`);
|
|
225
|
+
#
|
|
226
|
+
# @api private
|
|
227
|
+
# @since 1.20.0
|
|
228
|
+
def lock_key_family(key)
|
|
229
|
+
# NOTE:
|
|
230
|
+
# - `start_with?` instead of regexp `when`-matchers: no MatchData allocations;
|
|
231
|
+
# - prefixes are unambiguous: `rql:lock_reader:` is not a prefix of `rql:lock_readers:`;
|
|
232
|
+
case
|
|
233
|
+
when key.start_with?('rql:lock:') then :lock
|
|
234
|
+
when key.start_with?('rql:lock_queue:') then :lock_queue
|
|
235
|
+
when key.start_with?('rql:lock_readers:') then :lock_readers
|
|
236
|
+
when key.start_with?('rql:lock_read_queue:') then :read_lock_queue
|
|
237
|
+
when key.start_with?('rql:lock_reader:') then :read_lock
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Lock name of the given RQL key (the member of the lock index).
|
|
242
|
+
#
|
|
243
|
+
# @param key [String]
|
|
244
|
+
# @param key_family [Symbol,NilClass] Family of the key (see `lock_key_family`).
|
|
245
|
+
# @return [String,NilClass]
|
|
246
|
+
# - the lock name for the write lock, the readers registry and the lock queues (both modes);
|
|
247
|
+
# - `nil` for the read lock data (`:read_lock`: the lock name can not be separated from
|
|
248
|
+
# the acquirer id unambiguously) and for any other key;
|
|
249
|
+
#
|
|
250
|
+
# @api private
|
|
251
|
+
# @since 1.20.0
|
|
252
|
+
def lock_name_from_key(key, key_family)
|
|
253
|
+
case key_family
|
|
254
|
+
when :lock then key.delete_prefix('rql:lock:')
|
|
255
|
+
when :lock_queue then key.delete_prefix('rql:lock_queue:')
|
|
256
|
+
when :lock_readers then key.delete_prefix('rql:lock_readers:')
|
|
257
|
+
when :read_lock_queue then key.delete_prefix('rql:lock_read_queue:')
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Client time for the lock index scores (see `LOCK_INDEX_KEY`).
|
|
262
|
+
#
|
|
263
|
+
# @return [Integer] Epoch, in milliseconds.
|
|
264
|
+
#
|
|
265
|
+
# @api private
|
|
266
|
+
# @since 1.20.0
|
|
267
|
+
def lock_index_time
|
|
268
|
+
# NOTE:
|
|
269
|
+
# - an Integer of milliseconds without Time/Float objects;
|
|
270
|
+
# - CLOCK_REALTIME (epoch), not CLOCK_MONOTONIC: the lock index scores are absolute times
|
|
271
|
+
# stored in the shared Redis and compared by other hosts (pruning, expired names),
|
|
272
|
+
# while monotonic clocks have a per-host origin (boot time) and are not comparable
|
|
273
|
+
# between hosts; the wall clock skew between hosts is covered by
|
|
274
|
+
# `lock_index_grace_period`;
|
|
275
|
+
::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
|
|
276
|
+
end
|
|
277
|
+
|
|
182
278
|
# @param lock_readers_key [String]
|
|
183
279
|
# @return [String]
|
|
184
280
|
#
|
|
@@ -347,5 +443,6 @@ module RedisQueuedLocks::Resource
|
|
|
347
443
|
# steep:ignore:end
|
|
348
444
|
end
|
|
349
445
|
end
|
|
446
|
+
# rubocop:enable Metrics/ClassLength
|
|
350
447
|
end
|
|
351
448
|
# rubocop:enable Metrics/ModuleLength
|