redis_queued_locks 1.8.0 → 1.10.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 +66 -0
  4. data/README.md +528 -51
  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 +24 -12
  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 +246 -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