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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +51 -0
- data/README.md +402 -46
- data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue/log_visitor.rb +4 -0
- data/lib/redis_queued_locks/acquier/acquire_lock/dequeue_from_lock_queue.rb +4 -1
- data/lib/redis_queued_locks/acquier/acquire_lock/instr_visitor.rb +20 -5
- data/lib/redis_queued_locks/acquier/acquire_lock/log_visitor.rb +24 -0
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock/log_visitor.rb +56 -0
- data/lib/redis_queued_locks/acquier/acquire_lock/try_to_lock.rb +37 -30
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire/log_visitor.rb +8 -0
- data/lib/redis_queued_locks/acquier/acquire_lock/yield_expire.rb +13 -9
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +44 -20
- data/lib/redis_queued_locks/acquier/clear_dead_requests.rb +5 -1
- data/lib/redis_queued_locks/acquier/extend_lock_ttl.rb +5 -1
- data/lib/redis_queued_locks/acquier/lock_info.rb +4 -3
- data/lib/redis_queued_locks/acquier/locks.rb +2 -2
- data/lib/redis_queued_locks/acquier/queue_info.rb +2 -2
- data/lib/redis_queued_locks/acquier/release_all_locks.rb +12 -2
- data/lib/redis_queued_locks/acquier/release_lock.rb +12 -2
- data/lib/redis_queued_locks/client.rb +284 -34
- data/lib/redis_queued_locks/errors.rb +8 -0
- data/lib/redis_queued_locks/instrument.rb +8 -1
- data/lib/redis_queued_locks/logging.rb +8 -1
- data/lib/redis_queued_locks/resource.rb +59 -1
- data/lib/redis_queued_locks/swarm/acquirers.rb +44 -0
- data/lib/redis_queued_locks/swarm/flush_zombies.rb +133 -0
- data/lib/redis_queued_locks/swarm/probe_hosts.rb +69 -0
- data/lib/redis_queued_locks/swarm/redis_client_builder.rb +67 -0
- data/lib/redis_queued_locks/swarm/supervisor.rb +83 -0
- data/lib/redis_queued_locks/swarm/swarm_element/isolated.rb +287 -0
- data/lib/redis_queued_locks/swarm/swarm_element/threaded.rb +351 -0
- data/lib/redis_queued_locks/swarm/swarm_element.rb +8 -0
- data/lib/redis_queued_locks/swarm/zombie_info.rb +145 -0
- data/lib/redis_queued_locks/swarm.rb +241 -0
- data/lib/redis_queued_locks/utilities/lock.rb +22 -0
- data/lib/redis_queued_locks/utilities.rb +75 -0
- data/lib/redis_queued_locks/version.rb +2 -2
- data/lib/redis_queued_locks.rb +2 -0
- metadata +14 -4
- 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
|
data/lib/redis_queued_locks.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|