rsmp 0.48.4 → 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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -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/message_distribution.md +17 -3
  9. data/documentation/tasks.md +65 -117
  10. data/lib/rsmp/cli.rb +93 -5
  11. data/lib/rsmp/collect/collector.rb +117 -134
  12. data/lib/rsmp/collect/distributor.rb +16 -3
  13. data/lib/rsmp/collect/queue.rb +14 -6
  14. data/lib/rsmp/collect/receiver.rb +1 -1
  15. data/lib/rsmp/collect/state_collector.rb +11 -2
  16. data/lib/rsmp/collection.rb +12 -0
  17. data/lib/rsmp/completion.rb +49 -0
  18. data/lib/rsmp/component/component_base.rb +3 -3
  19. data/lib/rsmp/convert/export/json_schema/index.rb +8 -3
  20. data/lib/rsmp/convert/import/yaml.rb +12 -8
  21. data/lib/rsmp/delivery.rb +18 -0
  22. data/lib/rsmp/event.rb +19 -0
  23. data/lib/rsmp/event_source.rb +36 -0
  24. data/lib/rsmp/exchange.rb +10 -0
  25. data/lib/rsmp/failure.rb +23 -0
  26. data/lib/rsmp/helpers/error.rb +14 -23
  27. data/lib/rsmp/message.rb +9 -7
  28. data/lib/rsmp/node/node.rb +3 -21
  29. data/lib/rsmp/node/site/connections.rb +4 -9
  30. data/lib/rsmp/node/site/site.rb +31 -16
  31. data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
  32. data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
  33. data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
  34. data/lib/rsmp/node/task.rb +59 -53
  35. data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
  36. data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
  37. data/lib/rsmp/proxy/modules/receive.rb +109 -66
  38. data/lib/rsmp/proxy/modules/send.rb +109 -51
  39. data/lib/rsmp/proxy/modules/state.rb +15 -5
  40. data/lib/rsmp/proxy/modules/tasks.rb +96 -70
  41. data/lib/rsmp/proxy/modules/versions.rb +8 -8
  42. data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
  43. data/lib/rsmp/proxy/proxy.rb +4 -66
  44. data/lib/rsmp/proxy/site/connection.rb +27 -22
  45. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
  46. data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
  47. data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
  48. data/lib/rsmp/proxy/site/modules/status.rb +44 -19
  49. data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
  50. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
  51. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
  52. data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
  53. data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
  54. data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
  55. data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
  56. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +34 -30
  57. data/lib/rsmp/result.rb +95 -0
  58. data/lib/rsmp/schema/validation.rb +5 -3
  59. data/lib/rsmp/sxl/interface.rb +8 -1
  60. data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
  61. data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
  62. data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
  63. data/lib/rsmp/sxl/processing/document.rb +158 -0
  64. data/lib/rsmp/sxl/processing/error.rb +8 -0
  65. data/lib/rsmp/sxl/processing/manifest.rb +144 -0
  66. data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
  67. data/lib/rsmp/sxl/processing/resolver.rb +94 -0
  68. data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
  69. data/lib/rsmp/sxl/processing.rb +18 -0
  70. data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
  71. data/lib/rsmp/termination.rb +10 -0
  72. data/lib/rsmp/tlc/modules/plans.rb +0 -2
  73. data/lib/rsmp/tlc/proxy/control.rb +50 -16
  74. data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
  75. data/lib/rsmp/tlc/proxy/io.rb +46 -31
  76. data/lib/rsmp/tlc/proxy/plans.rb +36 -19
  77. data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
  78. data/lib/rsmp/tlc/proxy/status.rb +47 -23
  79. data/lib/rsmp/tlc/proxy/system.rb +16 -4
  80. data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
  81. data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
  82. data/lib/rsmp/validation.rb +22 -0
  83. data/lib/rsmp/version.rb +1 -1
  84. data/lib/rsmp.rb +19 -4
  85. data/rsmp.gemspec +1 -1
  86. metadata +25 -4
  87. 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 InvalidArgument unless original
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
- send_message ack, "for #{ack.original.type} #{original.m_id_short}"
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 InvalidArgument unless original
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
- send_message message, "for #{original.type} #{original.m_id_short}", force: force
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
- begin
49
- close
50
- ensure
51
- distribute_error MissingAcknowledgment.new(str)
52
- end
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
- # Handles receiving and processing incoming messages
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 handle_invalid_packet(json, error)
28
- str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{error.message}"
29
- distribute_error error.exception(str)
30
- log str, level: :warning
31
- nil
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 handle_malformed_message(attributes, error)
35
- str = "Received malformed message, #{error.message}"
36
- distribute_error error.exception(str)
37
- log str, message: Malformed.new(attributes), level: :warning
38
- nil
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 handle_schema_error(message, error)
42
- failed_schemas = error.respond_to?(:schemas) && error.schemas ? error.schemas : schemas
43
- schemas_string = failed_schemas.map { |schema| "#{schema.first}: #{schema.last}" }.join(', ')
44
- reason = "schema errors (#{schemas_string}): #{error.message}"
45
- str = "Received invalid #{message.type}"
46
- distribute_error error.exception(str), message: message
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 handle_invalid_message(message, error)
52
- reason = error.message.to_s
53
- str = "Received invalid #{message.type},"
54
- distribute_error error.exception("#{str} #{message.json}"), message: message
55
- dont_acknowledge message, str, reason
56
- message
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 handle_fatal_error(message, error)
60
- reason = error.message
61
- str = "Rejected #{message.type},"
62
- distribute_error error.exception(str), message: message
63
- dont_acknowledge message, str, reason
64
- close
65
- message
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 process_packet(json)
69
- attributes = Message.parse_attributes json
70
- message = Message.build attributes, json
71
- validate = should_validate_ingoing_message?(message)
72
- message.validate(schemas) if validate
73
- verify_sequence message
74
- message.decode_for(schemas) if validate
75
- with_deferred_distribution do
76
- distribute message
77
- process_message message
78
- end
79
- process_deferred
80
- message
81
- rescue InvalidPacket => e
82
- handle_invalid_packet(json, e)
83
- rescue MalformedMessage => e
84
- handle_malformed_message(attributes, e)
85
- rescue SchemaError, RSMP::Schema::Error => e
86
- handle_schema_error(message, e)
87
- rescue InvalidMessage, MessageRejected => e
88
- handle_invalid_message(message, e)
89
- rescue FatalError => e
90
- handle_fatal_error(message, e)
91
- ensure
92
- @node.clear_deferred
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 message
141
+ process_ack(message)
99
142
  when MessageNotAck
100
- process_not_ack message
143
+ process_not_ack(message)
101
144
  when Version
102
- process_version message
145
+ process_version(message)
103
146
  when RSMP::Watchdog
104
- process_watchdog message
147
+ process_watchdog(message)
105
148
  else
106
- dont_acknowledge message, 'Received', "unknown message (#{message.type})"
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 ||= "since we're a #{self.class.name.downcase}"
154
+ reason = "since we're a #{self.class.name.downcase}"
112
155
  log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
113
- dont_acknowledge message, nil, reason
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
- # Handles sending messages, validation, and buffering
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
- with_send_ready(message, force: force, buffer: buffer) do
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
- buffer_message message, e
25
- rescue SchemaError, RSMP::Schema::Error => e
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
- def with_send_ready(message, force:, buffer:)
30
- return yield if force || connected?
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
- error = NotReady.new
33
- raise error unless buffer
19
+ def send_message!(...)
20
+ send_message(...).value!
21
+ end
34
22
 
35
- buffer_message(message, error)
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
- raise IOError unless @protocol
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
- message.validate schemas unless validate == false
45
- @protocol.write_lines message.json
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 buffer_message(message, error = nil)
49
- str = "Cannot send #{message.type} because the connection is closed."
50
- log str, message: message, level: :error
51
- raise error if error
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
- str = if reason
56
- "Sent #{message.type} #{reason}"
57
- else
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
- task = @task.async do |t|
70
- t.annotate 'send_message_and_collect'
71
- collector.collect
72
- collector
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
- send_message message, validate: validate
75
- collector = task.wait
76
- { sent: message, collector: collector }
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 true if states.include?(@state)
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
- true
15
- rescue RSMP::TimeoutError
16
- raise RSMP::TimeoutError, "Did not reach state #{state} within #{timeout}s"
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