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,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
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 1.9.0
5
+ # rubocop:disable Metrics/ClassLength
6
+ class RedisQueuedLocks::Swarm::SwarmElement::Isolated
7
+ # @since 1.9.0
8
+ include RedisQueuedLocks::Utilities
9
+
10
+ # @return [RedisQueuedLocks::Client]
11
+ #
12
+ # @api private
13
+ # @since 1.9.0
14
+ attr_reader :rql_client
15
+
16
+ # @return [Ractor,NilClass]
17
+ #
18
+ # @api private
19
+ # @since 1.9.0
20
+ attr_reader :swarm_element
21
+
22
+ # @return [RedisQueuedLocks::Utilities::Lock]
23
+ #
24
+ # @api private
25
+ # @since 1.9.0
26
+ attr_reader :sync
27
+
28
+ # @return [void]
29
+ #
30
+ # @api private
31
+ # @since 1.9.0
32
+ def initialize(rql_client)
33
+ @rql_client = rql_client
34
+ @swarm_element = nil
35
+ @sync = RedisQueuedLocks::Utilities::Lock.new
36
+ end
37
+
38
+ # @return [void]
39
+ #
40
+ # @api private
41
+ # @since 1.9.0
42
+ def try_swarm!
43
+ return unless enabled?
44
+
45
+ sync.synchronize do
46
+ swarm_loop__kill
47
+ swarm!
48
+ swarm_loop__start
49
+ end
50
+ end
51
+
52
+ # @return [void]
53
+ #
54
+ # @api private
55
+ # @since 1.9.0
56
+ def reswarm_if_dead!
57
+ return unless enabled?
58
+
59
+ sync.synchronize do
60
+ if swarmed__stopped?
61
+ swarm_loop__start
62
+ elsif swarmed__dead? || idle?
63
+ swarm!
64
+ swarm_loop__start
65
+ end
66
+ end
67
+ end
68
+
69
+ # @return [void]
70
+ #
71
+ # @api private
72
+ # @since 1.9.0
73
+ def try_kill!
74
+ sync.synchronize do
75
+ swarm_loop__kill
76
+ end
77
+ end
78
+
79
+ # @return [Boolean]
80
+ #
81
+ # @api private
82
+ # @since 1.9.0
83
+ def enabled?
84
+ # NOTE: provide an <is enabled> logic here by analyzing the redis queued locks config.
85
+ end
86
+
87
+ # @return [Hash<Symbol,Boolean|Hash<Symbol,String|Boolean>>]
88
+ # Format: {
89
+ # enabled: <Boolean>,
90
+ # ractor: {
91
+ # running: <Boolean>,
92
+ # state: <String>,
93
+ # },
94
+ # main_loop: {
95
+ # running: <Boolean>,
96
+ # state: <String>
97
+ # }
98
+ # }
99
+ #
100
+ # @api private
101
+ # @since 1.9.0
102
+ def status
103
+ sync.synchronize do
104
+ ractor_running = swarmed__alive?
105
+ ractor_state = swarmed? ? ractor_status(swarm_element) : 'non_initialized'
106
+
107
+ main_loop_running = nil
108
+ main_loop_state = nil
109
+ begin
110
+ main_loop_running = swarmed__running?
111
+ main_loop_state =
112
+ main_loop_running ? swarm_loop__status[:main_loop][:state] : 'non_initialized'
113
+ rescue Ractor::ClosedError
114
+ # NOTE: it can happend when you run RedisQueuedLocks::Swarm#deswarm!;
115
+ main_loop_running = false
116
+ main_loop_state = 'non_initialized'
117
+ end
118
+
119
+ {
120
+ enabled: enabled?,
121
+ ractor: {
122
+ running: ractor_running,
123
+ state: ractor_state
124
+ },
125
+ main_loop: {
126
+ running: main_loop_running,
127
+ state: main_loop_state
128
+ }
129
+ }
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ # Swarm element lifecycle have the following scheme:
136
+ # => 1) init (swarm!): create a ractor, main loop is not started;
137
+ # => 2) start (swarm_loop__start!): run main lopp inside the ractor;
138
+ # => 3) stop (swarm_loop__stop!): stop the main loop inside a ractor;
139
+ # => 4) kill (swarm_loop__kill!): kill the main loop inside teh ractor and kill a ractor;
140
+ #
141
+ # @return [void]
142
+ #
143
+ # @api private
144
+ # @since 1.9.0
145
+ def swarm!
146
+ # IMPORTANT №1: initialize @swarm_element here with Ractor;
147
+ # IMPORTANT №2: your Ractor should invoke .swarm_loop inside (see below);
148
+ # IMPORTANT №3: you should pass the main loop logic as a block to .swarm_loop;
149
+ end
150
+
151
+ # @param main_loop_spawner [Block]
152
+ # @return [void]
153
+ #
154
+ # @api private
155
+ # @since 1.9.0
156
+ # rubocop:disable Layout/ClassStructure, Lint/IneffectiveAccessModifier
157
+ def self.swarm_loop(&main_loop_spawner)
158
+ # NOTE:
159
+ # This self.-related part of code is placed in the middle of class in order
160
+ # to provide better code readability (it is placed next to the method inside
161
+ # wich it should be called (see #swarm!)). That's why some rubocop cops are disabled.
162
+
163
+ main_loop = nil
164
+
165
+ loop do
166
+ command = Ractor.receive
167
+
168
+ case command
169
+ when :status
170
+ main_loop_alive = main_loop != nil && main_loop.alive?
171
+ main_loop_state =
172
+ if main_loop == nil
173
+ 'non_initialized'
174
+ else
175
+ # NOTE: (full name resolution): ractor has no syntax-based constatnt context;
176
+ RedisQueuedLocks::Utilities.thread_state(main_loop)
177
+ end
178
+ Ractor.yield({ main_loop: { alive: main_loop_alive, state: main_loop_state } })
179
+ when :is_active
180
+ Ractor.yield(main_loop != nil && main_loop.alive?)
181
+ when :start
182
+ main_loop.kill if main_loop != nil
183
+ main_loop = yield # REFERENCE: `main_loop_spawner.call`
184
+ when :stop
185
+ main_loop.kill if main_loop != nil
186
+ when :kill
187
+ main_loop.kill if main_loop != nil
188
+ exit
189
+ end
190
+ end
191
+ end
192
+ # rubocop:enable Layout/ClassStructure, Lint/IneffectiveAccessModifier
193
+
194
+ # @return [Boolean]
195
+ #
196
+ # @api private
197
+ # @since 1.9.0
198
+ def idle?
199
+ swarm_element == nil
200
+ end
201
+
202
+ # @return [Boolean]
203
+ #
204
+ # @api private
205
+ # @since 1.9.0
206
+ def swarmed?
207
+ swarm_element != nil
208
+ end
209
+
210
+ # @return [Boolean]
211
+ #
212
+ # @api private
213
+ # @since 1.9.0
214
+ def swarmed__alive?
215
+ swarm_element != nil && ractor_alive?(swarm_element)
216
+ end
217
+
218
+ # @return [Boolean]
219
+ #
220
+ # @api private
221
+ # @since 1.9.0
222
+ def swarmed__dead?
223
+ swarm_element != nil && !ractor_alive?(swarm_element)
224
+ end
225
+
226
+ # @return [Boolean]
227
+ #
228
+ # @api private
229
+ # @since 1.9.0
230
+ def swarmed__running?
231
+ swarm_element != nil && ractor_alive?(swarm_element) && swarm_loop__is_active
232
+ end
233
+
234
+ # @return [Boolean]
235
+ #
236
+ # @api private
237
+ # @since 1.9.0
238
+ def swarmed__stopped?
239
+ swarm_element != nil && ractor_alive?(swarm_element) && !swarm_loop__is_active
240
+ end
241
+
242
+ # @return [Boolean]
243
+ #
244
+ # @api private
245
+ # @since 1.9.0
246
+ def swarm_loop__is_active
247
+ return if idle? || swarmed__dead?
248
+ sync.synchronize { swarm_element.send(:is_active).take }
249
+ end
250
+
251
+ # @return [Hash]
252
+ #
253
+ # @api private
254
+ # @since 1.9.0
255
+ def swarm_loop__status
256
+ return if idle? || swarmed__dead?
257
+ sync.synchronize { swarm_element.send(:status).take }
258
+ end
259
+
260
+ # @return [void]
261
+ #
262
+ # @api private
263
+ # @since 1.9.0
264
+ def swarm_loop__start
265
+ return if idle? || swarmed__dead?
266
+ sync.synchronize { swarm_element.send(:start) }
267
+ end
268
+
269
+ # @return [void]
270
+ #
271
+ # @api private
272
+ # @since 1.9.0
273
+ def swarm_loop__pause
274
+ return if idle? || swarmed__dead?
275
+ sync.synchronize { swarm_element.send(:stop) }
276
+ end
277
+
278
+ # @return [void]
279
+ #
280
+ # @api private
281
+ # @since 1.9.0
282
+ def swarm_loop__kill
283
+ return if idle? || swarmed__dead?
284
+ sync.synchronize { swarm_element.send(:kill) }
285
+ end
286
+ end
287
+ # rubocop:enable Metrics/ClassLength