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
|
@@ -5,16 +5,16 @@ module RSMP
|
|
|
5
5
|
# Manages sending/receiving acks and nacks, and tracking acknowledged messages
|
|
6
6
|
module Acknowledgements
|
|
7
7
|
def acknowledge(original)
|
|
8
|
-
raise
|
|
8
|
+
raise ArgumentError, 'original message is required' unless original
|
|
9
9
|
|
|
10
10
|
ack = MessageAck.build_from(original)
|
|
11
11
|
ack.original = original.clone
|
|
12
|
-
|
|
12
|
+
send_generated_message ack, "for #{ack.original.type} #{original.m_id_short}"
|
|
13
13
|
check_ingoing_acknowledged original
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def dont_acknowledge(original, prefix = nil, reason = nil, force: true)
|
|
17
|
-
raise
|
|
17
|
+
raise ArgumentError, 'original message is required' unless original
|
|
18
18
|
|
|
19
19
|
str = [prefix, reason].join(' ')
|
|
20
20
|
log str, message: original, level: :warning if reason
|
|
@@ -23,7 +23,7 @@ module RSMP
|
|
|
23
23
|
'rea' => reason || 'Unknown reason'
|
|
24
24
|
})
|
|
25
25
|
message.original = original.clone
|
|
26
|
-
|
|
26
|
+
send_generated_message message, "for #{original.type} #{original.m_id_short}", force: force
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def expect_acknowledgement(message)
|
|
@@ -45,12 +45,25 @@ module RSMP
|
|
|
45
45
|
|
|
46
46
|
str = "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds"
|
|
47
47
|
log str, level: :error
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
failure = Failure.new(
|
|
49
|
+
code: :missing_acknowledgement,
|
|
50
|
+
message: str,
|
|
51
|
+
source: :peer,
|
|
52
|
+
context: { message: message, reason: :missing_acknowledgement, session_id: session_id }
|
|
53
|
+
)
|
|
54
|
+
distribute_event(
|
|
55
|
+
Event.new(
|
|
56
|
+
type: :missing_acknowledgement,
|
|
57
|
+
source: self,
|
|
58
|
+
session_id: session_id,
|
|
59
|
+
message: message,
|
|
60
|
+
failure: failure
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
return Result.failure(failure: failure)
|
|
53
64
|
end
|
|
65
|
+
|
|
66
|
+
Result.success
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
def find_original_for_message(message)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
class Proxy
|
|
3
|
+
module Modules
|
|
4
|
+
# Owns one connection session and translates expected termination into
|
|
5
|
+
# lifecycle events without swallowing unexpected task exceptions.
|
|
6
|
+
module Lifecycle
|
|
7
|
+
def disconnect
|
|
8
|
+
close(reason: :local_stop)
|
|
9
|
+
Result.success(self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def disconnect!
|
|
13
|
+
disconnect.value!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def connect!
|
|
17
|
+
connect.value!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wait_for_reader
|
|
21
|
+
wait_for_session
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def begin_session
|
|
25
|
+
clear
|
|
26
|
+
@session_id += 1
|
|
27
|
+
@session_closed = false
|
|
28
|
+
@session_id
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def close(reason: :peer_closed, failure: nil, session_id: @session_id)
|
|
32
|
+
return if session_id != @session_id || @session_closed
|
|
33
|
+
|
|
34
|
+
@session_closed = true
|
|
35
|
+
log 'Closing connection', level: :warning
|
|
36
|
+
close_stream
|
|
37
|
+
close_socket
|
|
38
|
+
stop_reader
|
|
39
|
+
self.state = :disconnected
|
|
40
|
+
publish_connection_end(reason, failure, session_id)
|
|
41
|
+
stop_timer
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stop_subtasks
|
|
45
|
+
stop_timer
|
|
46
|
+
stop_reader
|
|
47
|
+
clear
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def stop_timer
|
|
52
|
+
current = Async::Task.current?
|
|
53
|
+
@timer&.cancel unless @timer.equal?(current)
|
|
54
|
+
ensure
|
|
55
|
+
@timer = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def stop_reader
|
|
59
|
+
current = Async::Task.current?
|
|
60
|
+
@reader&.cancel unless @reader.equal?(current)
|
|
61
|
+
ensure
|
|
62
|
+
@reader = nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def close_stream
|
|
66
|
+
@stream&.close
|
|
67
|
+
ensure
|
|
68
|
+
@stream = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def close_socket
|
|
72
|
+
@socket&.close
|
|
73
|
+
ensure
|
|
74
|
+
@socket = nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def stop_task
|
|
78
|
+
close(reason: :local_stop)
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def distribute_event(event)
|
|
83
|
+
super
|
|
84
|
+
@node&.publish_event(event)
|
|
85
|
+
event
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def connection_end_failure(reason)
|
|
89
|
+
local = reason == :local_stop
|
|
90
|
+
Failure.new(
|
|
91
|
+
code: local ? :cancelled : :disconnected,
|
|
92
|
+
message: "Connection ended (#{reason})",
|
|
93
|
+
source: local ? :local : :connection,
|
|
94
|
+
context: { reason: reason, session_id: @session_id }
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def close_from_result(result)
|
|
99
|
+
return close(reason: :peer_closed) if result.nil? || result.success?
|
|
100
|
+
|
|
101
|
+
reason = result.failure.context[:reason] || :transport_failure
|
|
102
|
+
close(reason: reason, failure: result.failure)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def publish_connection_attempt_failure(failure)
|
|
106
|
+
log failure.message, level: :warning
|
|
107
|
+
distribute_event(
|
|
108
|
+
Event.new(
|
|
109
|
+
type: :connection_attempt_failed,
|
|
110
|
+
source: self,
|
|
111
|
+
session_id: @session_id,
|
|
112
|
+
failure: failure
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def publish_connection_end(reason, failure, session_id)
|
|
120
|
+
failure ||= connection_end_failure(reason)
|
|
121
|
+
distribute_event(
|
|
122
|
+
Event.new(
|
|
123
|
+
type: :connection_ended,
|
|
124
|
+
source: self,
|
|
125
|
+
session_id: session_id,
|
|
126
|
+
failure: failure,
|
|
127
|
+
context: { reason: reason }
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
class Proxy
|
|
3
3
|
module Modules
|
|
4
|
-
# Message processing functionality
|
|
5
|
-
#
|
|
4
|
+
# Message processing functionality. Peer-controlled invalid input is
|
|
5
|
+
# represented by Result::Failure; unexpected implementation errors escape.
|
|
6
6
|
module Receive
|
|
7
7
|
def should_validate_ingoing_message?(message)
|
|
8
8
|
return false if message.is_a?(Version) && !@version_determined
|
|
9
|
-
|
|
10
9
|
return true unless @site_settings
|
|
11
10
|
|
|
12
11
|
skip = @site_settings['skip_validation']
|
|
@@ -24,93 +23,137 @@ module RSMP
|
|
|
24
23
|
expect_version_message(message) unless @version_determined
|
|
25
24
|
end
|
|
26
25
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
def process_packet(json)
|
|
27
|
+
attributes = Message.parse_attributes(json)
|
|
28
|
+
message = Message.build(attributes, json)
|
|
29
|
+
process_incoming_message(message)
|
|
30
|
+
rescue InvalidPacket => e
|
|
31
|
+
reject_invalid_packet(json, e)
|
|
32
|
+
rescue MalformedMessage => e
|
|
33
|
+
reject_malformed_packet(json, attributes, e)
|
|
34
|
+
rescue PeerMessageError => e
|
|
35
|
+
reject_processed_message(message, e)
|
|
36
|
+
rescue HandshakeError, FatalError => e
|
|
37
|
+
reject_fatal_message(message, e)
|
|
38
|
+
ensure
|
|
39
|
+
@node&.clear_deferred
|
|
32
40
|
end
|
|
33
41
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
def process_incoming_message(message)
|
|
43
|
+
validate = should_validate_ingoing_message?(message)
|
|
44
|
+
if validate
|
|
45
|
+
validation = message.validate(schemas)
|
|
46
|
+
return reject_invalid_message(message, validation) if validation.invalid?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
verify_sequence(message)
|
|
50
|
+
message.decode_for(schemas) if validate
|
|
51
|
+
with_deferred_distribution do
|
|
52
|
+
distribute(message)
|
|
53
|
+
process_message(message)
|
|
54
|
+
end
|
|
55
|
+
process_deferred
|
|
56
|
+
Result.success(message)
|
|
39
57
|
end
|
|
40
58
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
dont_acknowledge message, str, reason
|
|
48
|
-
message
|
|
59
|
+
def reject_invalid_packet(json, error)
|
|
60
|
+
reject_packet(
|
|
61
|
+
:invalid_packet,
|
|
62
|
+
"Received invalid packet, expected JSON but got #{json.size} bytes: #{error.message}",
|
|
63
|
+
raw: json
|
|
64
|
+
)
|
|
49
65
|
end
|
|
50
66
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
def reject_malformed_packet(json, attributes, error)
|
|
68
|
+
reject_packet(
|
|
69
|
+
:malformed_message,
|
|
70
|
+
"Received malformed message: #{error.message}",
|
|
71
|
+
raw: json,
|
|
72
|
+
message: Malformed.new(attributes || {})
|
|
73
|
+
)
|
|
57
74
|
end
|
|
58
75
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
def reject_invalid_message(message, validation)
|
|
77
|
+
schemas_string = schemas.map { |schema| "#{schema.first}: #{schema.last}" }.join(', ')
|
|
78
|
+
reason = "schema errors (#{schemas_string}): #{validation.message}"
|
|
79
|
+
text = "Received invalid #{message.type}"
|
|
80
|
+
failure = peer_failure(
|
|
81
|
+
:invalid_peer_message,
|
|
82
|
+
"#{text}: #{validation.message}",
|
|
83
|
+
message: message,
|
|
84
|
+
validation: validation
|
|
85
|
+
)
|
|
86
|
+
publish_peer_failure(failure, message: message)
|
|
87
|
+
log(text, message: message, level: :warning)
|
|
88
|
+
dont_acknowledge(message, text, reason)
|
|
89
|
+
Result.failure(failure: failure)
|
|
66
90
|
end
|
|
67
91
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
message
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
message
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
def reject_packet(code, text, raw:, message: nil)
|
|
93
|
+
failure = peer_failure(code, text, message: message, raw: raw)
|
|
94
|
+
publish_peer_failure(failure, message: message)
|
|
95
|
+
log(text, message: message, level: :warning)
|
|
96
|
+
Result.failure(failure: failure)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def reject_processed_message(message, error)
|
|
100
|
+
code = error.is_a?(MessageRejected) ? :message_rejected : :invalid_peer_message
|
|
101
|
+
text = "Received invalid #{message.type}: #{error.message}"
|
|
102
|
+
failure = peer_failure(code, text, message: message)
|
|
103
|
+
publish_peer_failure(failure, message: message)
|
|
104
|
+
dont_acknowledge(message, "Received invalid #{message.type}", error.message.to_s)
|
|
105
|
+
Result.failure(failure: failure)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def reject_fatal_message(message, error)
|
|
109
|
+
text = "Rejected #{message&.type || 'message'}: #{error.message}"
|
|
110
|
+
failure = peer_failure(:protocol_failure, text, message: message)
|
|
111
|
+
publish_peer_failure(failure, message: message)
|
|
112
|
+
dont_acknowledge(message, "Rejected #{message.type}", error.message.to_s) if message
|
|
113
|
+
close(reason: :protocol_failure, failure: failure)
|
|
114
|
+
Result.failure(failure: failure)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def peer_failure(code, text, message: nil, **context)
|
|
118
|
+
Failure.new(
|
|
119
|
+
code: code,
|
|
120
|
+
message: text,
|
|
121
|
+
source: :peer,
|
|
122
|
+
context: context.merge(message: message).compact
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def publish_peer_failure(failure, message:)
|
|
127
|
+
distribute_event(
|
|
128
|
+
Event.new(
|
|
129
|
+
type: :invalid_message,
|
|
130
|
+
source: self,
|
|
131
|
+
session_id: session_id,
|
|
132
|
+
message: message,
|
|
133
|
+
failure: failure
|
|
134
|
+
)
|
|
135
|
+
)
|
|
93
136
|
end
|
|
94
137
|
|
|
95
138
|
def process_message(message)
|
|
96
139
|
case message
|
|
97
140
|
when MessageAck
|
|
98
|
-
process_ack
|
|
141
|
+
process_ack(message)
|
|
99
142
|
when MessageNotAck
|
|
100
|
-
process_not_ack
|
|
143
|
+
process_not_ack(message)
|
|
101
144
|
when Version
|
|
102
|
-
process_version
|
|
145
|
+
process_version(message)
|
|
103
146
|
when RSMP::Watchdog
|
|
104
|
-
process_watchdog
|
|
147
|
+
process_watchdog(message)
|
|
105
148
|
else
|
|
106
|
-
dont_acknowledge
|
|
149
|
+
dont_acknowledge(message, 'Received', "unknown message (#{message.type})")
|
|
107
150
|
end
|
|
108
151
|
end
|
|
109
152
|
|
|
110
153
|
def will_not_handle(message)
|
|
111
|
-
reason
|
|
154
|
+
reason = "since we're a #{self.class.name.downcase}"
|
|
112
155
|
log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
|
|
113
|
-
dont_acknowledge
|
|
156
|
+
dont_acknowledge(message, nil, reason)
|
|
114
157
|
end
|
|
115
158
|
|
|
116
159
|
def expect_version_message(message)
|
|
@@ -1,79 +1,137 @@
|
|
|
1
1
|
module RSMP
|
|
2
2
|
class Proxy
|
|
3
3
|
module Modules
|
|
4
|
-
# Message sending functionality
|
|
5
|
-
#
|
|
4
|
+
# Message sending functionality. Expected operational failures are returned
|
|
5
|
+
# as Result::Failure; unexpected implementation errors are not rescued.
|
|
6
6
|
module Send
|
|
7
|
-
def handle_send_schema_error(message, error)
|
|
8
|
-
schemas_string = error.schemas.map { |schema| "#{schema.first}: #{schema.last}" }.join(', ')
|
|
9
|
-
str = "Could not send #{message.type} because schema validation failed (#{schemas_string}): #{error.message}"
|
|
10
|
-
log str, message: message, level: :error
|
|
11
|
-
distribute_error error.exception("#{str} #{message.json}")
|
|
12
|
-
end
|
|
13
|
-
|
|
14
7
|
def send_message(message, reason = nil, validate: true, force: false, buffer: true)
|
|
15
|
-
|
|
16
|
-
write_message(message, validate: validate)
|
|
17
|
-
expect_acknowledgement message
|
|
18
|
-
distribute message
|
|
19
|
-
log_send message, reason
|
|
20
|
-
end
|
|
21
|
-
rescue NotReady, IOError => e
|
|
22
|
-
raise e unless buffer
|
|
8
|
+
return buffer_or_fail(message, buffer: buffer) unless force || connected?
|
|
23
9
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
handle_send_schema_error(message, e)
|
|
27
|
-
end
|
|
10
|
+
written = write_message(message, validate: validate)
|
|
11
|
+
return recover_write_failure(message, written, buffer: buffer) if written.failure?
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
expect_acknowledgement(message)
|
|
14
|
+
distribute(message)
|
|
15
|
+
log_send(message, reason)
|
|
16
|
+
Result.success(Delivery.new(message: message, state: :sent))
|
|
17
|
+
end
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
def send_message!(...)
|
|
20
|
+
send_message(...).value!
|
|
21
|
+
end
|
|
34
22
|
|
|
35
|
-
|
|
23
|
+
# Messages constructed by RSMP itself should never fail local schema
|
|
24
|
+
# validation. Surface that as an implementation defect while preserving
|
|
25
|
+
# connection and transport failures as ordinary Result values.
|
|
26
|
+
def send_generated_message(...)
|
|
27
|
+
result = send_message(...)
|
|
28
|
+
result.value! if result.failure&.source == :local
|
|
29
|
+
result
|
|
36
30
|
end
|
|
37
31
|
|
|
38
32
|
def write_message(message, validate:)
|
|
39
|
-
|
|
33
|
+
return disconnected_send_result(message) unless @protocol
|
|
34
|
+
|
|
35
|
+
prepared = prepare_message(message, validate: validate)
|
|
36
|
+
return prepared if prepared.failure?
|
|
40
37
|
|
|
38
|
+
write_protocol(message)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def prepare_message(message, validate:)
|
|
41
42
|
message.direction = :out
|
|
42
43
|
message.encode_for(schemas) unless validate == false
|
|
43
44
|
message.generate_json
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
unless validate == false
|
|
46
|
+
validation = message.validate(schemas)
|
|
47
|
+
return invalid_outbound_result(message, validation.message, validation: validation) if validation.invalid?
|
|
48
|
+
end
|
|
49
|
+
Result.success(message)
|
|
50
|
+
rescue RSMP::Schema::UnknownMessageCodeError => e
|
|
51
|
+
invalid_outbound_result(message, e.message, cause: e)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def write_protocol(message)
|
|
55
|
+
@protocol.write_lines(message.json)
|
|
56
|
+
Result.success(message)
|
|
57
|
+
rescue IOError, SystemCallError => e
|
|
58
|
+
Result.failure(
|
|
59
|
+
:disconnected,
|
|
60
|
+
message: "Could not send #{message.type}: #{e.message}",
|
|
61
|
+
source: :transport,
|
|
62
|
+
context: { message: message, session_id: session_id },
|
|
63
|
+
cause: e
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def recover_write_failure(message, result, buffer:)
|
|
68
|
+
return result unless result.failure.code == :disconnected
|
|
69
|
+
|
|
70
|
+
buffered = buffer_message(message) if buffer
|
|
71
|
+
return Result.success(Delivery.new(message: buffered, state: :buffered)) if buffered
|
|
72
|
+
|
|
73
|
+
result
|
|
46
74
|
end
|
|
47
75
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
def buffer_or_fail(message, buffer:)
|
|
77
|
+
buffered = buffer_message(message) if buffer
|
|
78
|
+
return Result.success(Delivery.new(message: buffered, state: :buffered)) if buffered
|
|
79
|
+
|
|
80
|
+
Result.failure(
|
|
81
|
+
:not_ready,
|
|
82
|
+
message: "Cannot send #{message.type}: connection is #{@state}",
|
|
83
|
+
source: :connection,
|
|
84
|
+
context: { message: message, state: @state, session_id: session_id }
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Base proxies do not buffer. SupervisorProxy's MessageBuffer override
|
|
89
|
+
# returns a cloned queued message when its explicit policy accepts it.
|
|
90
|
+
def buffer_message(message)
|
|
91
|
+
log "Discarded #{message.type}; connection is #{@state}", message: message, level: :warning
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def disconnected_send_result(message)
|
|
96
|
+
Result.failure(
|
|
97
|
+
:disconnected,
|
|
98
|
+
message: "Cannot send #{message.type}: connection transport is closed",
|
|
99
|
+
source: :transport,
|
|
100
|
+
context: { message: message, session_id: session_id }
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def invalid_outbound_result(message, detail, validation: nil, cause: nil)
|
|
105
|
+
text = "Could not send #{message.type}: #{detail}"
|
|
106
|
+
log(text, message: message, level: :error)
|
|
107
|
+
Result.failure(
|
|
108
|
+
:invalid_outbound_message,
|
|
109
|
+
message: text,
|
|
110
|
+
source: :local,
|
|
111
|
+
context: { message: message, validation: validation }.compact,
|
|
112
|
+
cause: cause
|
|
113
|
+
)
|
|
52
114
|
end
|
|
53
115
|
|
|
54
116
|
def log_send(message, reason = nil)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"Sent #{message.type}"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
if message.type == 'MessageNotAck'
|
|
62
|
-
log str, message: message, level: :warning
|
|
63
|
-
else
|
|
64
|
-
log str, message: message, level: :log
|
|
65
|
-
end
|
|
117
|
+
text = reason ? "Sent #{message.type} #{reason}" : "Sent #{message.type}"
|
|
118
|
+
level = message.type == 'MessageNotAck' ? :warning : :log
|
|
119
|
+
log(text, message: message, level: level)
|
|
66
120
|
end
|
|
67
121
|
|
|
68
122
|
def send_message_and_collect(message, collector, validate: true)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
123
|
+
collector.start
|
|
124
|
+
delivery = send_message(message, validate: validate, buffer: false)
|
|
125
|
+
collector.fail(delivery.failure) if delivery.failure?
|
|
126
|
+
collector.wait.map do |collection|
|
|
127
|
+
Exchange.new(request: message, collection: collection)
|
|
73
128
|
end
|
|
74
|
-
|
|
75
|
-
collector
|
|
76
|
-
|
|
129
|
+
ensure
|
|
130
|
+
collector.stop
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def send_message_and_collect!(...)
|
|
134
|
+
send_message_and_collect(...).value!
|
|
77
135
|
end
|
|
78
136
|
|
|
79
137
|
def apply_nts_message_attributes(message)
|
|
@@ -6,14 +6,24 @@ module RSMP
|
|
|
6
6
|
module State
|
|
7
7
|
def wait_for_state(state, timeout:)
|
|
8
8
|
states = [state].flatten
|
|
9
|
-
return
|
|
9
|
+
return Result.success(@state) if states.include?(@state)
|
|
10
10
|
|
|
11
|
-
wait_for_condition(@state_condition, timeout: timeout) do
|
|
11
|
+
result = wait_for_condition(@state_condition, timeout: timeout) do
|
|
12
12
|
states.include?(@state)
|
|
13
13
|
end
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
return Result.success(@state) if result.success?
|
|
15
|
+
|
|
16
|
+
Result.failure(
|
|
17
|
+
:timeout,
|
|
18
|
+
message: "Did not reach state #{state} within #{timeout}s",
|
|
19
|
+
source: :timeout,
|
|
20
|
+
context: { expected: states, actual: @state },
|
|
21
|
+
cause: result.failure.cause
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def wait_for_state!(...)
|
|
26
|
+
wait_for_state(...).value!
|
|
17
27
|
end
|
|
18
28
|
|
|
19
29
|
def handshake_complete
|