redis_queued_locks 1.7.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 +60 -1
- data/README.md +485 -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/with_acq_timeout.rb +41 -7
- 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 +21 -9
- data/lib/redis_queued_locks/acquier/acquire_lock.rb +61 -22
- 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 +320 -10
- 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
- data/redis_queued_locks.gemspec +6 -10
- metadata +24 -6
- data/lib/redis_queued_locks/watcher.rb +0 -1
@@ -21,6 +21,12 @@ module RedisQueuedLocks::Resource
|
|
21
21
|
# @since 1.0.0
|
22
22
|
LOCK_QUEUE_PATTERN = 'rql:lock_queue:*'
|
23
23
|
|
24
|
+
# @return [String]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
# @since 1.9.0
|
28
|
+
SWARM_KEY = 'rql:swarm:hsts'
|
29
|
+
|
24
30
|
# @return [Integer] Redis time error (in milliseconds).
|
25
31
|
#
|
26
32
|
# @api private
|
@@ -53,6 +59,22 @@ module RedisQueuedLocks::Resource
|
|
53
59
|
"rql:acq:#{process_id}/#{thread_id}/#{fiber_id}/#{ractor_id}/#{identity}"
|
54
60
|
end
|
55
61
|
|
62
|
+
# @param process_id [Integer,String]
|
63
|
+
# @param thread_id [Integer,String]
|
64
|
+
# @param ractor_id [Integer,String]
|
65
|
+
# @param identity [String]
|
66
|
+
# @return [String]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
# @since 1.9.0
|
70
|
+
def host_identifier(process_id, thread_id, ractor_id, identity)
|
71
|
+
# NOTE:
|
72
|
+
# - fiber's object_id is not used cuz we can't analyze fiber objects via ObjectSpace
|
73
|
+
# after the any new Ractor object is created in the current process
|
74
|
+
# (ObjectSpace no longer sees objects of Thread and Fiber classes after that);
|
75
|
+
"rql:hst:#{process_id}/#{thread_id}/#{ractor_id}/#{identity}"
|
76
|
+
end
|
77
|
+
|
56
78
|
# @param lock_name [String]
|
57
79
|
# @return [String]
|
58
80
|
#
|
@@ -88,8 +110,17 @@ module RedisQueuedLocks::Resource
|
|
88
110
|
Time.now.to_f - queue_ttl
|
89
111
|
end
|
90
112
|
|
113
|
+
# @param zombie_ttl [Float] In seconds with milliseconds.
|
114
|
+
# @return [Float]
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
# @since 1.9.0
|
118
|
+
def calc_zombie_score(zombie_ttl)
|
119
|
+
Time.now.to_f - zombie_ttl
|
120
|
+
end
|
121
|
+
|
91
122
|
# @param acquier_position [Float]
|
92
|
-
# A time (epoch, seconds.
|
123
|
+
# A time (epoch, seconds.milliseconds) that represents
|
93
124
|
# the acquier position in lock request queue.
|
94
125
|
# @parma queue_ttl [Integer]
|
95
126
|
# In second.
|
@@ -143,5 +174,32 @@ module RedisQueuedLocks::Resource
|
|
143
174
|
def get_process_id
|
144
175
|
::Process.pid
|
145
176
|
end
|
177
|
+
|
178
|
+
# @return [Array<String>]
|
179
|
+
#
|
180
|
+
# @api private
|
181
|
+
# @since 1.9.0
|
182
|
+
def possible_host_identifiers(identity)
|
183
|
+
# NOTE №1: we can not use ObjectSpace.each_object for Thread and Fiber cuz after the any
|
184
|
+
# ractor creation the ObjectSpace stops seeing ::Thread and ::Fiber objects: each_object
|
185
|
+
# for each of them returns `0`;
|
186
|
+
# NOTE №2: we have no any approach to count Fiber objects in the current process without
|
187
|
+
# object space API (or super memory-expensive) so host identification works without fibers;
|
188
|
+
# NOTE №3: we still can extract thread objects via Thread.list API;
|
189
|
+
current_process_id = get_process_id
|
190
|
+
current_threads = ::Thread.list
|
191
|
+
current_ractor_id = get_ractor_id
|
192
|
+
|
193
|
+
[].tap do |acquiers|
|
194
|
+
current_threads.each do |thread|
|
195
|
+
acquiers << host_identifier(
|
196
|
+
current_process_id,
|
197
|
+
thread.object_id,
|
198
|
+
current_ractor_id,
|
199
|
+
identity
|
200
|
+
)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
146
204
|
end
|
147
205
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.9.0
|
5
|
+
module RedisQueuedLocks::Swarm::Acquirers
|
6
|
+
class << self
|
7
|
+
# Returns the list of swarm acquirers stored as HASH.
|
8
|
+
# Format:
|
9
|
+
# {
|
10
|
+
# <acquirer id #1> => {
|
11
|
+
# zombie: <Boolean>,
|
12
|
+
# last_probe_time: <Time>,
|
13
|
+
# last_probe_score: <Numeric>
|
14
|
+
# },
|
15
|
+
# <acquirer id #2> => {
|
16
|
+
# zombie: <Boolean>,
|
17
|
+
# last_probe_time: <Time>,
|
18
|
+
# last_probe_score: <Numeric>
|
19
|
+
# },
|
20
|
+
# ...
|
21
|
+
# }
|
22
|
+
# Liveness probe time is represented as a float value (Time.now.to_f initially).
|
23
|
+
#
|
24
|
+
# @param redis_client [RedisClient]
|
25
|
+
# @param zombie_ttl [Integer]
|
26
|
+
# @return [Hash<String,Hash<Symbol,Float|Time>>]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
# @since 1.9.0
|
30
|
+
def acquirers(redis_client, zombie_ttl)
|
31
|
+
redis_client.with do |rconn|
|
32
|
+
rconn.call('HGETALL', RedisQueuedLocks::Resource::SWARM_KEY).tap do |swarm_acqs|
|
33
|
+
swarm_acqs.transform_values! do |last_probe|
|
34
|
+
last_probe_score = last_probe.to_f
|
35
|
+
last_probe_time = Time.at(last_probe_score)
|
36
|
+
zombie_score = RedisQueuedLocks::Resource.calc_zombie_score(zombie_ttl / 1_000)
|
37
|
+
is_zombie = last_probe_score < zombie_score
|
38
|
+
{ zombie: is_zombie, last_probe_time:, last_probe_score: }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.9.0
|
5
|
+
class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElement::Isolated
|
6
|
+
class << self
|
7
|
+
# @param redis_client [RedisClient]
|
8
|
+
# @parma zombie_ttl [Integer]
|
9
|
+
# @param lock_scan_size [Integer]
|
10
|
+
# @param queue_scan_size [Integer]
|
11
|
+
# @return [
|
12
|
+
# RedisQueuedLocks::Data[
|
13
|
+
# ok: <Boolean>,
|
14
|
+
# deleted_zombie_hosts: <Set<String>>,
|
15
|
+
# deleted_zombie_acquiers: <Set<String>>,
|
16
|
+
# deleted_zombie_locks: <Set<String>>
|
17
|
+
# ]
|
18
|
+
# ]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
# @since 1.9.0
|
22
|
+
# rubocop:disable Metrics/MethodLength
|
23
|
+
def flush_zombies(
|
24
|
+
redis_client,
|
25
|
+
zombie_ttl,
|
26
|
+
lock_scan_size,
|
27
|
+
queue_scan_size
|
28
|
+
)
|
29
|
+
redis_client.with do |rconn|
|
30
|
+
# Step 1:
|
31
|
+
# calculate zombie score (the time marker that shows acquirers that
|
32
|
+
# have not announced live probes for a long time)
|
33
|
+
zombie_score = RedisQueuedLocks::Resource.calc_zombie_score(zombie_ttl / 1_000)
|
34
|
+
|
35
|
+
# Step 2: extract zombie acquirers from the swarm list
|
36
|
+
zombie_hosts = rconn.call('HGETALL', RedisQueuedLocks::Resource::SWARM_KEY)
|
37
|
+
zombie_hosts = zombie_hosts.each_with_object(Set.new) do |(hst_id, ts), zombies|
|
38
|
+
(zombies << hst_id) if (zombie_score > ts.to_f)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Step X: exit if we have no any zombie acquirer
|
42
|
+
next RedisQueuedLocks::Data[
|
43
|
+
ok: true,
|
44
|
+
deleted_zombie_hosts: Set.new,
|
45
|
+
deleted_zombie_acquirers: Set.new,
|
46
|
+
deleted_zombie_locks: Set.new,
|
47
|
+
] if zombie_hosts.empty?
|
48
|
+
|
49
|
+
# Step 3: find zombie locks held by zombies and delete them
|
50
|
+
# TODO: indexing (in order to prevent full database scan);
|
51
|
+
# NOTE: original redis does not support indexing so we need to use
|
52
|
+
# internal data structers to simulate data indexing (such as sorted sets or lists);
|
53
|
+
zombie_locks = Set.new
|
54
|
+
zombie_acquiers = Set.new
|
55
|
+
|
56
|
+
rconn.scan(
|
57
|
+
'MATCH', RedisQueuedLocks::Resource::LOCK_PATTERN, count: lock_scan_size
|
58
|
+
) do |lock_key|
|
59
|
+
acquier_id, host_id = rconn.call('HMGET', lock_key, 'acq_id', 'hst_id')
|
60
|
+
if zombie_hosts.include?(host_id)
|
61
|
+
zombie_locks << lock_key
|
62
|
+
zombie_acquiers << acquier_id
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rconn.call('DEL', *zombie_locks) if zombie_locks.any?
|
66
|
+
|
67
|
+
# Step 4: find zombie requests => and drop them
|
68
|
+
# TODO: indexing (in order to prevent full database scan);
|
69
|
+
# NOTE: original redis does not support indexing so we need to use
|
70
|
+
# internal data structers to simulate data indexing (such as sorted sets or lists);
|
71
|
+
rconn.scan(
|
72
|
+
'MATCH', RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN, count: queue_scan_size
|
73
|
+
) do |lock_queue|
|
74
|
+
zombie_acquiers.each do |zombie_acquier|
|
75
|
+
rconn.call('ZREM', lock_queue, zombie_acquier)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Step 5: drop zombies from the swarm
|
80
|
+
rconn.call('HDEL', RedisQueuedLocks::Resource::SWARM_KEY, *zombie_hosts)
|
81
|
+
|
82
|
+
# Step 6: inform about deleted zombies
|
83
|
+
RedisQueuedLocks::Data[
|
84
|
+
ok: true,
|
85
|
+
deleted_zombie_hosts: zombie_hosts,
|
86
|
+
deleted_zombie_acquiers: zombie_acquiers,
|
87
|
+
deleted_zombie_locks: zombie_locks
|
88
|
+
]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/MethodLength
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Boolean]
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
# @since 1.9.0
|
98
|
+
def enabled?
|
99
|
+
rql_client.config[:swarm][:flush_zombies][:enabled_for_swarm]
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [void]
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
# @since 1.9.0
|
106
|
+
def swarm!
|
107
|
+
@swarm_element = Ractor.new(
|
108
|
+
rql_client.config.slice_value('swarm.flush_zombies.redis_config'),
|
109
|
+
rql_client.config[:swarm][:flush_zombies][:zombie_ttl],
|
110
|
+
rql_client.config[:swarm][:flush_zombies][:zombie_lock_scan_size],
|
111
|
+
rql_client.config[:swarm][:flush_zombies][:zombie_queue_scan_size],
|
112
|
+
rql_client.config[:swarm][:flush_zombies][:zombie_flush_period]
|
113
|
+
) do |rc, z_ttl, z_lss, z_qss, z_fl_prd|
|
114
|
+
RedisQueuedLocks::Swarm::FlushZombies.swarm_loop do
|
115
|
+
Thread.new do
|
116
|
+
redis_client = RedisQueuedLocks::Swarm::RedisClientBuilder.build(
|
117
|
+
pooled: rc['pooled'],
|
118
|
+
sentinel: rc['sentinel'],
|
119
|
+
config: rc['config'],
|
120
|
+
pool_config: rc['pool_config']
|
121
|
+
)
|
122
|
+
|
123
|
+
loop do
|
124
|
+
RedisQueuedLocks::Swarm::FlushZombies.flush_zombies(
|
125
|
+
redis_client, z_ttl, z_lss, z_qss
|
126
|
+
)
|
127
|
+
sleep(z_fl_prd)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.9.0
|
5
|
+
class RedisQueuedLocks::Swarm::ProbeHosts < RedisQueuedLocks::Swarm::SwarmElement::Threaded
|
6
|
+
class << self
|
7
|
+
# @param redis_client [RedisClient]
|
8
|
+
# @param uniq_identity [String]
|
9
|
+
# @return [
|
10
|
+
# RedisQueuedLocks::Data[
|
11
|
+
# ok: <Boolean>,
|
12
|
+
# result: {
|
13
|
+
# host_id1 <String> => score1 <String>,
|
14
|
+
# host_id2 <String> => score2 <String>,
|
15
|
+
# etc...
|
16
|
+
# }
|
17
|
+
# ]
|
18
|
+
# ]
|
19
|
+
#
|
20
|
+
# @api private
|
21
|
+
# @since 1.9.0
|
22
|
+
def probe_hosts(redis_client, uniq_identity)
|
23
|
+
possible_hosts = RedisQueuedLocks::Resource.possible_host_identifiers(uniq_identity)
|
24
|
+
probed_hosts = {}
|
25
|
+
|
26
|
+
redis_client.with do |rconn|
|
27
|
+
possible_hosts.each do |host_id|
|
28
|
+
rconn.call(
|
29
|
+
'HSET',
|
30
|
+
RedisQueuedLocks::Resource::SWARM_KEY,
|
31
|
+
host_id,
|
32
|
+
probe_score = Time.now.to_f
|
33
|
+
)
|
34
|
+
probed_hosts[host_id] = probe_score
|
35
|
+
end
|
36
|
+
|
37
|
+
RedisQueuedLocks::Data[ok: true, result: probed_hosts]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
# @since 1.9.0
|
46
|
+
def enabled?
|
47
|
+
rql_client.config[:swarm][:probe_hosts][:enabled_for_swarm]
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Thread]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
# @since 1.9.0
|
54
|
+
def spawn_main_loop!
|
55
|
+
Thread.new do
|
56
|
+
redis_client = RedisQueuedLocks::Swarm::RedisClientBuilder.build(
|
57
|
+
pooled: rql_client.config[:swarm][:probe_hosts][:redis_config][:pooled],
|
58
|
+
sentinel: rql_client.config[:swarm][:probe_hosts][:redis_config][:sentinel],
|
59
|
+
config: rql_client.config[:swarm][:probe_hosts][:redis_config][:config],
|
60
|
+
pool_config: rql_client.config[:swarm][:probe_hosts][:redis_config][:pool_config]
|
61
|
+
)
|
62
|
+
|
63
|
+
loop do
|
64
|
+
RedisQueuedLocks::Swarm::ProbeHosts.probe_hosts(redis_client, rql_client.uniq_identity)
|
65
|
+
sleep(rql_client.config[:swarm][:probe_hosts][:probe_period])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.9.0
|
5
|
+
module RedisQueuedLocks::Swarm::RedisClientBuilder
|
6
|
+
class << self
|
7
|
+
# @option pooled [Boolean]
|
8
|
+
# @option sentinel [Boolean]
|
9
|
+
# @option config [Hash]
|
10
|
+
# @return [RedisClient]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 1.9.0
|
14
|
+
# rubocop:disable Style/RedundantAssignment
|
15
|
+
def build(pooled: false, sentinel: false, config: {}, pool_config: {})
|
16
|
+
config.transform_keys!(&:to_sym)
|
17
|
+
pool_config.transform_keys!(&:to_sym)
|
18
|
+
|
19
|
+
redis_config =
|
20
|
+
sentinel ? sentinel_config(config) : non_sentinel_config(config)
|
21
|
+
redis_client =
|
22
|
+
pooled ? pooled_client(redis_config, pool_config) : non_pooled_client(redis_config)
|
23
|
+
|
24
|
+
redis_client
|
25
|
+
end
|
26
|
+
# rubocop:enable Style/RedundantAssignment
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# @param config [Hash]
|
31
|
+
# @return [RedisClient::Config]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
# @since 1.9.0
|
35
|
+
def sentinel_config(config)
|
36
|
+
RedisClient.sentinel(**config)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param config [Hash]
|
40
|
+
# @return [RedisClient::Config]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
# @since 1.9.0
|
44
|
+
def non_sentinel_config(config)
|
45
|
+
RedisClient.config(**config)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param redis_config [ReidsClient::Config]
|
49
|
+
# @param pool_config [Hash]
|
50
|
+
# @return [RedisClient]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
# @since 1.9.0
|
54
|
+
def pooled_client(redis_config, pool_config)
|
55
|
+
redis_config.new_pool(**pool_config)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param redis_config [ReidsClient::Config]
|
59
|
+
# @return [RedisClient]
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
# @since 1.9.0
|
63
|
+
def non_pooled_client(redis_config)
|
64
|
+
redis_config.new_client
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
# @since 1.9.0
|
5
|
+
class RedisQueuedLocks::Swarm::Supervisor
|
6
|
+
# @since 1.9.0
|
7
|
+
include RedisQueuedLocks::Utilities
|
8
|
+
|
9
|
+
# @return [RedisQueuedLocks::Client]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
# @since 1.9.0
|
13
|
+
attr_reader :rql_client
|
14
|
+
|
15
|
+
# @return [Thread,NilClass]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
# @since 1.9.0
|
19
|
+
attr_reader :visor
|
20
|
+
|
21
|
+
# @return [Proc,NilClass]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
# @since 1.9.0
|
25
|
+
attr_reader :observable
|
26
|
+
|
27
|
+
# @return [void]
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
# @since 1.9.0
|
31
|
+
def initialize(rql_client)
|
32
|
+
@rql_client = rql_client
|
33
|
+
@visor = nil
|
34
|
+
@observable = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param observable [Block]
|
38
|
+
# @return [void]
|
39
|
+
#
|
40
|
+
# @api private
|
41
|
+
# @since 1.9.0
|
42
|
+
def observe!(&observable)
|
43
|
+
@observable = observable
|
44
|
+
@visor = Thread.new do
|
45
|
+
loop do
|
46
|
+
yield rescue nil # TODO/CHECK: may be we need to process exceptions here
|
47
|
+
sleep(rql_client.config[:swarm][:supervisor][:liveness_probing_period])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
# NOTE: need to give a timespot to initialize visor thread;
|
51
|
+
sleep(0.1)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Boolean]
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
# @since 1.9.0
|
58
|
+
def running?
|
59
|
+
visor != nil && visor.alive?
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [void]
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
# @since 1.9.0
|
66
|
+
def stop!
|
67
|
+
visor.kill if running?
|
68
|
+
@visor = nil
|
69
|
+
@observable = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Hash<Symbol|Hash<Symbol,String|Boolean>>]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
# @since 1.9.0
|
76
|
+
def status
|
77
|
+
{
|
78
|
+
running: running?,
|
79
|
+
state: (visor == nil) ? 'non_initialized' : thread_state(visor),
|
80
|
+
observable: (observable == nil) ? 'non_initialized' : 'initialized'
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|