redis_queued_locks 1.8.0 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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