rsmp 0.48.4 → 0.51.1

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/Gemfile.lock +19 -20
  4. data/README.md +27 -3
  5. data/Rakefile +3 -1
  6. data/documentation/cli.md +79 -4
  7. data/documentation/collecting_message.md +90 -170
  8. data/documentation/configuration.md +8 -0
  9. data/documentation/message_distribution.md +17 -3
  10. data/documentation/tasks.md +65 -117
  11. data/lib/rsmp/cli.rb +93 -5
  12. data/lib/rsmp/collect/collector.rb +117 -134
  13. data/lib/rsmp/collect/distributor.rb +16 -3
  14. data/lib/rsmp/collect/queue.rb +14 -6
  15. data/lib/rsmp/collect/receiver.rb +1 -1
  16. data/lib/rsmp/collect/state_collector.rb +11 -2
  17. data/lib/rsmp/collection.rb +12 -0
  18. data/lib/rsmp/completion.rb +49 -0
  19. data/lib/rsmp/component/component_base.rb +3 -3
  20. data/lib/rsmp/convert/export/json_schema/index.rb +8 -4
  21. data/lib/rsmp/convert/import/yaml.rb +12 -8
  22. data/lib/rsmp/delivery.rb +18 -0
  23. data/lib/rsmp/event.rb +19 -0
  24. data/lib/rsmp/event_source.rb +36 -0
  25. data/lib/rsmp/exchange.rb +10 -0
  26. data/lib/rsmp/failure.rb +23 -0
  27. data/lib/rsmp/helpers/error.rb +14 -23
  28. data/lib/rsmp/message.rb +10 -9
  29. data/lib/rsmp/node/node.rb +3 -21
  30. data/lib/rsmp/node/site/connections.rb +4 -9
  31. data/lib/rsmp/node/site/site.rb +33 -17
  32. data/lib/rsmp/node/supervisor/modules/configuration.rb +24 -11
  33. data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
  34. data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
  35. data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
  36. data/lib/rsmp/node/task.rb +59 -53
  37. data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
  38. data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
  39. data/lib/rsmp/proxy/modules/receive.rb +109 -66
  40. data/lib/rsmp/proxy/modules/send.rb +109 -51
  41. data/lib/rsmp/proxy/modules/state.rb +15 -5
  42. data/lib/rsmp/proxy/modules/tasks.rb +96 -70
  43. data/lib/rsmp/proxy/modules/versions.rb +69 -19
  44. data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
  45. data/lib/rsmp/proxy/proxy.rb +7 -69
  46. data/lib/rsmp/proxy/site/connection.rb +27 -22
  47. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
  48. data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
  49. data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
  50. data/lib/rsmp/proxy/site/modules/status.rb +44 -19
  51. data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
  52. data/lib/rsmp/proxy/site/sxl_selection.rb +7 -2
  53. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
  54. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
  55. data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
  56. data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
  57. data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
  58. data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
  59. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +36 -31
  60. data/lib/rsmp/result.rb +95 -0
  61. data/lib/rsmp/schema/validation.rb +5 -3
  62. data/lib/rsmp/schema/version.rb +27 -0
  63. data/lib/rsmp/schema.rb +1 -0
  64. data/lib/rsmp/sxl/interface.rb +8 -1
  65. data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
  66. data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
  67. data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
  68. data/lib/rsmp/sxl/processing/document.rb +158 -0
  69. data/lib/rsmp/sxl/processing/error.rb +8 -0
  70. data/lib/rsmp/sxl/processing/manifest.rb +144 -0
  71. data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
  72. data/lib/rsmp/sxl/processing/resolver.rb +94 -0
  73. data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
  74. data/lib/rsmp/sxl/processing.rb +18 -0
  75. data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
  76. data/lib/rsmp/termination.rb +10 -0
  77. data/lib/rsmp/tlc/modules/plans.rb +0 -2
  78. data/lib/rsmp/tlc/modules/system.rb +2 -2
  79. data/lib/rsmp/tlc/proxy/control.rb +50 -16
  80. data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
  81. data/lib/rsmp/tlc/proxy/io.rb +46 -31
  82. data/lib/rsmp/tlc/proxy/plans.rb +36 -19
  83. data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
  84. data/lib/rsmp/tlc/proxy/status.rb +47 -23
  85. data/lib/rsmp/tlc/proxy/system.rb +16 -4
  86. data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
  87. data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
  88. data/lib/rsmp/validation.rb +22 -0
  89. data/lib/rsmp/version.rb +1 -1
  90. data/lib/rsmp.rb +19 -4
  91. data/rsmp.gemspec +1 -1
  92. metadata +26 -4
  93. data/lib/rsmp/collect/collector/status.rb +0 -34
@@ -0,0 +1,36 @@
1
+ module RSMP
2
+ # Reactor-confined, ordered delivery of background lifecycle events.
3
+ module EventSource
4
+ attr_reader :event_receivers
5
+
6
+ def initialize_event_source
7
+ @event_receivers = []
8
+ end
9
+
10
+ def add_event_receiver(receiver)
11
+ raise ArgumentError, 'event receiver must respond to receive_event' unless receiver.respond_to?(:receive_event)
12
+
13
+ @event_receivers << receiver unless @event_receivers.include?(receiver)
14
+ receiver
15
+ end
16
+
17
+ def remove_event_receiver(receiver)
18
+ @event_receivers.delete(receiver)
19
+ receiver
20
+ end
21
+
22
+ def publish_event(event)
23
+ raise ArgumentError, 'event must be an RSMP::Event' unless event.is_a?(Event)
24
+
25
+ @event_receivers.dup.each do |receiver|
26
+ receiver.receive_event(event)
27
+ rescue StandardError => e
28
+ remove_event_receiver(receiver)
29
+ raise unless receiver.respond_to?(:crash)
30
+
31
+ receiver.crash(e)
32
+ end
33
+ event
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ require 'forwardable'
2
+
3
+ module RSMP
4
+ # A sent request and the immutable collection produced in response.
5
+ Exchange = Data.define(:request, :collection) do
6
+ extend Forwardable
7
+
8
+ def_delegators :collection, :messages, :reached, :matcher_status
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ module RSMP
2
+ # An expected operational failure. Failures are values: they describe why an
3
+ # operation could not complete without changing Async task control flow.
4
+ Failure = Data.define(:code, :message, :source, :context, :cause) do
5
+ def initialize(code:, message:, source:, context: {}, cause: nil)
6
+ raise ArgumentError, 'failure code must be a Symbol' unless code.is_a?(Symbol)
7
+ raise ArgumentError, 'failure source must be a Symbol' unless source.is_a?(Symbol)
8
+ raise ArgumentError, 'failure context must be a Hash' unless context.is_a?(Hash)
9
+
10
+ super(
11
+ code: code,
12
+ message: message.to_s.freeze,
13
+ source: source,
14
+ context: context.dup.freeze,
15
+ cause: cause
16
+ )
17
+ end
18
+
19
+ def to_s
20
+ message.empty? ? code.to_s : message
21
+ end
22
+ end
23
+ end
@@ -8,24 +8,24 @@ module RSMP
8
8
  class MalformedMessage < Error
9
9
  end
10
10
 
11
+ # Semantic failures which may be raised while processing a schema-valid peer
12
+ # message. The receive boundary converts only this explicit family to a peer
13
+ # Failure; unrelated exceptions retain their original stack trace.
14
+ class PeerMessageError < Error
15
+ end
16
+
11
17
  # Raised when schema validation fails.
12
18
  class SchemaError < Error
13
19
  attr_accessor :schemas
14
20
  end
15
21
 
16
- class InvalidMessage < Error
22
+ class InvalidMessage < PeerMessageError
17
23
  end
18
24
 
19
25
  class UnknownMessage < Error
20
26
  end
21
27
 
22
- class MissingAcknowledgment < Error
23
- end
24
-
25
- class MissingWatchdog < Error
26
- end
27
-
28
- class MessageRejected < Error
28
+ class MessageRejected < PeerMessageError
29
29
  end
30
30
 
31
31
  class MissingAttribute < InvalidMessage
@@ -37,36 +37,27 @@ module RSMP
37
37
  class HandshakeError < FatalError
38
38
  end
39
39
 
40
- class NotReady < Error
41
- end
42
-
43
- class TimeoutError < Error
44
- end
45
-
46
- class DisconnectError < Error
47
- end
48
-
49
40
  class ConnectionError < Error
50
41
  end
51
42
 
52
- class UnknownComponent < Error
43
+ class UnknownComponent < PeerMessageError
53
44
  end
54
45
 
55
- class UnknownCommand < Error
46
+ class UnknownCommand < PeerMessageError
56
47
  end
57
48
 
58
- class UnknownStatus < Error
49
+ class UnknownStatus < PeerMessageError
59
50
  end
60
51
 
61
52
  class ConfigurationError < Error
62
53
  end
63
54
 
64
- class RepeatedAlarmError < Error
55
+ class RepeatedAlarmError < PeerMessageError
65
56
  end
66
57
 
67
- class RepeatedStatusError < Error
58
+ class RepeatedStatusError < PeerMessageError
68
59
  end
69
60
 
70
- class TimestampError < Error
61
+ class TimestampError < PeerMessageError
71
62
  end
72
63
  end
data/lib/rsmp/message.rb CHANGED
@@ -164,11 +164,14 @@ module RSMP
164
164
  end
165
165
 
166
166
  def validate(schemas)
167
- errors = RSMP::Schema.validate attributes, schemas
168
- return unless errors
167
+ RSMP::Schema.validate attributes, schemas
168
+ end
169
+
170
+ def validate!(schemas)
171
+ validation = validate(schemas)
172
+ return self if validation.valid?
169
173
 
170
- error_string = errors.map { |item| item.reject { |e| e == '' } }.compact.join(', ').strip
171
- err = SchemaError.new error_string.to_s
174
+ err = SchemaError.new validation.message
172
175
  err.schemas = schemas
173
176
  raise err
174
177
  end
@@ -199,13 +202,11 @@ module RSMP
199
202
 
200
203
  # Represents a malformed message with invalid attributes.
201
204
  class Malformed < Message
202
- # rubocop:disable Lint/MissingSuper
203
205
  def initialize(attributes = {})
204
- # don't call super, just copy (potentially invalid) attributes
205
- @attributes = {}
206
+ super({})
207
+ @attributes.clear
206
208
  @invalid_attributes = attributes
207
209
  end
208
- # rubocop:enable Lint/MissingSuper
209
210
  end
210
211
 
211
212
  # Version message, lists supported versions and SXL information.
@@ -391,7 +392,7 @@ module RSMP
391
392
  end
392
393
 
393
394
  def original=(message)
394
- raise InvalidArgument unless message
395
+ raise ArgumentError, 'original message is required' unless message
395
396
 
396
397
  @original = message
397
398
  end
@@ -3,16 +3,16 @@ module RSMP
3
3
  class Node
4
4
  include Logging
5
5
  include Task
6
+ include EventSource
6
7
 
7
- attr_reader :archive, :logger, :task, :deferred, :error_queue, :clock, :collector
8
+ attr_reader :archive, :logger, :task, :deferred, :clock, :collector
8
9
 
9
10
  def initialize(options = {})
10
11
  initialize_logging options
11
12
  initialize_task
13
+ initialize_event_source
12
14
  @deferred = []
13
15
  @clock = Clock.new
14
- @error_queue = Async::Queue.new
15
- @ignore_errors = []
16
16
  @collect = options[:collect]
17
17
  end
18
18
 
@@ -31,24 +31,6 @@ module RSMP
31
31
  super
32
32
  end
33
33
 
34
- def ignore_errors(classes)
35
- was = @ignore_errors
36
- @ignore_errors = [classes].flatten
37
- yield
38
- ensure
39
- @ignore_errors = was
40
- end
41
-
42
- def distribute_error(error, options = {})
43
- return if @ignore_errors.find { |klass| error.is_a? klass }
44
-
45
- if options[:level] == :internal
46
- log ["#{error} in task: #{Async::Task.current}", error.backtrace].flatten.join("\n"),
47
- level: :error
48
- end
49
- @error_queue.enqueue error
50
- end
51
-
52
34
  def defer(key, item = nil)
53
35
  @deferred << [key, item]
54
36
  end
@@ -9,30 +9,25 @@ module RSMP
9
9
  end
10
10
  end
11
11
 
12
- def listen_for_supervisors
12
+ def listen_for_supervisors(parent:)
13
13
  ip = @site_settings['ip'] || '0.0.0.0'
14
14
  port = @site_settings['port']
15
15
  log "Starting #{site_type_name} listener on #{ip}:#{port}", level: :info, timestamp: @clock.now
16
16
  @endpoint = IO::Endpoint.tcp(ip, port)
17
- @accept_task = Async::Task.current.async do |task|
17
+ @accept_task = parent.async do |task|
18
18
  task.annotate 'site accept loop'
19
19
  accept_supervisor_connections
20
20
  end
21
21
 
22
22
  @ready_condition.signal
23
- @accept_task.wait
23
+ @accept_task
24
24
  end
25
25
 
26
26
  def accept_supervisor_connections
27
27
  @endpoint.accept do |socket|
28
28
  accept_supervisor_connection socket
29
- rescue StandardError => e
30
- distribute_error e, level: :internal
31
29
  end
32
- rescue Async::Stop
33
- # Expected during shutdown - no action needed
34
- rescue StandardError => e
35
- distribute_error e, level: :internal
30
+ Async::Task.current.wait_all
36
31
  end
37
32
 
38
33
  def accept_supervisor_connection(socket)
@@ -81,8 +81,9 @@ module RSMP
81
81
  def check_core_versions
82
82
  version = @site_settings['core_version']
83
83
  return unless version
84
+ return if %w[all latest].include? version
84
85
 
85
- return if RSMP::Schema.core_versions.include? version
86
+ return if RSMP::Schema.normalize_core_version version
86
87
 
87
88
  error_str = "Unknown core version: #{version}"
88
89
  raise RSMP::ConfigurationError, error_str
@@ -106,13 +107,24 @@ module RSMP
106
107
 
107
108
  def run
108
109
  log_site_starting
109
- start_status_timer
110
+ barrier = Async::Barrier.new(parent: @task)
111
+ termination_task = barrier.async { wait_for_termination }
112
+ start_auxiliary_tasks(barrier)
110
113
  if server_role?
111
- listen_for_supervisors
114
+ listen_for_supervisors(parent: barrier)
112
115
  else
113
- @proxies.each(&:start)
114
- @proxies.each(&:wait)
116
+ @proxies.each { |proxy| proxy.start(parent: barrier) }
115
117
  end
118
+ barrier.wait do |finished|
119
+ result = finished.wait
120
+ break result if finished.equal?(termination_task)
121
+ end
122
+ ensure
123
+ barrier.cancel if barrier && !barrier.empty?
124
+ end
125
+
126
+ def start_auxiliary_tasks(parent)
127
+ start_status_timer(parent: parent)
116
128
  end
117
129
 
118
130
  def aggregated_status_changed(component, _options = {})
@@ -135,16 +147,16 @@ module RSMP
135
147
 
136
148
  def send_alarm(alarm)
137
149
  @proxies.each do |proxy|
138
- proxy.send_message alarm if proxy.receive_alarms?
150
+ proxy.send_generated_message alarm if proxy.receive_alarms?
139
151
  end
140
152
  end
141
153
 
142
- def start_status_timer
154
+ def start_status_timer(parent:)
143
155
  return if @status_timer
144
156
 
145
157
  interval = @site_settings['intervals']['timer'] || 1
146
158
  log "Starting site status timer with interval #{interval} seconds", level: :debug
147
- @status_timer = @task.async do |task|
159
+ @status_timer = parent.async do |task|
148
160
  task.annotate 'site status timer'
149
161
  run_status_timer task, interval
150
162
  end
@@ -155,11 +167,8 @@ module RSMP
155
167
  loop do
156
168
  now = Clock.now
157
169
  tick_status_subscriptions now
158
- rescue StandardError => e
159
- distribute_error e, level: :internal
160
- ensure
161
170
  next_time += interval
162
- duration = next_time - Time.now.to_f
171
+ duration = [next_time - Time.now.to_f, 0].max
163
172
  task.sleep duration
164
173
  end
165
174
  end
@@ -169,14 +178,14 @@ module RSMP
169
178
  end
170
179
 
171
180
  def stop_status_timer
172
- @status_timer&.stop
181
+ @status_timer&.cancel if @status_timer&.running?
173
182
  ensure
174
183
  @status_timer = nil
175
184
  end
176
185
 
177
186
  def stop_subtasks
178
187
  stop_status_timer
179
- @accept_task&.stop
188
+ @accept_task&.cancel if @accept_task&.running?
180
189
  @accept_task = nil
181
190
  @endpoint = nil
182
191
  super
@@ -190,11 +199,18 @@ module RSMP
190
199
 
191
200
  def wait_for_supervisor(ip, timeout:)
192
201
  supervisor = find_supervisor ip
193
- return supervisor if supervisor
202
+ return Result.success(supervisor) if supervisor
194
203
 
195
204
  wait_for_condition(@proxies_condition, timeout: timeout) { find_supervisor ip }
196
- rescue Async::TimeoutError
197
- raise RSMP::TimeoutError, "Supervisor '#{ip}' did not connect within #{timeout}s"
205
+ .map { |proxy| proxy }
206
+ end
207
+
208
+ def wait_for_supervisor!(ip, timeout:)
209
+ result = wait_for_supervisor(ip, timeout: timeout)
210
+ return result.value! if result.success?
211
+
212
+ failure = result.failure.with(message: "Supervisor '#{ip}' did not connect within #{timeout}s")
213
+ Result.failure(failure: failure).value!
198
214
  end
199
215
 
200
216
  def find_supervisor(ip)
@@ -35,23 +35,36 @@ module RSMP
35
35
  sites.each do |site_id, settings|
36
36
  raise RSMP::ConfigurationError, "Configuration for site '#{site_id}' is empty" unless settings
37
37
 
38
- sxls = settings['sxls']
39
- raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': No SXLs specified" unless sxls
38
+ check_configured_sxls site_id, settings['sxls']
39
+ check_configured_core_version site_id, settings['core_version']
40
+ rescue RSMP::Schema::UnknownSchemaError => e
41
+ raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': #{e}"
42
+ end
43
+ end
40
44
 
41
- sxls.each do |sxl|
42
- name = sxl['name']
43
- if name.to_s == 'core'
44
- raise RSMP::ConfigurationError,
45
- "Configuration error for site '#{site_id}': SXL name cannot be core"
46
- end
45
+ def check_configured_sxls(site_id, sxls)
46
+ raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': No SXLs specified" unless sxls
47
47
 
48
- RSMP::Schema.find_schema! name, sxl['version'], lenient: true
48
+ sxls.each do |sxl|
49
+ name = sxl['name']
50
+ if name.to_s == 'core'
51
+ raise RSMP::ConfigurationError,
52
+ "Configuration error for site '#{site_id}': SXL name cannot be core"
49
53
  end
50
- rescue RSMP::Schema::UnknownSchemaError => e
51
- raise RSMP::ConfigurationError, "Configuration error for site '#{site_id}': #{e}"
54
+
55
+ RSMP::Schema.find_schema! name, sxl['version'], lenient: true
52
56
  end
53
57
  end
54
58
 
59
+ def check_configured_core_version(site_id, core_version)
60
+ return unless core_version
61
+ return if %w[all latest].include? core_version
62
+ return if RSMP::Schema.normalize_core_version core_version
63
+
64
+ raise RSMP::ConfigurationError,
65
+ "Configuration error for site '#{site_id}': Unknown core version: #{core_version}"
66
+ end
67
+
55
68
  def site_id_to_site_setting(site_id)
56
69
  base = @supervisor_settings['default'] || {}
57
70
 
@@ -15,15 +15,29 @@ module RSMP
15
15
  reject_connection socket, info
16
16
  end
17
17
  rescue ConnectionError, HandshakeError => e
18
- log "Rejected connection from #{remote_ip}:#{remote_port}, #{e}", level: :warning
19
- distribute_error e
20
- rescue StandardError => e
21
- log "Connection: #{e}", exception: e, level: :error
22
- distribute_error e, level: :internal
18
+ report_connection_rejection(e, remote_ip, remote_port)
23
19
  ensure
24
20
  close socket, info
25
21
  end
26
22
 
23
+ def report_connection_rejection(error, remote_ip, remote_port)
24
+ log "Rejected connection from #{remote_ip}:#{remote_port}, #{error}", level: :warning
25
+ failure = Failure.new(
26
+ code: error.is_a?(HandshakeError) ? :handshake_failed : :connection_rejected,
27
+ message: error.message,
28
+ source: :peer,
29
+ context: { ip: remote_ip, port: remote_port }
30
+ )
31
+ publish_event(
32
+ Event.new(
33
+ type: :connection_rejected,
34
+ source: self,
35
+ failure: failure,
36
+ context: { ip: remote_ip, port: remote_port }
37
+ )
38
+ )
39
+ end
40
+
27
41
  def accept?(_socket, _info)
28
42
  true
29
43
  end
@@ -23,24 +23,35 @@ module RSMP
23
23
 
24
24
  def wait_for_site(site_id, timeout:)
25
25
  site = find_site site_id
26
- return site if site
26
+ return Result.success(site) if site
27
27
 
28
28
  wait_for_condition(@site_id_condition, timeout: timeout) do
29
29
  find_site site_id
30
30
  end
31
- rescue Async::TimeoutError
31
+ end
32
+
33
+ def wait_for_site!(site_id, timeout:)
34
+ result = wait_for_site(site_id, timeout: timeout)
35
+ return result.value! if result.success?
36
+
32
37
  str = if site_id == :any
33
38
  'No site connected'
34
39
  else
35
40
  "Site '#{site_id}' did not connect"
36
41
  end
37
- raise RSMP::TimeoutError, "#{str} within #{timeout}s"
42
+ Result.failure(failure: result.failure.with(message: "#{str} within #{timeout}s")).value!
38
43
  end
39
44
 
40
45
  def wait_for_site_disconnect(site_id, timeout:)
41
46
  wait_for_condition(@site_id_condition, timeout: timeout) { true unless find_site site_id }
42
- rescue Async::TimeoutError
43
- raise RSMP::TimeoutError, "Site '#{site_id}' did not disconnect within #{timeout}s"
47
+ end
48
+
49
+ def wait_for_site_disconnect!(site_id, timeout:)
50
+ result = wait_for_site_disconnect(site_id, timeout: timeout)
51
+ return result.value! if result.success?
52
+
53
+ failure = result.failure.with(message: "Site '#{site_id}' did not disconnect within #{timeout}s")
54
+ Result.failure(failure: failure).value!
44
55
  end
45
56
 
46
57
  def check_site_id(site_id)
@@ -38,23 +38,19 @@ module RSMP
38
38
  timestamp: @clock.now
39
39
 
40
40
  @endpoint = IO::Endpoint.tcp('0.0.0.0', @supervisor_settings['port'])
41
- @accept_task = Async::Task.current.async do |task|
41
+ barrier = Async::Barrier.new(parent: @task)
42
+ @accept_task = barrier.async do |task|
42
43
  task.annotate 'supervisor accept loop'
43
- @endpoint.accept do |socket| # creates fibers
44
+ @endpoint.accept do |socket|
44
45
  handle_connection(socket)
45
- rescue StandardError => e
46
- distribute_error e, level: :internal
47
46
  end
48
- rescue Async::Stop
49
- # Expected during shutdown - no action needed
50
- rescue StandardError => e
51
- distribute_error e, level: :internal
47
+ task.wait_all
52
48
  end
53
49
 
54
50
  @ready_condition.signal
55
- @accept_task.wait
56
- rescue StandardError => e
57
- distribute_error e, level: :internal
51
+ barrier.wait
52
+ ensure
53
+ barrier.cancel if barrier && !barrier.empty?
58
54
  end
59
55
 
60
56
  def connect_to_sites
@@ -63,8 +59,11 @@ module RSMP
63
59
  timestamp: @clock.now
64
60
  build_outbound_proxies
65
61
  @ready_condition.signal
66
- @proxies.each(&:start)
67
- @proxies.each(&:wait)
62
+ barrier = Async::Barrier.new(parent: @task)
63
+ @proxies.each { |proxy| proxy.start(parent: barrier) }
64
+ barrier.wait
65
+ ensure
66
+ barrier.cancel if barrier && !barrier.empty?
68
67
  end
69
68
 
70
69
  def build_outbound_proxies
@@ -93,7 +92,7 @@ module RSMP
93
92
  def stop
94
93
  log "Stopping supervisor #{@supervisor_settings['site_id']}", level: :info
95
94
 
96
- @accept_task&.stop
95
+ @accept_task&.cancel if @accept_task&.running?
97
96
  @accept_task = nil
98
97
 
99
98
  @endpoint = nil