redis_queued_locks 1.12.1 → 1.14.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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -1
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +45 -5
  5. data/LICENSE.txt +1 -1
  6. data/README.md +574 -296
  7. data/Rakefile +12 -4
  8. data/Steepfile +15 -0
  9. data/github_ci/ruby3.3.gemfile +17 -0
  10. data/github_ci/ruby3.3.gemfile.lock +217 -0
  11. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/delay_execution.rb +4 -4
  12. data/lib/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +40 -0
  13. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/dequeue_from_lock_queue.rb +17 -8
  14. data/lib/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rb +166 -0
  15. data/lib/redis_queued_locks/acquirer/acquire_lock/log_visitor.rb +218 -0
  16. data/lib/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rb +543 -0
  17. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/try_to_lock.rb +126 -92
  18. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/with_acq_timeout.rb +14 -9
  19. data/lib/redis_queued_locks/acquirer/acquire_lock/yield_expire/log_visitor.rb +76 -0
  20. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock/yield_expire.rb +42 -19
  21. data/lib/redis_queued_locks/{acquier → acquirer}/acquire_lock.rb +74 -47
  22. data/lib/redis_queued_locks/{acquier → acquirer}/clear_dead_requests.rb +5 -3
  23. data/lib/redis_queued_locks/{acquier → acquirer}/extend_lock_ttl.rb +4 -3
  24. data/lib/redis_queued_locks/{acquier → acquirer}/is_locked.rb +1 -1
  25. data/lib/redis_queued_locks/{acquier → acquirer}/is_queued.rb +1 -1
  26. data/lib/redis_queued_locks/{acquier → acquirer}/keys.rb +5 -5
  27. data/lib/redis_queued_locks/{acquier → acquirer}/lock_info.rb +9 -5
  28. data/lib/redis_queued_locks/{acquier → acquirer}/locks.rb +16 -3
  29. data/lib/redis_queued_locks/{acquier → acquirer}/queue_info.rb +8 -6
  30. data/lib/redis_queued_locks/{acquier → acquirer}/queues.rb +9 -2
  31. data/lib/redis_queued_locks/{acquier → acquirer}/release_all_locks.rb +26 -21
  32. data/lib/redis_queued_locks/{acquier → acquirer}/release_lock.rb +28 -22
  33. data/lib/redis_queued_locks/acquirer/release_locks_of.rb +211 -0
  34. data/lib/redis_queued_locks/acquirer.rb +19 -0
  35. data/lib/redis_queued_locks/client.rb +317 -254
  36. data/lib/redis_queued_locks/config/dsl.rb +94 -0
  37. data/lib/redis_queued_locks/config.rb +236 -0
  38. data/lib/redis_queued_locks/data.rb +2 -0
  39. data/lib/redis_queued_locks/errors.rb +27 -11
  40. data/lib/redis_queued_locks/instrument.rb +11 -4
  41. data/lib/redis_queued_locks/logging/void_logger.rb +38 -1
  42. data/lib/redis_queued_locks/logging.rb +20 -5
  43. data/lib/redis_queued_locks/resource.rb +49 -11
  44. data/lib/redis_queued_locks/swarm/acquirers.rb +17 -16
  45. data/lib/redis_queued_locks/swarm/flush_zombies.rb +26 -25
  46. data/lib/redis_queued_locks/swarm/probe_hosts.rb +20 -19
  47. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +3 -3
  48. data/lib/redis_queued_locks/swarm/supervisor.rb +19 -6
  49. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +20 -18
  50. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +35 -27
  51. data/lib/redis_queued_locks/swarm/zombie_info.rb +9 -9
  52. data/lib/redis_queued_locks/swarm.rb +20 -41
  53. data/lib/redis_queued_locks/utilities.rb +11 -2
  54. data/lib/redis_queued_locks/version.rb +2 -2
  55. data/lib/redis_queued_locks.rb +2 -3
  56. data/rbs_collection.lock.yaml +28 -0
  57. data/rbs_collection.yaml +17 -0
  58. data/redis_queued_locks.gemspec +22 -23
  59. data/sig/manifest.yml +6 -0
  60. data/sig/redis_queued_locks/acquier.rbs +4 -0
  61. data/sig/redis_queued_locks/acquirer/acquire_lock/delay_execution.rbs +9 -0
  62. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue/log_visitor.rbs +21 -0
  63. data/sig/redis_queued_locks/acquirer/acquire_lock/dequeue_from_lock_queue.rbs +26 -0
  64. data/sig/redis_queued_locks/acquirer/acquire_lock/instr_visitor.rbs +71 -0
  65. data/sig/redis_queued_locks/acquirer/acquire_lock/log_visitor.rbs +72 -0
  66. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock/log_visitor.rbs +179 -0
  67. data/sig/redis_queued_locks/acquirer/acquire_lock/try_to_lock.rbs +48 -0
  68. data/sig/redis_queued_locks/acquirer/acquire_lock/with_acq_timeout.rbs +19 -0
  69. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_expire.rbs +41 -0
  70. data/sig/redis_queued_locks/acquirer/acquire_lock/yield_with_expire/log_visitor.rbs +32 -0
  71. data/sig/redis_queued_locks/acquirer/acquire_lock.rbs +52 -0
  72. data/sig/redis_queued_locks/acquirer/clear_dead_requests.rbs +28 -0
  73. data/sig/redis_queued_locks/acquirer/extend_lock_ttl.rbs +28 -0
  74. data/sig/redis_queued_locks/acquirer/is_locked.rbs +9 -0
  75. data/sig/redis_queued_locks/acquirer/is_queued.rbs +9 -0
  76. data/sig/redis_queued_locks/acquirer/keys.rbs +10 -0
  77. data/sig/redis_queued_locks/acquirer/lock_info.rbs +10 -0
  78. data/sig/redis_queued_locks/acquirer/locks.rbs +16 -0
  79. data/sig/redis_queued_locks/acquirer/queue_info.rbs +13 -0
  80. data/sig/redis_queued_locks/acquirer/queues.rbs +16 -0
  81. data/sig/redis_queued_locks/acquirer/release_all_locks.rbs +30 -0
  82. data/sig/redis_queued_locks/acquirer/release_lock.rbs +38 -0
  83. data/sig/redis_queued_locks/acquirer/release_locks_of.rbs +46 -0
  84. data/sig/redis_queued_locks/client.rbs +235 -0
  85. data/sig/redis_queued_locks/config/dsl.rbs +26 -0
  86. data/sig/redis_queued_locks/config.rbs +23 -0
  87. data/sig/redis_queued_locks/data.rbs +4 -0
  88. data/sig/redis_queued_locks/debugger/interface.rbs +9 -0
  89. data/sig/redis_queued_locks/debugger.rbs +13 -0
  90. data/sig/redis_queued_locks/errors.rbs +43 -0
  91. data/sig/redis_queued_locks/instrument/active_support.rbs +7 -0
  92. data/sig/redis_queued_locks/instrument/sampler.rbs +9 -0
  93. data/sig/redis_queued_locks/instrument/void_notifier.rbs +7 -0
  94. data/sig/redis_queued_locks/instrument.rbs +15 -0
  95. data/sig/redis_queued_locks/logging/sampler.rbs +9 -0
  96. data/sig/redis_queued_locks/logging/void_logger.rbs +15 -0
  97. data/sig/redis_queued_locks/logging.rbs +15 -0
  98. data/sig/redis_queued_locks/resource.rbs +42 -0
  99. data/sig/redis_queued_locks/swarm/acquirers.rbs +10 -0
  100. data/sig/redis_queued_locks/swarm/flush_zombies.rbs +13 -0
  101. data/sig/redis_queued_locks/swarm/probe_hosts.rbs +13 -0
  102. data/sig/redis_queued_locks/swarm/redis_client_builder.rbs +19 -0
  103. data/sig/redis_queued_locks/swarm/supervisor.rbs +26 -0
  104. data/sig/redis_queued_locks/swarm/swarm_element/isolated.rbs +52 -0
  105. data/sig/redis_queued_locks/swarm/swarm_element/threaded.rbs +61 -0
  106. data/sig/redis_queued_locks/swarm/swarm_element.rbs +8 -0
  107. data/sig/redis_queued_locks/swarm/zombie_info.rbs +24 -0
  108. data/sig/redis_queued_locks/swarm.rbs +41 -0
  109. data/sig/redis_queued_locks/utilities/lock.rbs +10 -0
  110. data/sig/redis_queued_locks/utilities.rbs +12 -0
  111. data/sig/redis_queued_locks/version.rbs +3 -0
  112. data/sig/redis_queued_locks.rbs +14 -0
  113. data/sig/vendor/active_support.rbs +9 -0
  114. data/sig/vendor/redis_client.rbs +39 -0
  115. data/sig/vendor/semantic_logger.rbs +4 -0
  116. metadata +98 -54
  117. data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +0 -40
  118. data/lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb +0 -166
  119. data/lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb +0 -216
  120. data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb +0 -541
  121. data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb +0 -76
  122. data/lib/redis_queued_locks/acquier.rb +0 -18
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::Locks
5
+ module RedisQueuedLocks::Acquirer::Locks
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @option scan_size [Integer]
@@ -50,23 +50,36 @@ module RedisQueuedLocks::Acquier::Locks
50
50
  def extract_locks_info(redis_client, lock_keys)
51
51
  # TODO: refactor with RedisQueuedLocks::Acquier::LockInfo
52
52
  Set.new.tap do |seeded_locks|
53
+ # rubocop:disable Layout/LineLength
54
+ # @type var seeded_locks: Set[{ lock: String, status: :released|:alive, info: Hash[String,untyped] }]
55
+ # rubocop:enable Layout/LineLength
56
+
53
57
  # Step X: iterate each lock and extract their info
54
58
  lock_keys.each do |lock_key|
55
59
  # Step 1: extract lock info from redis
60
+
61
+ # @type var lock_info: Hash[String,String|Float|Integer]
56
62
  lock_info = redis_client.pipelined do |pipeline|
57
63
  pipeline.call('HGETALL', lock_key)
58
64
  pipeline.call('PTTL', lock_key)
59
65
  end.yield_self do |result| # Step 2: format the result
60
66
  # Step 2.X: lock is released
61
67
  if result == nil
62
- {}
68
+ {} #: Hash[String,String|Float|Integer]
63
69
  else
70
+ # NOTE: the result of MULTI-command is an array of results of each internal command
71
+ # - result[0] (HGETALL) (Hash<String,String>)
72
+ # => (will be mutated further with different value types)
73
+ # - result[1] (PTTL) (Integer)
74
+ # => (without any mutation, integer is atomic)
75
+
76
+ # @type var result: [Hash[String,String|Float|Integer],Integer]
64
77
  hget_cmd_res = result[0] # NOTE: HGETALL result (hash)
65
78
  pttl_cmd_res = result[1] # NOTE: PTTL result (integer)
66
79
 
67
80
  # Step 2.Y: lock is released
68
81
  if hget_cmd_res == {} || pttl_cmd_res == -2 # NOTE: key does not exist
69
- {}
82
+ {} #: Hash[String,String|Float|Integer]
70
83
  else
71
84
  # Step 2.Z: lock is alive => format received info + add additional rem_ttl info
72
85
  hget_cmd_res.tap do |lock_data|
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::QueueInfo
5
+ module RedisQueuedLocks::Acquirer::QueueInfo
6
6
  class << self
7
7
  # Returns an information about the required lock queue by the lock name. The result
8
8
  # represnts the ordered lock request queue that is ordered by score (Redis sets) and shows
@@ -12,14 +12,15 @@ module RedisQueuedLocks::Acquier::QueueInfo
12
12
  #
13
13
  # @param redis_client [RedisClient]
14
14
  # @param lock_name [String]
15
- # @return [Hash<String|Array<Hash<String,String|Numeric>>,NilClass]
15
+ # @return [Hash<String,Array<Hash<String,String|Numeric>>,NilClass]
16
16
  # - `nil` is returned when lock queue does not exist;
17
17
  # - result format: {
18
18
  # "lock_queue" => "rql:lock_queue:your_lock_name", # lock queue key in redis,
19
- # queue: [
20
- # { "acq_id" => "rql:acq:123/456/789/987/identity", "score" => 123 },
21
- # { "acq_id" => "rql:acq:123/686/789/987/identity", "score" => 456 },
22
- # ] # ordered set (by score) with information about an acquier and their position in queue
19
+ # "queue" => [
20
+ # { "acq_id" => "rql:acq:123/456/789/987/identity", "score" => 123.456 },
21
+ # { "acq_id" => "rql:acq:123/686/789/987/identity", "score" => 456.789 },
22
+ # ...
23
+ # ] # ordered set (by score) with information about an acquirer and their position in queue
23
24
  # }
24
25
  #
25
26
  # @api private
@@ -32,6 +33,7 @@ module RedisQueuedLocks::Acquier::QueueInfo
32
33
  pipeline.call('ZRANGE', lock_key_queue, '0', '-1', 'WITHSCORES')
33
34
  end
34
35
 
36
+ # @type var result: [Integer,Array[[String,Integer|Float]]]
35
37
  exists_cmd_res = result[0]
36
38
  zrange_cmd_res = result[1]
37
39
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::Queues
5
+ module RedisQueuedLocks::Acquirer::Queues
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
8
  # @option scan_size [Integer]
@@ -28,12 +28,14 @@ module RedisQueuedLocks::Acquier::Queues
28
28
  # @since 1.0.0
29
29
  def scan_queues(redis_client, scan_size)
30
30
  Set.new.tap do |lock_queues|
31
+ # @type var lock_queues: Set[String]
31
32
  redis_client.scan(
32
33
  'MATCH',
33
34
  RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
34
35
  count: scan_size
35
36
  ) do |lock_queue|
36
37
  # TODO: reduce unnecessary iterations
38
+ # @type var lock_queue: String
37
39
  lock_queues.add(lock_queue)
38
40
  end
39
41
  end
@@ -49,12 +51,15 @@ module RedisQueuedLocks::Acquier::Queues
49
51
  # TODO: refactor with RedisQueuedLocks::Acquier::QueueInfo
50
52
  Set.new.tap do |seeded_queues|
51
53
  # Step X: iterate over each lock queue and extract their info
54
+ # @type var seeded_queues: Set[Hash[Symbol,untyped]]
52
55
  lock_queues.each do |lock_queue|
53
56
  # Step 1: extract lock queue info from reids
54
57
  queue_info = redis_client.pipelined do |pipeline|
55
58
  pipeline.call('EXISTS', lock_queue)
56
59
  pipeline.call('ZRANGE', lock_queue, '0', '-1', 'WITHSCORES')
57
60
  end.yield_self do |result| # Step 2: format the result
61
+ # @type var result: [Integer, Array[[String,Float]]]
62
+
58
63
  exists_cmd_res = result[0]
59
64
  zrange_cmd_res = result[1]
60
65
 
@@ -62,10 +67,12 @@ module RedisQueuedLocks::Acquier::Queues
62
67
  zrange_cmd_res.map { |val| { 'acq_id' => val[0], 'score' => val[1] } }
63
68
  else
64
69
  # Step 2.Y: lock queue did not exist during the pipeline invocation
65
- []
70
+ [] #: Array[Hash[String,Float]]
66
71
  end
67
72
  end
68
73
 
74
+ # @type var queue_info: Array[Hash[String,Float]]
75
+
69
76
  # Step 3: push the lock queue info to the result store
70
77
  seeded_queues << {
71
78
  queue: lock_queue,
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::ReleaseAllLocks
5
+ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
6
6
  # @since 1.0.0
7
7
  extend RedisQueuedLocks::Utilities
8
8
 
@@ -16,7 +16,7 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
16
16
  # @param batch_size [Integer]
17
17
  # The number of lock keys that should be released in a time.
18
18
  # @param logger [::Logger,#debug]
19
- # - Logger object used from `configuration` layer (see config[:logger]);
19
+ # - Logger object used from `configuration` layer (see config['logger']);
20
20
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
21
21
  # @param isntrumenter [#notify]
22
22
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
@@ -26,15 +26,15 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
26
26
  # @param log_sampling_enabled [Boolean]
27
27
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
28
28
  # - disabled by default;
29
- # - works in tandem with <config.log_sampling_percent and <log.sampler>;
29
+ # - works in tandem with <config['log_sampling_percent']> and <config['log_sampler']>;
30
30
  # @param log_sampling_percent [Integer]
31
31
  # - the percent of cases that should be logged;
32
- # - take an effect when <config.log_sampling_enalbed> is true;
33
- # - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
32
+ # - take an effect when <config['log_sampling_enalbed']> is true;
33
+ # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
34
34
  # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
35
35
  # - percent-based log sampler that decides should be RQL case logged or not;
36
- # - works in tandem with <config.log_sampling_enabled> and
37
- # <config.log_sampling_percent> configs;
36
+ # - works in tandem with <config['log_sampling_enabled']> and
37
+ # <config['log_sampling_percent']> configs;
38
38
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
39
39
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
40
40
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -47,15 +47,16 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
47
47
  # - enables <instrumentaion sampling>: only the configured percent
48
48
  # of RQL cases will be instrumented;
49
49
  # - disabled by default;
50
- # - works in tandem with <config.instr_sampling_percent and <log.instr_sampler>;
50
+ # - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
51
51
  # @param instr_sampling_percent [Integer]
52
52
  # - the percent of cases that should be instrumented;
53
- # - take an effect when <config.instr_sampling_enalbed> is true;
54
- # - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampler> configs;
53
+ # - take an effect when <config['instr_sampling_enalbed']> is true;
54
+ # - works in tandem with <config['instr_sampling_enabled']>
55
+ # and <config['instr_sampler']> configs;
55
56
  # @param instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
56
57
  # - percent-based log sampler that decides should be RQL case instrumented or not;
57
- # - works in tandem with <config.instr_sampling_enabled> and
58
- # <config.instr_sampling_percent> configs;
58
+ # - works in tandem with <config['instr_sampling_enabled']> and
59
+ # <config['instr_sampling_percent']> configs;
59
60
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
60
61
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
61
62
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -65,12 +66,12 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
65
66
  # - marks the method that everything should be instrumneted
66
67
  # despite the enabled instrumentation sampling;
67
68
  # - makes sense when instrumentation sampling is enabled;
68
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
69
+ # @return [Hash<Symbol,Any>]
69
70
  # Format: { ok: true, result: Hash<Symbol,Numeric> }
70
71
  #
71
72
  # @api private
72
73
  # @since 1.0.0
73
- # @version 1.6.0
74
+ # @version 1.14.0
74
75
  def release_all_locks(
75
76
  redis,
76
77
  batch_size,
@@ -86,10 +87,14 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
86
87
  instr_sampler,
87
88
  instr_sample_this
88
89
  )
89
- rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
90
- fully_release_all_locks(redis, batch_size) => { ok:, result: }
90
+ rel_start_time = clock_gettime
91
+ fully_release_all_locks(redis, batch_size) => { ok:, result: } # steep:ignore
92
+
93
+ # @type var ok: bool
94
+ # @type var result: Hash[Symbol,Integer]
95
+
91
96
  time_at = Time.now.to_f
92
- rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
97
+ rel_end_time = clock_gettime
93
98
  rel_time = ((rel_end_time - rel_start_time) / 1_000.0).ceil(2)
94
99
 
95
100
  instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
@@ -107,10 +112,10 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
107
112
  })
108
113
  end if instr_sampled
109
114
 
110
- RedisQueuedLocks::Data[
115
+ {
111
116
  ok: true,
112
117
  result: { rel_key_cnt: result[:rel_key_cnt], rel_time: rel_time }
113
- ]
118
+ }
114
119
  end
115
120
 
116
121
  private
@@ -119,7 +124,7 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
119
124
  #
120
125
  # @param redis [RedisClient]
121
126
  # @param batch_size [Integer]
122
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
127
+ # @return [Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
123
128
  # - Exmaple: { ok: true, result: { rel_key_cnt: 12345 } }
124
129
  #
125
130
  # @api private
@@ -149,7 +154,7 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
149
154
  end
150
155
  end
151
156
 
152
- RedisQueuedLocks::Data[ok: true, result: { rel_key_cnt: result.sum }]
157
+ { ok: true, result: { rel_key_cnt: result.sum } }
153
158
  end
154
159
  end
155
160
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- module RedisQueuedLocks::Acquier::ReleaseLock
5
+ module RedisQueuedLocks::Acquirer::ReleaseLock
6
6
  # @since 1.0.0
7
7
  extend RedisQueuedLocks::Utilities
8
8
 
@@ -21,20 +21,20 @@ module RedisQueuedLocks::Acquier::ReleaseLock
21
21
  # @param isntrumenter [#notify]
22
22
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
23
23
  # @param logger [::Logger,#debug]
24
- # - Logger object used from `configuration` layer (see config[:logger]);
24
+ # - Logger object used from `configuration` layer (see config['logger']);
25
25
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
26
26
  # @param log_sampling_enabled [Boolean]
27
27
  # - enables <log sampling>: only the configured percent of RQL cases will be logged;
28
28
  # - disabled by default;
29
- # - works in tandem with <config.log_sampling_percent and <log.sampler>;
29
+ # - works in tandem with <config['log_sampling_percent']> and <config['log_sampler']>;
30
30
  # @param log_sampling_percent [Integer]
31
31
  # - the percent of cases that should be logged;
32
- # - take an effect when <config.log_sampling_enalbed> is true;
33
- # - works in tandem with <config.log_sampling_enabled> and <config.log_sampler> configs;
32
+ # - take an effect when <config['log_sampling_enalbed']> is true;
33
+ # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
34
34
  # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
35
35
  # - percent-based log sampler that decides should be RQL case logged or not;
36
- # - works in tandem with <config.log_sampling_enabled> and
37
- # <config.log_sampling_percent> configs;
36
+ # - works in tandem with <config['log_sampling_enabled']> and
37
+ # <config['log_sampling_percent']> configs;
38
38
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
39
39
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
40
40
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -47,15 +47,16 @@ module RedisQueuedLocks::Acquier::ReleaseLock
47
47
  # - enables <instrumentaion sampling>: only the configured percent
48
48
  # of RQL cases will be instrumented;
49
49
  # - disabled by default;
50
- # - works in tandem with <config.instr_sampling_percent and <log.instr_sampler>;
50
+ # - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
51
51
  # @param instr_sampling_percent [Integer]
52
52
  # - the percent of cases that should be instrumented;
53
- # - take an effect when <config.instr_sampling_enalbed> is true;
54
- # - works in tandem with <config.instr_sampling_enabled> and <config.instr_sampler> configs;
53
+ # - take an effect when <config['instr_sampling_enalbed']> is true;
54
+ # - works in tandem with <config['instr_sampling_enabled']>
55
+ # and <config['instr_sampler']> configs;
55
56
  # @param instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
56
57
  # - percent-based log sampler that decides should be RQL case instrumented or not;
57
- # - works in tandem with <config.instr_sampling_enabled> and
58
- # <config.instr_sampling_percent> configs;
58
+ # - works in tandem with <config['instr_sampling_enabled']> and
59
+ # <config['instr_sampling_percent']> configs;
59
60
  # - based on the ultra simple percent-based (weight-based) algorithm that uses
60
61
  # SecureRandom.rand method so the algorithm error is ~(0%..13%);
61
62
  # - you can provide your own log sampler with bettter algorithm that should realize
@@ -65,12 +66,12 @@ module RedisQueuedLocks::Acquier::ReleaseLock
65
66
  # - marks the method that everything should be instrumneted
66
67
  # despite the enabled instrumentation sampling;
67
68
  # - makes sense when instrumentation sampling is enabled;
68
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean<Hash<Symbol,Numeric|String|Symbol>>]
69
+ # @return [Hash<Symbol,Boolean<Hash<Symbol,Numeric|String|Symbol>>]
69
70
  # Format: { ok: true/false, result: Hash<Symbol,Numeric|String|Symbol> }
70
71
  #
71
72
  # @api private
72
73
  # @since 1.0.0
73
- # @version 1.6.0
74
+ # @version 1.14.0
74
75
  # rubocop:disable Metrics/MethodLength
75
76
  def release_lock(
76
77
  redis,
@@ -89,10 +90,14 @@ module RedisQueuedLocks::Acquier::ReleaseLock
89
90
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
90
91
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
91
92
 
92
- rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
93
- fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: }
93
+ rel_start_time = clock_gettime
94
+ fully_release_lock(redis, lock_key, lock_key_queue) => { ok:, result: } # steep:ignore
95
+
96
+ # @type var ok: bool
97
+ # @type var result: Hash[Symbol,Symbol]
98
+
94
99
  time_at = Time.now.to_f
95
- rel_end_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond)
100
+ rel_end_time = clock_gettime
96
101
  rel_time = ((rel_end_time - rel_start_time) / 1_000.0).ceil(2)
97
102
 
98
103
  instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
@@ -111,7 +116,7 @@ module RedisQueuedLocks::Acquier::ReleaseLock
111
116
  })
112
117
  end if instr_sampled
113
118
 
114
- RedisQueuedLocks::Data[
119
+ {
115
120
  ok: true,
116
121
  result: {
117
122
  rel_time: rel_time,
@@ -120,7 +125,7 @@ module RedisQueuedLocks::Acquier::ReleaseLock
120
125
  queue_res: result[:queue],
121
126
  lock_res: result[:lock]
122
127
  }
123
- ]
128
+ }
124
129
  end
125
130
  # rubocop:enable Metrics/MethodLength
126
131
 
@@ -131,7 +136,7 @@ module RedisQueuedLocks::Acquier::ReleaseLock
131
136
  # @param redis [RedisClient]
132
137
  # @param lock_key [String]
133
138
  # @param lock_key_queue [String]
134
- # @return [RedisQueuedLocks::Data,Hash<Symbol,Boolean|Hash<Symbol,Symbol>>]
139
+ # @return [Hash<Symbol,Boolean|Hash<Symbol,Symbol>>]
135
140
  # Format: {
136
141
  # ok: true/false,
137
142
  # result: {
@@ -143,6 +148,7 @@ module RedisQueuedLocks::Acquier::ReleaseLock
143
148
  # @api private
144
149
  # @since 1.0.0
145
150
  def fully_release_lock(redis, lock_key, lock_key_queue)
151
+ # @type var result: [Integer,Integer]
146
152
  result = redis.with do |rconn|
147
153
  rconn.multi do |transact|
148
154
  transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
@@ -150,13 +156,13 @@ module RedisQueuedLocks::Acquier::ReleaseLock
150
156
  end
151
157
  end
152
158
 
153
- RedisQueuedLocks::Data[
159
+ {
154
160
  ok: true,
155
161
  result: {
156
162
  queue: (result[0] != 0) ? :released : :nothing_to_release,
157
163
  lock: (result[1] != 0) ? :released : :nothing_to_release
158
164
  }
159
- ]
165
+ }
160
166
  end
161
167
  end
162
168
  end
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.14.0
5
+ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
6
+ # @since 1.14.0
7
+ extend RedisQueuedLocks::Utilities
8
+
9
+ class << self
10
+ # Release all queues and locks that belong to the given host and its associated acquirer.
11
+ #
12
+ # @param refused_host_id [String]
13
+ # A host whose locks and queues should be released.
14
+ # @param refused_acquirer_id [String]
15
+ # An acquirer (of the passed host) whose locks and queues should be released.
16
+ # @param redis [RedisClient]
17
+ # Redis connection client.
18
+ # @param lock_scan_size [Integer]
19
+ # The number of lock keys that should be released in a time.
20
+ # Affects the RubyVM memmory (cuz each found lock will be temporary stored in memory for
21
+ # subsequent removal from redis in one query at a time).
22
+ # @param queue_scan_size [Integer]
23
+ # The number of lock queues that should be scanned removing an acquirer from them (at a time).
24
+ # @param logger [::Logger,#debug]
25
+ # - Logger object used from `configuration` layer (see config['logger']);
26
+ # - See RedisQueuedLocks::Logging::VoidLogger for example;
27
+ # @param isntrumenter [#notify]
28
+ # See RedisQueuedLocks::Instrument::ActiveSupport for example.
29
+ # @param instrument [NilClass,Any]
30
+ # - Custom instrumentation data wich will be passed to the instrumenter's payload
31
+ # with :instrument key;
32
+ # @param log_sampling_enabled [Boolean]
33
+ # - enables <log sampling>: only the configured percent of RQL cases will be logged;
34
+ # - disabled by default;
35
+ # - works in tandem with <config['log_sampling_percent']> and <config['log_sampler']>;
36
+ # @param log_sampling_percent [Integer]
37
+ # - the percent of cases that should be logged;
38
+ # - take an effect when <config['log_sampling_enalbed']> is true;
39
+ # - works in tandem with <config['log_sampling_enabled']> and <config['log_sampler']> configs;
40
+ # @param log_sampler [#sampling_happened?,Module<RedisQueuedLocks::Logging::Sampler>]
41
+ # - percent-based log sampler that decides should be RQL case logged or not;
42
+ # - works in tandem with <config['log_sampling_enabled']> and
43
+ # <config['log_sampling_percent']> configs;
44
+ # - based on the ultra simple percent-based (weight-based) algorithm that uses
45
+ # SecureRandom.rand method so the algorithm error is ~(0%..13%);
46
+ # - you can provide your own log sampler with bettter algorithm that should realize
47
+ # `sampling_happened?(percent) => boolean` interface
48
+ # (see `RedisQueuedLocks::Logging::Sampler` for example);
49
+ # @param log_sample_this [Boolean]
50
+ # - marks the method that everything should be logged despite the enabled log sampling;
51
+ # - makes sense when log sampling is enabled;
52
+ # @param instr_sampling_enabled [Boolean]
53
+ # - enables <instrumentaion sampling>: only the configured percent
54
+ # of RQL cases will be instrumented;
55
+ # - disabled by default;
56
+ # - works in tandem with <config['instr_sampling_percent']> and <config['instr_sampler']>;
57
+ # @param instr_sampling_percent [Integer]
58
+ # - the percent of cases that should be instrumented;
59
+ # - take an effect when <config['instr_sampling_enalbed']> is true;
60
+ # - works in tandem with <config['instr_sampling_enabled']>
61
+ # and <config['instr_sampler']> configs;
62
+ # @param instr_sampler [#sampling_happened?,Module<RedisQueuedLocks::Instrument::Sampler>]
63
+ # - percent-based log sampler that decides should be RQL case instrumented or not;
64
+ # - works in tandem with <config['instr_sampling_enabled']> and
65
+ # <config['instr_sampling_percent']> configs;
66
+ # - based on the ultra simple percent-based (weight-based) algorithm that uses
67
+ # SecureRandom.rand method so the algorithm error is ~(0%..13%);
68
+ # - you can provide your own log sampler with bettter algorithm that should realize
69
+ # `sampling_happened?(percent) => boolean` interface
70
+ # (see `RedisQueuedLocks::Instrument::Sampler` for example);
71
+ # @param instr_sample_this [Boolean]
72
+ # - marks the method that everything should be instrumneted
73
+ # despite the enabled instrumentation sampling;
74
+ # - makes sense when instrumentation sampling is enabled;
75
+ # @return [Hash<Symbol,Any>]
76
+ # Format: { ok: true, result: Hash<Symbol,Numeric> }
77
+ #
78
+ # @api private
79
+ # @since 1.14.0
80
+ # rubocop:disable Metrics/MethodLength
81
+ def release_locks_of(
82
+ refused_host_id,
83
+ refused_acquirer_id,
84
+ redis,
85
+ lock_scan_size,
86
+ queue_scan_size,
87
+ logger,
88
+ instrumenter,
89
+ instrument,
90
+ log_sampling_enabled,
91
+ log_sampling_percent,
92
+ log_sampler,
93
+ log_sample_this,
94
+ instr_sampling_enabled,
95
+ instr_sampling_percent,
96
+ instr_sampler,
97
+ instr_sample_this
98
+ ) # TODO: move from SCAN to indexes :thinking:
99
+ rel_start_time = clock_gettime
100
+
101
+ # steep:ignore:start
102
+ fully_release_locks_of(
103
+ refused_host_id,
104
+ refused_acquirer_id,
105
+ redis,
106
+ lock_scan_size,
107
+ queue_scan_size
108
+ ) => { ok:, result: }
109
+ # steep:ignore:end
110
+
111
+ # @type var ok: bool
112
+ # @type var result: Hash[Symbol,Integer]
113
+
114
+ time_at = Time.now.to_f
115
+ rel_end_time = clock_gettime
116
+ rel_time = ((rel_end_time - rel_start_time) / 1_000.0).ceil(2)
117
+
118
+ instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
119
+ instr_sampling_enabled,
120
+ instr_sample_this,
121
+ instr_sampling_percent,
122
+ instr_sampler
123
+ )
124
+
125
+ run_non_critical do
126
+ instrumenter.notify('redis_queued_locks.release_locks_of', {
127
+ at: time_at,
128
+ rel_time: rel_time,
129
+ rel_key_cnt: result[:rel_key_cnt],
130
+ tch_queue_cnt: result[:tch_queue_cnt]
131
+ })
132
+ end if instr_sampled
133
+
134
+ {
135
+ ok: true,
136
+ result: {
137
+ rel_key_cnt: result[:rel_key_cnt],
138
+ tch_queue_cnt: result[:tch_queue_cnt],
139
+ rel_time: rel_time
140
+ }
141
+ }
142
+ end
143
+ # rubocop:enable Metrics/MethodLength
144
+
145
+ private
146
+
147
+ # @param refused_host_id [String]
148
+ # @param refused_acquirer_id [String]
149
+ # @param redis [RedisClient]
150
+ # @param lock_scan_size [Integer]
151
+ # @param queue_scan_size [Integer]
152
+ # @return [Hash<Symbol,Boolean|Hash<Symbol,Integer>>]
153
+ # - Example: { ok: true, result: { rel_key_cnt: 12345, tch_queue_cnt: 321 } }
154
+ #
155
+ # @api private
156
+ # @since 1.14.0
157
+ # rubocop:disable Metrics/MethodLength
158
+ def fully_release_locks_of(
159
+ refused_host_id,
160
+ refused_acquirer_id,
161
+ redis,
162
+ lock_scan_size,
163
+ queue_scan_size
164
+ )
165
+ # TODO: some indexing approach isntead of <scan>
166
+ rel_key_cnt = 0
167
+ tch_queue_cnt = 0
168
+
169
+ redis.with do |rconn|
170
+ # Step A: drop locks of the passed host/acquirer
171
+ refused_locks = Set.new #: Set[String]
172
+ rconn.scan(
173
+ 'MATCH',
174
+ RedisQueuedLocks::Resource::LOCK_PATTERN,
175
+ count: lock_scan_size
176
+ ) do |lock_key|
177
+ acquirer_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
178
+ if refused_host_id == host_id && refused_acquirer_id == acquirer_id
179
+ refused_locks << lock_key
180
+ end
181
+
182
+ if refused_locks.size >= lock_scan_size
183
+ # NOTE: steep can not recognize the `*`-splat operator on Set objects
184
+ rconn.call('DEL', *refused_locks) # steep:ignore
185
+ rel_key_cnt += refused_locks.size
186
+ refused_locks.clear
187
+ end
188
+ end
189
+
190
+ if refused_locks.any?
191
+ # NOTE: steep can not recognize the `*`-splat operator on Set objects
192
+ rconn.call('DEL', *refused_locks) # steep:ignore
193
+ rel_key_cnt += refused_locks.size
194
+ end
195
+
196
+ # Step B: drop passed host/acquirer from lock queues
197
+ rconn.scan(
198
+ 'MATCH',
199
+ RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
200
+ count: queue_scan_size
201
+ ) do |lock_queue|
202
+ res = rconn.call('ZREM', lock_queue, refused_acquirer_id)
203
+ tch_queue_cnt += 1 if res != 0
204
+ end
205
+ end
206
+
207
+ { ok: true, result: { rel_key_cnt:, tch_queue_cnt: } }
208
+ end
209
+ end
210
+ # rubocop:enable Metrics/MethodLength
211
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.0.0
5
+ module RedisQueuedLocks::Acquirer
6
+ require_relative 'acquirer/acquire_lock'
7
+ require_relative 'acquirer/release_lock'
8
+ require_relative 'acquirer/release_all_locks'
9
+ require_relative 'acquirer/release_locks_of'
10
+ require_relative 'acquirer/is_locked'
11
+ require_relative 'acquirer/is_queued'
12
+ require_relative 'acquirer/lock_info'
13
+ require_relative 'acquirer/queue_info'
14
+ require_relative 'acquirer/locks'
15
+ require_relative 'acquirer/queues'
16
+ require_relative 'acquirer/keys'
17
+ require_relative 'acquirer/extend_lock_ttl'
18
+ require_relative 'acquirer/clear_dead_requests'
19
+ end