redis_queued_locks 1.8.0 → 1.9.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +51 -0
  4. data/README.md +402 -46
  5. data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +4 -0
  6. data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue.rb +4 -1
  7. data/lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb +20 -5
  8. data/lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb +24 -0
  9. data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb +56 -0
  10. data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +37 -30
  11. data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb +8 -0
  12. data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +13 -9
  13. data/lib/redis_queued_locks/acquier/acquire_lock.rb +44 -20
  14. data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +5 -1
  15. data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +5 -1
  16. data/lib/redis_queued_locks/acquier/lock_info.rb +4 -3
  17. data/lib/redis_queued_locks/acquier/locks.rb +2 -2
  18. data/lib/redis_queued_locks/acquier/queue_info.rb +2 -2
  19. data/lib/redis_queued_locks/acquier/release_all_locks.rb +12 -2
  20. data/lib/redis_queued_locks/acquier/release_lock.rb +12 -2
  21. data/lib/redis_queued_locks/client.rb +284 -34
  22. data/lib/redis_queued_locks/errors.rb +8 -0
  23. data/lib/redis_queued_locks/instrument.rb +8 -1
  24. data/lib/redis_queued_locks/logging.rb +8 -1
  25. data/lib/redis_queued_locks/resource.rb +59 -1
  26. data/lib/redis_queued_locks/swarm/acquirers.rb +44 -0
  27. data/lib/redis_queued_locks/swarm/flush_zombies.rb +133 -0
  28. data/lib/redis_queued_locks/swarm/probe_hosts.rb +69 -0
  29. data/lib/redis_queued_locks/swarm/redis_client_builder.rb +67 -0
  30. data/lib/redis_queued_locks/swarm/supervisor.rb +83 -0
  31. data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +287 -0
  32. data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +351 -0
  33. data/lib/redis_queued_locks/swarm/swarm_element.rb +8 -0
  34. data/lib/redis_queued_locks/swarm/zombie_info.rb +145 -0
  35. data/lib/redis_queued_locks/swarm.rb +241 -0
  36. data/lib/redis_queued_locks/utilities/lock.rb +22 -0
  37. data/lib/redis_queued_locks/utilities.rb +75 -0
  38. data/lib/redis_queued_locks/version.rb +2 -2
  39. data/lib/redis_queued_locks.rb +2 -0
  40. metadata +14 -4
  41. data/lib/redis_queued_locks/watcher.rb +0 -1
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 1.9.0
5
+ # rubocop:disable Metrics/ClassLength
6
+ class RedisQueuedLocks::Swarm
7
+ require_relative 'swarm/redis_client_builder'
8
+ require_relative 'swarm/supervisor'
9
+ require_relative 'swarm/acquirers'
10
+ require_relative 'swarm/zombie_info'
11
+ require_relative 'swarm/swarm_element'
12
+ require_relative 'swarm/probe_hosts'
13
+ require_relative 'swarm/flush_zombies'
14
+
15
+ # @return [RedisQueuedLocks::Client]
16
+ #
17
+ # @api private
18
+ # @since 1.9.0
19
+ attr_reader :rql_client
20
+
21
+ # @return [RedisQueuedLocks::Swarm::Supervisor]
22
+ #
23
+ # @api private
24
+ # @since 1.9.0
25
+ attr_reader :supervisor
26
+
27
+ # @return [RedisQueuedLocks::Swarm::ProbeHosts]
28
+ #
29
+ # @api private
30
+ # @since 1.9.0
31
+ attr_reader :probe_hosts_element
32
+
33
+ # @return [RedisQueuedLocks::Swarm::FlushZombies]
34
+ #
35
+ # @api private
36
+ # @since 1.9.0
37
+ attr_reader :flush_zombies_element
38
+
39
+ # @return [RedisQueuedLocks::Utilities::Lock]
40
+ #
41
+ # @api private
42
+ # @since 1.9.0
43
+ attr_reader :sync
44
+
45
+ # @param rql_client [RedisQueuedLocks::Client]
46
+ # @return [void]
47
+ #
48
+ # @api private
49
+ # @since 1.9.0
50
+ def initialize(rql_client)
51
+ @rql_client = rql_client
52
+ @sync = RedisQueuedLocks::Utilities::Lock.new
53
+ @supervisor = RedisQueuedLocks::Swarm::Supervisor.new(rql_client)
54
+ @probe_hosts_element = RedisQueuedLocks::Swarm::ProbeHosts.new(rql_client)
55
+ @flush_zombies_element = RedisQueuedLocks::Swarm::FlushZombies.new(rql_client)
56
+ end
57
+
58
+ # @return [Hash<Symbol,Boolean|<Hash<Symbol,Boolean>>]
59
+ #
60
+ # @api public
61
+ # @since 1.9.0
62
+ def swarm_status
63
+ sync.synchronize do
64
+ {
65
+ auto_swarm: rql_client.config[:swarm][:auto_swarm],
66
+ supervisor: supervisor.status,
67
+ probe_hosts: probe_hosts_element.status,
68
+ flush_zombies: flush_zombies_element.status
69
+ }
70
+ end
71
+ end
72
+ alias_method :swarm_state, :swarm_status
73
+
74
+ # @option zombie_ttl [Integer]
75
+ # @return [Hash<String,Hash<Symbol,Float|Time>>]
76
+ #
77
+ # @api public
78
+ # @since 1.9.0
79
+ def swarm_info(zombie_ttl: rql_client.config[:swarm][:flush_zombies][:zombie_ttl])
80
+ RedisQueuedLocks::Swarm::Acquirers.acquirers(
81
+ rql_client.redis_client,
82
+ zombie_ttl
83
+ )
84
+ end
85
+
86
+ # @return [
87
+ # RedisQueuedLocks::Data[
88
+ # ok: <Boolean>,
89
+ # result: {
90
+ # host_id1 <String> => score1 <String>,
91
+ # host_id2 <String> => score2 <String>,
92
+ # etc...
93
+ # }
94
+ # ]
95
+ # ]
96
+ #
97
+ # @api public
98
+ # @since 1.9.0
99
+ def probe_hosts
100
+ RedisQueuedLocks::Swarm::ProbeHosts.probe_hosts(
101
+ rql_client.redis_client,
102
+ rql_client.uniq_identity
103
+ )
104
+ end
105
+
106
+ # @option zombie_ttl [Integer]
107
+ # @option lock_scan_size [Integer]
108
+ # @option queue_scan_size [Integer]
109
+ # @return [
110
+ # RedisQueuedLocks::Data[
111
+ # ok: <Boolean>,
112
+ # deleted_zombie_hosts: <Set<String>>,
113
+ # deleted_zombie_acquiers: <Set<String>>,
114
+ # deleted_zombie_locks: <Set<String>>
115
+ # ]
116
+ # ]
117
+ #
118
+ # @api public
119
+ # @since 1.9.0
120
+ def flush_zombies(
121
+ zombie_ttl: rql_client.config[:swarm][:flush_zombies][:zombie_ttl],
122
+ lock_scan_size: rql_client.config[:swarm][:flush_zombies][:zombie_lock_scan_size],
123
+ queue_scan_size: rql_client.config[:swarm][:flush_zombies][:zombie_queue_scan_size]
124
+ )
125
+ RedisQueuedLocks::Swarm::FlushZombies.flush_zombies(
126
+ rql_client.redis_client,
127
+ zombie_ttl,
128
+ lock_scan_size,
129
+ queue_scan_size
130
+ )
131
+ end
132
+
133
+ # @return [Set<String>]
134
+ #
135
+ # @api public
136
+ # @since 1.9.0
137
+ def zombie_locks(
138
+ zombie_ttl: rql_client.config[:swarm][:flush_zombies][:zombie_ttl],
139
+ lock_scan_size: rql_client.config[:swarm][:flush_zombies][:zombie_lock_scan_size]
140
+ )
141
+ RedisQueuedLocks::Swarm::ZombieInfo.zombie_locks(
142
+ rql_client.redis_client,
143
+ zombie_ttl,
144
+ lock_scan_size
145
+ )
146
+ end
147
+
148
+ # @return [Set<String>]
149
+ #
150
+ # @api ppublic
151
+ # @since 1.9.0
152
+ def zombie_acquiers(
153
+ zombie_ttl: rql_client.config[:swarm][:flush_zombies][:zombie_ttl],
154
+ lock_scan_size: rql_client.config[:swarm][:flush_zombies][:zombie_lock_scan_size]
155
+ )
156
+ RedisQueuedLocks::Swarm::ZombieInfo.zombie_acquiers(
157
+ rql_client.redis_client,
158
+ zombie_ttl,
159
+ lock_scan_size
160
+ )
161
+ end
162
+
163
+ # @return [Set<String>]
164
+ #
165
+ # @api public
166
+ # @since 1.9.0
167
+ def zombie_hosts(zombie_ttl: rql_client.config[:swarm][:flush_zombies][:zombie_ttl])
168
+ RedisQueuedLocks::Swarm::ZombieInfo.zombie_hosts(rql_client.redis_client, zombie_ttl)
169
+ end
170
+
171
+ # @return [Hash<Symbol,Set<String>>]
172
+ # Format: {
173
+ # zombie_hosts: <Set<String>>,
174
+ # zombie_acquirers: <Set<String>>,
175
+ # zombie_locks: <Set<String>>
176
+ # }
177
+ #
178
+ # @api public
179
+ # @since 1.9.0
180
+ def zombies_info(
181
+ zombie_ttl: rql_client.config[:swarm][:flush_zombies][:zombie_ttl],
182
+ lock_scan_size: rql_client.config[:swarm][:flush_zombies][:zombie_lock_scan_size]
183
+ )
184
+ RedisQueuedLocks::Swarm::ZombieInfo.zombies_info(
185
+ rql_client.redis_client,
186
+ zombie_ttl,
187
+ lock_scan_size
188
+ )
189
+ end
190
+
191
+ # @option silently [Boolean]
192
+ # @return [NilClass,Hash<Symbol,Symbol|Boolean>]
193
+ #
194
+ # @api public
195
+ # @since 1.9.0
196
+ def swarm!
197
+ sync.synchronize do
198
+ # Step 0:
199
+ # - stop the supervisor (kill internal observer objects if supervisor is alredy running);
200
+ supervisor.stop!
201
+
202
+ # Step 1:
203
+ # - initialize swarm elements and start their main loops;
204
+ probe_hosts_element.try_swarm!
205
+ flush_zombies_element.try_swarm!
206
+
207
+ # Step 2:
208
+ # - run supervisor that should keep running created swarm elements and their main loops;
209
+ unless supervisor.running?
210
+ supervisor.observe! do
211
+ probe_hosts_element.reswarm_if_dead!
212
+ flush_zombies_element.reswarm_if_dead!
213
+ end
214
+ end
215
+
216
+ # NOTE: need to give a little timespot to initialize ractor objects and their main loops;
217
+ sleep(0.1)
218
+
219
+ RedisQueuedLocks::Data[ok: true, result: :swarming]
220
+ end
221
+ end
222
+
223
+ # @option silently [Boolean]
224
+ # @return [NilClass,Hash<Symbol,Symbol|Boolean>]
225
+ #
226
+ # @api public
227
+ # @since 1.9.0
228
+ def deswarm!
229
+ sync.synchronize do
230
+ supervisor.stop!
231
+ probe_hosts_element.try_kill!
232
+ flush_zombies_element.try_kill!
233
+
234
+ # NOTE: need to give a little timespot to stop ractor objects and their main loops;
235
+ sleep(0.1)
236
+
237
+ RedisQueuedLocks::Data[ok: true, result: :terminating]
238
+ end
239
+ end
240
+ end
241
+ # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.9.0
5
+ class RedisQueuedLocks::Utilities::Lock
6
+ # @return [void]
7
+ #
8
+ # @api private
9
+ # @since 1.9.0
10
+ def initialize
11
+ @lock = ::Mutex.new
12
+ end
13
+
14
+ # @param block [Block]
15
+ # @return [Any]
16
+ #
17
+ # @api private
18
+ # @since 1.9.0
19
+ def synchronize(&block)
20
+ @lock.owned? ? yield : @lock.synchronize(&block)
21
+ end
22
+ end
@@ -3,8 +3,38 @@
3
3
  # @api private
4
4
  # @since 1.0.0
5
5
  module RedisQueuedLocks::Utilities
6
+ require_relative 'utilities/lock'
7
+
6
8
  module_function
7
9
 
10
+ # Ractor class has no methods for Ractor object status identification.
11
+ # The only one approach (without C/FFI extending) to give the status is to check
12
+ # the status from the `#inspect/#to_s` methods. String representation has the following format:
13
+ # Format: "#<Ractor:##{id}#{name ? ' '+name : ''}#{loc ? " " + loc : ''} #{status}>"
14
+ # Example: #<Ractor:#2 (irb):2 terminated>
15
+ # So we need to parse the `status` part of the inspect method and use it for our logic.
16
+ # Current regexp object provides the pattern for this.
17
+ #
18
+ # @return [Regexp]
19
+ #
20
+ # @api private
21
+ # @since 1.9.0
22
+ RACTOR_LIVENESS_PATTERN = /\A.*?(created|running|blocking).*?\z/i
23
+
24
+ # Ractor status as a string extracted from the object string representation.
25
+ # This way is used cuz the ractor class has no any detailed status extraction API.
26
+ # Possible statuses (at the moment of Ruby@3.4):
27
+ # - "created"
28
+ # - "blocking"
29
+ # - "running"
30
+ # - "terminated"
31
+ #
32
+ # @return [Regexp]
33
+ #
34
+ # @api private
35
+ # @since 1.9.0
36
+ RACTOR_STATUS_PATTERN = /\A.*?\s(?<status>\w+)>\z/i
37
+
8
38
  # @param block [Block]
9
39
  # @return [Any]
10
40
  #
@@ -13,4 +43,49 @@ module RedisQueuedLocks::Utilities
13
43
  def run_non_critical(&block)
14
44
  yield rescue nil
15
45
  end
46
+
47
+ # Possible statuses (at the moment of Ruby@3.4):
48
+ # - "created"
49
+ # - "blocking"
50
+ # - "running"
51
+ # - "terminated"
52
+ #
53
+ # @param ractor [Ractor]
54
+ # @return [String]
55
+ #
56
+ # @api private
57
+ # @since 1.9.0
58
+ def ractor_status(ractor)
59
+ ractor.to_s.match(RACTOR_STATUS_PATTERN)[:status]
60
+ end
61
+
62
+ # @param ractor [Ractor]
63
+ # @return [Boolean]
64
+ #
65
+ # @api private
66
+ # @since 1.9.0
67
+ def ractor_alive?(ractor)
68
+ ractor.to_s.match?(RACTOR_LIVENESS_PATTERN)
69
+ end
70
+
71
+ # Returns the status of the passed thread object.
72
+ # Possible statuses:
73
+ # - "run" (thread is executing);
74
+ # - "sleep" (thread is sleeping or waiting on I/O);
75
+ # - "aborting" (thread is aborting)
76
+ # - "dead" (thread is terminated normally);
77
+ # - "failed" (thread is terminated with an exception);
78
+ # See Thread#status official documentation.
79
+ #
80
+ # @param [Thread]
81
+ # @return [String]
82
+ #
83
+ # @api private
84
+ # @since 1.9.0
85
+ def thread_state(thread)
86
+ status = thread.status
87
+ return 'dead' if status == false
88
+ return 'failed' if status == nil
89
+ status
90
+ end
16
91
  end
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 1.8.0
9
- VERSION = '1.8.0'
8
+ # @version 1.9.0
9
+ VERSION = '1.9.0'
10
10
  end
@@ -5,6 +5,7 @@ require 'qonfig'
5
5
  require 'timeout'
6
6
  require 'securerandom'
7
7
  require 'logger'
8
+ require 'objspace'
8
9
 
9
10
  # @api public
10
11
  # @since 1.0.0
@@ -18,6 +19,7 @@ module RedisQueuedLocks
18
19
  require_relative 'redis_queued_locks/resource'
19
20
  require_relative 'redis_queued_locks/acquier'
20
21
  require_relative 'redis_queued_locks/instrument'
22
+ require_relative 'redis_queued_locks/swarm'
21
23
  require_relative 'redis_queued_locks/client'
22
24
 
23
25
  # @since 1.0.0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -99,9 +99,19 @@ files:
99
99
  - lib/redis_queued_locks/logging/sampler.rb
100
100
  - lib/redis_queued_locks/logging/void_logger.rb
101
101
  - lib/redis_queued_locks/resource.rb
102
+ - lib/redis_queued_locks/swarm.rb
103
+ - lib/redis_queued_locks/swarm/acquirers.rb
104
+ - lib/redis_queued_locks/swarm/flush_zombies.rb
105
+ - lib/redis_queued_locks/swarm/probe_hosts.rb
106
+ - lib/redis_queued_locks/swarm/redis_client_builder.rb
107
+ - lib/redis_queued_locks/swarm/supervisor.rb
108
+ - lib/redis_queued_locks/swarm/swarm_element.rb
109
+ - lib/redis_queued_locks/swarm/swarm_element/isolated.rb
110
+ - lib/redis_queued_locks/swarm/swarm_element/threaded.rb
111
+ - lib/redis_queued_locks/swarm/zombie_info.rb
102
112
  - lib/redis_queued_locks/utilities.rb
113
+ - lib/redis_queued_locks/utilities/lock.rb
103
114
  - lib/redis_queued_locks/version.rb
104
- - lib/redis_queued_locks/watcher.rb
105
115
  - redis_queued_locks.gemspec
106
116
  homepage: https://github.com/0exp/redis_queued_locks
107
117
  licenses:
@@ -125,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
135
  - !ruby/object:Gem::Version
126
136
  version: '0'
127
137
  requirements: []
128
- rubygems_version: 3.5.1
138
+ rubygems_version: 3.5.11
129
139
  signing_key:
130
140
  specification_version: 4
131
141
  summary: Distributed locks with "prioritized lock acquisition queue" capabilities
@@ -1 +0,0 @@
1
- # frozen_string_literal: true