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
@@ -1,95 +1,101 @@
1
1
  module RSMP
2
- class Restart < StandardError
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
- # start our async tasks and return immediately
14
- # run() will be called inside the task to perform actual long-running work
15
- def start
16
- return if @task
17
-
18
- # Use current task context if available, otherwise create new reactor
19
- if Async::Task.current?
20
- Async::Task.current.async do |task|
21
- task.annotate "#{self.class.name} main task"
22
- @task = task
23
- run
24
- stop_subtasks
25
- @task = nil
26
- end
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
- self
28
+ @task = child
37
29
  end
38
30
 
39
- # initiate restart by raising a Restart exception
40
31
  def restart
41
- raise Restart, "restart initiated by #{self.class.name}:#{object_id}"
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 for our task to complete
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
- stop_subtasks
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.stop
73
- @task = nil
60
+ task = @task
61
+ return unless task&.running?
62
+
63
+ task.cancel
64
+ task.wait unless task.current?
74
65
  end
75
66
 
76
- # wait for an async condition to signal, then yield to block
77
- # if block returns true we're done. otherwise, wait again
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 "Can't wait without a task" unless task
70
+ raise ArgumentError, 'an active Async task is required' unless task&.running?
80
71
 
81
- task.with_timeout(timeout) do
82
- while task.running?
83
- value = condition.wait
84
- return value unless block
72
+ value = task.with_timeout(timeout, ConditionWaitTimeout) do
73
+ loop do
74
+ signalled = condition.wait
75
+ break signalled unless block
85
76
 
86
- result = yield value
87
- return result if result
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
- rescue Async::TimeoutError
92
- raise RSMP::TimeoutError
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
@@ -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)