rsmp 0.48.3 → 0.51.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/CHANGELOG.md +6 -0
- data/Gemfile.lock +19 -20
- data/README.md +27 -3
- data/Rakefile +3 -1
- data/documentation/cli.md +79 -4
- data/documentation/collecting_message.md +90 -170
- data/documentation/message_distribution.md +17 -3
- data/documentation/tasks.md +65 -117
- data/lib/rsmp/cli.rb +93 -5
- data/lib/rsmp/collect/collector.rb +117 -134
- data/lib/rsmp/collect/distributor.rb +16 -3
- data/lib/rsmp/collect/queue.rb +14 -6
- data/lib/rsmp/collect/receiver.rb +1 -1
- data/lib/rsmp/collect/state_collector.rb +11 -2
- data/lib/rsmp/collection.rb +12 -0
- data/lib/rsmp/completion.rb +49 -0
- data/lib/rsmp/component/component_base.rb +3 -3
- data/lib/rsmp/convert/export/json_schema/index.rb +8 -3
- data/lib/rsmp/convert/import/yaml.rb +12 -8
- data/lib/rsmp/delivery.rb +18 -0
- data/lib/rsmp/event.rb +19 -0
- data/lib/rsmp/event_source.rb +36 -0
- data/lib/rsmp/exchange.rb +10 -0
- data/lib/rsmp/failure.rb +23 -0
- data/lib/rsmp/helpers/error.rb +14 -23
- data/lib/rsmp/message.rb +9 -7
- data/lib/rsmp/node/node.rb +3 -21
- data/lib/rsmp/node/site/connections.rb +4 -9
- data/lib/rsmp/node/site/site.rb +31 -16
- data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
- data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
- data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
- data/lib/rsmp/node/task.rb +59 -53
- data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
- data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
- data/lib/rsmp/proxy/modules/receive.rb +109 -66
- data/lib/rsmp/proxy/modules/send.rb +109 -51
- data/lib/rsmp/proxy/modules/state.rb +15 -5
- data/lib/rsmp/proxy/modules/tasks.rb +96 -70
- data/lib/rsmp/proxy/modules/versions.rb +8 -8
- data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
- data/lib/rsmp/proxy/proxy.rb +4 -66
- data/lib/rsmp/proxy/site/connection.rb +27 -22
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
- data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
- data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
- data/lib/rsmp/proxy/site/modules/status.rb +44 -19
- data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
- data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +34 -30
- data/lib/rsmp/result.rb +95 -0
- data/lib/rsmp/schema/validation.rb +5 -3
- data/lib/rsmp/sxl/interface.rb +8 -1
- data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
- data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
- data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
- data/lib/rsmp/sxl/processing/document.rb +158 -0
- data/lib/rsmp/sxl/processing/error.rb +8 -0
- data/lib/rsmp/sxl/processing/manifest.rb +144 -0
- data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
- data/lib/rsmp/sxl/processing/resolver.rb +94 -0
- data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
- data/lib/rsmp/sxl/processing.rb +18 -0
- data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
- data/lib/rsmp/termination.rb +10 -0
- data/lib/rsmp/tlc/modules/plans.rb +0 -2
- data/lib/rsmp/tlc/proxy/control.rb +50 -16
- data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
- data/lib/rsmp/tlc/proxy/io.rb +46 -31
- data/lib/rsmp/tlc/proxy/plans.rb +36 -19
- data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
- data/lib/rsmp/tlc/proxy/status.rb +47 -23
- data/lib/rsmp/tlc/proxy/system.rb +16 -4
- data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
- data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
- data/lib/rsmp/validation.rb +22 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +19 -4
- data/rsmp.gemspec +1 -1
- data/schemas/tlc/1.0.10/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.10/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.10/sxl_index.json +3 -3
- data/schemas/tlc/1.0.13/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.13/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.13/sxl_index.json +3 -3
- data/schemas/tlc/1.0.14/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.14/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.14/sxl_index.json +3 -3
- data/schemas/tlc/1.0.15/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.15/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.15/sxl_index.json +3 -3
- data/schemas/tlc/1.0.7/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.7/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.7/sxl_index.json +3 -3
- data/schemas/tlc/1.0.8/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.8/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.8/sxl_index.json +3 -3
- data/schemas/tlc/1.0.9/commands/M0001.json +3 -3
- data/schemas/tlc/1.0.9/source/sxl.yaml +3 -3
- data/schemas/tlc/1.0.9/sxl_index.json +3 -3
- metadata +25 -4
- 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
|
data/lib/rsmp/failure.rb
ADDED
|
@@ -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
|
data/lib/rsmp/helpers/error.rb
CHANGED
|
@@ -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 <
|
|
22
|
+
class InvalidMessage < PeerMessageError
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
class UnknownMessage < Error
|
|
20
26
|
end
|
|
21
27
|
|
|
22
|
-
class
|
|
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 <
|
|
43
|
+
class UnknownComponent < PeerMessageError
|
|
53
44
|
end
|
|
54
45
|
|
|
55
|
-
class UnknownCommand <
|
|
46
|
+
class UnknownCommand < PeerMessageError
|
|
56
47
|
end
|
|
57
48
|
|
|
58
|
-
class UnknownStatus <
|
|
49
|
+
class UnknownStatus < PeerMessageError
|
|
59
50
|
end
|
|
60
51
|
|
|
61
52
|
class ConfigurationError < Error
|
|
62
53
|
end
|
|
63
54
|
|
|
64
|
-
class RepeatedAlarmError <
|
|
55
|
+
class RepeatedAlarmError < PeerMessageError
|
|
65
56
|
end
|
|
66
57
|
|
|
67
|
-
class RepeatedStatusError <
|
|
58
|
+
class RepeatedStatusError < PeerMessageError
|
|
68
59
|
end
|
|
69
60
|
|
|
70
|
-
class TimestampError <
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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,12 @@ module RSMP
|
|
|
199
202
|
|
|
200
203
|
# Represents a malformed message with invalid attributes.
|
|
201
204
|
class Malformed < Message
|
|
202
|
-
# rubocop:disable Lint/MissingSuper
|
|
205
|
+
# rubocop:disable-next Lint/MissingSuper
|
|
203
206
|
def initialize(attributes = {})
|
|
204
207
|
# don't call super, just copy (potentially invalid) attributes
|
|
205
208
|
@attributes = {}
|
|
206
209
|
@invalid_attributes = attributes
|
|
207
210
|
end
|
|
208
|
-
# rubocop:enable Lint/MissingSuper
|
|
209
211
|
end
|
|
210
212
|
|
|
211
213
|
# Version message, lists supported versions and SXL information.
|
|
@@ -391,7 +393,7 @@ module RSMP
|
|
|
391
393
|
end
|
|
392
394
|
|
|
393
395
|
def original=(message)
|
|
394
|
-
raise
|
|
396
|
+
raise ArgumentError, 'original message is required' unless message
|
|
395
397
|
|
|
396
398
|
@original = message
|
|
397
399
|
end
|
data/lib/rsmp/node/node.rb
CHANGED
|
@@ -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, :
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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)
|
data/lib/rsmp/node/site/site.rb
CHANGED
|
@@ -106,13 +106,24 @@ module RSMP
|
|
|
106
106
|
|
|
107
107
|
def run
|
|
108
108
|
log_site_starting
|
|
109
|
-
|
|
109
|
+
barrier = Async::Barrier.new(parent: @task)
|
|
110
|
+
termination_task = barrier.async { wait_for_termination }
|
|
111
|
+
start_auxiliary_tasks(barrier)
|
|
110
112
|
if server_role?
|
|
111
|
-
listen_for_supervisors
|
|
113
|
+
listen_for_supervisors(parent: barrier)
|
|
112
114
|
else
|
|
113
|
-
@proxies.each(
|
|
114
|
-
@proxies.each(&:wait)
|
|
115
|
+
@proxies.each { |proxy| proxy.start(parent: barrier) }
|
|
115
116
|
end
|
|
117
|
+
barrier.wait do |finished|
|
|
118
|
+
result = finished.wait
|
|
119
|
+
break result if finished.equal?(termination_task)
|
|
120
|
+
end
|
|
121
|
+
ensure
|
|
122
|
+
barrier.cancel if barrier && !barrier.empty?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def start_auxiliary_tasks(parent)
|
|
126
|
+
start_status_timer(parent: parent)
|
|
116
127
|
end
|
|
117
128
|
|
|
118
129
|
def aggregated_status_changed(component, _options = {})
|
|
@@ -135,16 +146,16 @@ module RSMP
|
|
|
135
146
|
|
|
136
147
|
def send_alarm(alarm)
|
|
137
148
|
@proxies.each do |proxy|
|
|
138
|
-
proxy.
|
|
149
|
+
proxy.send_generated_message alarm if proxy.receive_alarms?
|
|
139
150
|
end
|
|
140
151
|
end
|
|
141
152
|
|
|
142
|
-
def start_status_timer
|
|
153
|
+
def start_status_timer(parent:)
|
|
143
154
|
return if @status_timer
|
|
144
155
|
|
|
145
156
|
interval = @site_settings['intervals']['timer'] || 1
|
|
146
157
|
log "Starting site status timer with interval #{interval} seconds", level: :debug
|
|
147
|
-
@status_timer =
|
|
158
|
+
@status_timer = parent.async do |task|
|
|
148
159
|
task.annotate 'site status timer'
|
|
149
160
|
run_status_timer task, interval
|
|
150
161
|
end
|
|
@@ -155,11 +166,8 @@ module RSMP
|
|
|
155
166
|
loop do
|
|
156
167
|
now = Clock.now
|
|
157
168
|
tick_status_subscriptions now
|
|
158
|
-
rescue StandardError => e
|
|
159
|
-
distribute_error e, level: :internal
|
|
160
|
-
ensure
|
|
161
169
|
next_time += interval
|
|
162
|
-
duration = next_time - Time.now.to_f
|
|
170
|
+
duration = [next_time - Time.now.to_f, 0].max
|
|
163
171
|
task.sleep duration
|
|
164
172
|
end
|
|
165
173
|
end
|
|
@@ -169,14 +177,14 @@ module RSMP
|
|
|
169
177
|
end
|
|
170
178
|
|
|
171
179
|
def stop_status_timer
|
|
172
|
-
@status_timer&.
|
|
180
|
+
@status_timer&.cancel if @status_timer&.running?
|
|
173
181
|
ensure
|
|
174
182
|
@status_timer = nil
|
|
175
183
|
end
|
|
176
184
|
|
|
177
185
|
def stop_subtasks
|
|
178
186
|
stop_status_timer
|
|
179
|
-
@accept_task&.
|
|
187
|
+
@accept_task&.cancel if @accept_task&.running?
|
|
180
188
|
@accept_task = nil
|
|
181
189
|
@endpoint = nil
|
|
182
190
|
super
|
|
@@ -190,11 +198,18 @@ module RSMP
|
|
|
190
198
|
|
|
191
199
|
def wait_for_supervisor(ip, timeout:)
|
|
192
200
|
supervisor = find_supervisor ip
|
|
193
|
-
return supervisor if supervisor
|
|
201
|
+
return Result.success(supervisor) if supervisor
|
|
194
202
|
|
|
195
203
|
wait_for_condition(@proxies_condition, timeout: timeout) { find_supervisor ip }
|
|
196
|
-
|
|
197
|
-
|
|
204
|
+
.map { |proxy| proxy }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def wait_for_supervisor!(ip, timeout:)
|
|
208
|
+
result = wait_for_supervisor(ip, timeout: timeout)
|
|
209
|
+
return result.value! if result.success?
|
|
210
|
+
|
|
211
|
+
failure = result.failure.with(message: "Supervisor '#{ip}' did not connect within #{timeout}s")
|
|
212
|
+
Result.failure(failure: failure).value!
|
|
198
213
|
end
|
|
199
214
|
|
|
200
215
|
def find_supervisor(ip)
|
|
@@ -15,15 +15,29 @@ module RSMP
|
|
|
15
15
|
reject_connection socket, info
|
|
16
16
|
end
|
|
17
17
|
rescue ConnectionError, HandshakeError => e
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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|
|
|
44
|
+
@endpoint.accept do |socket|
|
|
44
45
|
handle_connection(socket)
|
|
45
|
-
rescue StandardError => e
|
|
46
|
-
distribute_error e, level: :internal
|
|
47
46
|
end
|
|
48
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
67
|
-
@proxies.each(
|
|
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&.
|
|
95
|
+
@accept_task&.cancel if @accept_task&.running?
|
|
97
96
|
@accept_task = nil
|
|
98
97
|
|
|
99
98
|
@endpoint = nil
|
data/lib/rsmp/node/task.rb
CHANGED
|
@@ -1,95 +1,101 @@
|
|
|
1
1
|
module RSMP
|
|
2
|
-
|
|
3
|
-
end
|
|
4
|
-
|
|
5
|
-
# Task helpers for starting and managing an Async task lifecycle.
|
|
2
|
+
# Explicit ownership for a long-running Async task tree.
|
|
6
3
|
module Task
|
|
4
|
+
class ConditionWaitTimeout < Async::TimeoutError; end
|
|
5
|
+
|
|
7
6
|
attr_reader :task
|
|
8
7
|
|
|
9
8
|
def initialize_task
|
|
10
9
|
@task = nil
|
|
10
|
+
@termination_completion = Completion.new
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
def start
|
|
16
|
-
return if @task
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
else
|
|
28
|
-
Async do |task|
|
|
29
|
-
task.annotate "#{self.class.name} main task"
|
|
30
|
-
@task = task
|
|
31
|
-
run
|
|
32
|
-
stop_subtasks
|
|
33
|
-
@task = nil
|
|
34
|
-
end
|
|
13
|
+
# Start under an explicit Async parent. A current task is accepted for
|
|
14
|
+
# convenience, but this library never creates a hidden root reactor.
|
|
15
|
+
def start(parent: Async::Task.current)
|
|
16
|
+
return @task if @task&.running?
|
|
17
|
+
|
|
18
|
+
valid_parent = parent.respond_to?(:async) && (!parent.respond_to?(:running?) || parent.running?)
|
|
19
|
+
raise ArgumentError, 'an active Async parent task is required' unless valid_parent
|
|
20
|
+
|
|
21
|
+
child = parent.async do |task|
|
|
22
|
+
task.annotate "#{self.class.name} main task"
|
|
23
|
+
@task = task
|
|
24
|
+
run
|
|
25
|
+
ensure
|
|
26
|
+
stop_subtasks
|
|
35
27
|
end
|
|
36
|
-
|
|
28
|
+
@task = child
|
|
37
29
|
end
|
|
38
30
|
|
|
39
|
-
# initiate restart by raising a Restart exception
|
|
40
31
|
def restart
|
|
41
|
-
|
|
32
|
+
termination_completion.succeed(Termination.new(reason: :restart, source: self))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def wait_for_termination
|
|
36
|
+
termination_completion.wait
|
|
42
37
|
end
|
|
43
38
|
|
|
44
|
-
# get the status of our task, or nil of no task
|
|
45
39
|
def task_status
|
|
46
40
|
@task&.status
|
|
47
41
|
end
|
|
48
42
|
|
|
49
|
-
# perform any long-running work
|
|
50
|
-
# the method will be called from an async task, and should not return
|
|
51
|
-
# if subtasks are needed, the method should call wait() on each of them
|
|
52
|
-
# once running, ready() must be called
|
|
53
43
|
def run
|
|
54
44
|
start_subtasks
|
|
55
45
|
end
|
|
56
46
|
|
|
57
|
-
# wait
|
|
47
|
+
# Async::Task#wait raises unexpected child failures and returns nil for
|
|
48
|
+
# cancellation, matching Async's native task contract.
|
|
58
49
|
def wait
|
|
59
50
|
@task&.wait
|
|
60
51
|
end
|
|
61
52
|
|
|
62
|
-
# stop our task
|
|
63
53
|
def stop
|
|
64
|
-
|
|
65
|
-
stop_task if @task
|
|
54
|
+
stop_task
|
|
66
55
|
end
|
|
67
56
|
|
|
68
57
|
def stop_subtasks; end
|
|
69
58
|
|
|
70
|
-
# stop our task and any subtask
|
|
71
59
|
def stop_task
|
|
72
|
-
@task
|
|
73
|
-
|
|
60
|
+
task = @task
|
|
61
|
+
return unless task&.running?
|
|
62
|
+
|
|
63
|
+
task.cancel
|
|
64
|
+
task.wait unless task.current?
|
|
74
65
|
end
|
|
75
66
|
|
|
76
|
-
#
|
|
77
|
-
#
|
|
67
|
+
# Wait for an edge-triggered condition and return an expected timeout as a
|
|
68
|
+
# Result. Async cancellation is not rescued and therefore propagates.
|
|
78
69
|
def wait_for_condition(condition, timeout:, task: Async::Task.current, &block)
|
|
79
|
-
raise
|
|
70
|
+
raise ArgumentError, 'an active Async task is required' unless task&.running?
|
|
80
71
|
|
|
81
|
-
task.with_timeout(timeout) do
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
value = task.with_timeout(timeout, ConditionWaitTimeout) do
|
|
73
|
+
loop do
|
|
74
|
+
signalled = condition.wait
|
|
75
|
+
break signalled unless block
|
|
85
76
|
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
matched = yield(signalled)
|
|
78
|
+
break matched if matched
|
|
88
79
|
end
|
|
89
|
-
raise "Can't wait for condition because task #{task.object_id} #{task.annotation} is not running"
|
|
90
80
|
end
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
Result.success(value)
|
|
82
|
+
rescue ConditionWaitTimeout => e
|
|
83
|
+
Result.failure(
|
|
84
|
+
:timeout,
|
|
85
|
+
message: "Condition was not met within #{timeout}s",
|
|
86
|
+
source: :timeout,
|
|
87
|
+
cause: e
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def wait_for_condition!(...)
|
|
92
|
+
wait_for_condition(...).value!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def termination_completion
|
|
98
|
+
@termination_completion ||= Completion.new
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
end
|