rsmp 0.1.12 → 0.1.27

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.
@@ -0,0 +1,38 @@
1
+ # Import SXL from YAML format
2
+
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'fileutils'
6
+
7
+ module RSMP
8
+ module Convert
9
+ module Import
10
+ module YAML
11
+
12
+ def self.read path
13
+ convert ::YAML.load_file(path)
14
+ end
15
+
16
+ def self.parse str
17
+ convert ::YAML.load(str)
18
+ end
19
+
20
+ def self.convert yaml
21
+ sxl = {
22
+ alarms: {},
23
+ statuses: {},
24
+ commands: {}
25
+ }
26
+
27
+ yaml['objects'].each_pair do |type,object|
28
+ object["alarms"].each { |id,item| sxl[:alarms][id] = item }
29
+ object["statuses"].each { |id,item| sxl[:statuses][id] = item }
30
+ object["commands"].each { |id,item| sxl[:commands][id] = item }
31
+ end
32
+ sxl
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
data/lib/rsmp/error.rb CHANGED
@@ -23,7 +23,7 @@ module RSMP
23
23
  class MissingWatchdog < Error
24
24
  end
25
25
 
26
- class MissingAcknowledgment < Error
26
+ class MessageRejected < Error
27
27
  end
28
28
 
29
29
  class MissingAttribute < InvalidMessage
@@ -43,4 +43,13 @@ module RSMP
43
43
 
44
44
  class UnknownComponent < Error
45
45
  end
46
+
47
+ class UnknownCommand < Error
48
+ end
49
+
50
+ class UnknownStatus < Error
51
+ end
52
+
53
+ class ConfigurationError < Error
54
+ end
46
55
  end
@@ -0,0 +1,46 @@
1
+ # Costume inspect, to reduce noise
2
+ #
3
+ # Instance variables of classes starting with Async or RSMP are shown
4
+ # with only their class name and object id, which reduces output,
5
+ # especially for deep object structures.
6
+ # Additionally, a list of variables to shown in short format can be passed.
7
+ #
8
+ # The short form is generated by using to_s() insted of inspect()
9
+ #
10
+ # Array#to_s and Hash#to_s usually show items, but here we show just number
11
+ # of items, when the short form is requested.
12
+
13
+ module RSMP
14
+ module Inspect
15
+
16
+ def inspector *short_items
17
+ instance_variables.map do |var_name|
18
+ var = instance_variable_get(var_name)
19
+ class_name = var.class.name
20
+
21
+ short = short_items.include?(var_name) ||
22
+ class_name.start_with?('Async') ||
23
+ class_name.start_with?('RSMP')
24
+
25
+ if short
26
+ if var.is_a? Array
27
+ "#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
28
+ elsif var.is_a? Hash
29
+ "#{var_name}: #<#{class_name}:#{class_name.object_id}, #{var.size} items>"
30
+ else
31
+ "#{var_name}: #{var.to_s}"
32
+ end
33
+ else
34
+ "#{var_name}: #{var.inspect}"
35
+ end
36
+ end.join(', ')
37
+ end
38
+
39
+ # override this if you want additional variable to be shown in the short format,
40
+ # or ottherwise change the inspect format
41
+ def inspect
42
+ "#<#{self.class.name}:#{self.object_id}, #{inspector}>"
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ # Receives items from a Notifier, as long as it's
2
+ # installed as a listener.
3
+
4
+ module RSMP
5
+ class Listener
6
+ include Inspect
7
+
8
+ def initialize proxy, options={}
9
+ @proxy = proxy
10
+ end
11
+
12
+ def notify message
13
+ end
14
+
15
+ def listen &block
16
+ @proxy.add_listener self
17
+ yield
18
+ ensure
19
+ @proxy.remove_listener self
20
+ end
21
+
22
+ end
23
+ end
data/lib/rsmp/logger.rb CHANGED
@@ -13,6 +13,7 @@ module RSMP
13
13
  'component'=>false,
14
14
  'level'=>false,
15
15
  'ip'=>false,
16
+ 'port'=>false,
16
17
  'index'=>false,
17
18
  'timestamp'=>true,
18
19
  'json'=>false,
@@ -82,7 +83,6 @@ module RSMP
82
83
  end
83
84
 
84
85
  def colorize level, str
85
- #p String.color_samples
86
86
  if @settings["color"] == false || @settings["color"] == nil
87
87
  str
88
88
  elsif @settings["color"] == true
@@ -125,8 +125,9 @@ module RSMP
125
125
  end
126
126
  end
127
127
 
128
- def dump archive, force:false
129
- log = archive.items.map do |item|
128
+ def dump archive, force:false, num:nil
129
+ num ||= archive.items.size
130
+ log = archive.items.last(num).map do |item|
130
131
  str = build_output item
131
132
  str = colorize item[:level], str
132
133
  end
@@ -137,8 +138,9 @@ module RSMP
137
138
  parts = []
138
139
  parts << item[:index].to_s.ljust(7) if @settings["index"] == true
139
140
  parts << item[:author].to_s.ljust(13) if @settings["author"] == true
140
- parts << item[:timestamp].to_s.ljust(24) unless @settings["timestamp"] == false
141
+ parts << Clock.to_s(item[:timestamp]).ljust(24) unless @settings["timestamp"] == false
141
142
  parts << item[:ip].to_s.ljust(22) unless @settings["ip"] == false
143
+ parts << item[:port].to_s.ljust(8) unless @settings["port"] == false
142
144
  parts << item[:site_id].to_s.ljust(13) unless @settings["site_id"] == false
143
145
  parts << item[:component_id].to_s.ljust(18) unless @settings["component"] == false
144
146
 
@@ -3,10 +3,10 @@
3
3
  #
4
4
 
5
5
  module RSMP
6
- class Base
6
+ module Logging
7
7
  attr_reader :archive, :logger
8
8
 
9
- def initialize options
9
+ def initialize_logging options
10
10
  @archive = options[:archive] || RSMP::Archive.new
11
11
  @logger = options[:logger] || RSMP::Logger.new(options[:log_settings])
12
12
  end
@@ -15,7 +15,7 @@ module RSMP
15
15
  end
16
16
 
17
17
  def log str, options={}
18
- default = { str:str, level: :log, author: author }
18
+ default = { str:str, level: :log, author: author, ip: @ip, port: @port }
19
19
  prepared = RSMP::Archive.prepare_item default.merge(options)
20
20
  @archive.add prepared
21
21
  @logger.log prepared
data/lib/rsmp/message.rb CHANGED
@@ -1,33 +1,18 @@
1
+ require 'rsmp_schemer'
2
+
1
3
  # rsmp messages
2
4
  module RSMP
3
5
  class Message
6
+ include Inspect
4
7
 
5
- attr_reader :now, :attributes, :out, :timestamp
8
+ attr_reader :now, :attributes, :out
9
+ attr_reader :timestamp # this is an internal timestamp recording when we receive/send
6
10
  attr_accessor :json, :direction
7
11
 
8
- def self.load_schemas
9
- # path to files in submodule folder
10
- schema_path = File.join(File.dirname(__dir__),'rsmp_schema','schema')
11
- @@schemas = {}
12
-
13
- core_schema_path = File.join(schema_path,'core','rsmp.json')
14
- @@schemas[nil] = JSONSchemer.schema( Pathname.new(core_schema_path) )
15
-
16
- tlc_schema_path = File.join(schema_path,'tlc','sxl.json')
17
- @@schemas['traffic_light_controller'] = JSONSchemer.schema( Pathname.new(tlc_schema_path) )
18
-
19
- @@schemas
20
- end
21
-
22
- def self.get_schema sxl=nil
23
- schema = @@schemas[sxl]
24
- raise SchemaError.new("Unknown schema #{sxl}") unless schema
25
- schema
12
+ def self.make_m_id
13
+ SecureRandom.uuid()
26
14
  end
27
15
 
28
- @@schemas = load_schemas
29
-
30
-
31
16
  def self.parse_attributes json
32
17
  raise ArgumentError unless json
33
18
  JSON.parse json
@@ -46,6 +31,8 @@ module RSMP
46
31
  message = Version.new attributes
47
32
  when "AggregatedStatus"
48
33
  message = AggregatedStatus.new attributes
34
+ when "AggregatedStatusRequest"
35
+ message = AggregatedStatusRequest.new attributes
49
36
  when "Watchdog"
50
37
  message = Watchdog.new attributes
51
38
  when "Alarm"
@@ -80,8 +67,12 @@ module RSMP
80
67
  @attributes["mId"]
81
68
  end
82
69
 
70
+ def self.shorten_m_id m_id, length=4
71
+ m_id[0..length-1]
72
+ end
73
+
83
74
  def m_id_short
84
- @attributes["mId"][0..3]
75
+ Message.shorten_m_id @attributes["mId"]
85
76
  end
86
77
 
87
78
  def attribute key
@@ -118,7 +109,9 @@ module RSMP
118
109
  end
119
110
 
120
111
  def initialize attributes = {}
121
- @timestamp = RSMP.now_object
112
+ @timestamp = Time.now # this timestamp is for internal use, and does not the clock
113
+ # in the node, which can be set by an rsmp supervisor
114
+
122
115
  @attributes = { "mType"=> "rSMsg" }.merge attributes
123
116
 
124
117
  ensure_message_id
@@ -126,16 +119,13 @@ module RSMP
126
119
 
127
120
  def ensure_message_id
128
121
  # if message id is empty, generate a new one
129
- @attributes["mId"] ||= SecureRandom.uuid()
122
+ @attributes["mId"] ||= Message.make_m_id
130
123
  end
131
124
 
132
- def validate sxl=nil
133
- schema = Message.get_schema(sxl)
134
- unless schema.valid? attributes
135
- errors = schema.validate attributes
136
- error_string = errors.map do |item|
137
- [item['data_pointer'],item['type'],item['details']].compact.join(' ')
138
- end.join(", ")
125
+ def validate schemas
126
+ errors = RSMP::Schemer.validate attributes, schemas
127
+ if errors
128
+ error_string = errors.compact.join(', ').strip
139
129
  raise SchemaError.new error_string
140
130
  end
141
131
  end
@@ -189,6 +179,14 @@ module RSMP
189
179
  end
190
180
  end
191
181
 
182
+ class AggregatedStatusRequest < Message
183
+ def initialize attributes = {}
184
+ super({
185
+ "type" => "AggregatedStatusRequest",
186
+ }.merge attributes)
187
+ end
188
+ end
189
+
192
190
  class Alarm < Message
193
191
  def initialize attributes = {}
194
192
  super({
@@ -197,6 +195,22 @@ module RSMP
197
195
  end
198
196
  end
199
197
 
198
+ class AlarmRequest < Message
199
+ def initialize attributes = {}
200
+ super({
201
+ "type" => "Alarm",
202
+ }.merge attributes)
203
+ end
204
+ end
205
+
206
+ class AlarmAcknowledged < Message
207
+ def initialize attributes = {}
208
+ super({
209
+ "type" => "Alarm",
210
+ }.merge attributes)
211
+ end
212
+ end
213
+
200
214
  class Watchdog < Message
201
215
  def initialize attributes = {}
202
216
  super({
data/lib/rsmp/node.rb CHANGED
@@ -1,23 +1,58 @@
1
- # RSMP site
2
- #
3
- # Handles a single connection to a supervisor.
4
- # We connect to the supervisor.
1
+ # Base class for sites and supervisors
5
2
 
6
3
  module RSMP
7
- class Node < Base
8
- attr_reader :archive, :logger, :task
4
+ class Node
5
+ include Logging
6
+ include Wait
7
+ include Inspect
8
+
9
+ attr_reader :archive, :logger, :task, :deferred, :error_condition, :clock
9
10
 
10
11
  def initialize options
11
- super options
12
+ initialize_logging options
13
+ @task = options[:task]
14
+ @deferred = []
15
+ @clock = Clock.new
16
+ @error_condition = Async::Notification.new
17
+ end
18
+
19
+ def notify_error e, options={}
20
+ if options[:level] == :internal
21
+ log ["#{e.to_s} in task: #{Async::Task.current.to_s}",e.backtrace].flatten.join("\n"), level: :error
22
+ end
23
+ @error_condition.signal e
24
+ end
25
+
26
+ def defer item
27
+ @deferred << item
28
+ end
29
+
30
+ def process_deferred
31
+ cloned = @deferred.clone # clone in case do_deferred restarts the current task
32
+ @deferred.clear
33
+ cloned.each do |item|
34
+ do_deferred item
35
+ end
36
+ end
37
+
38
+ def do_deferred item
39
+ end
40
+
41
+ def do_start task
42
+ task.annotate self.class.to_s
43
+ @task = task
44
+ start_action
45
+ idle
12
46
  end
13
47
 
14
48
  def start
15
49
  starting
16
- Async do |task|
17
- task.annotate self.class
18
- @task = task
19
- start_action
20
- idle
50
+ if @task
51
+ do_start @task
52
+ else
53
+ Async do |task|
54
+ do_start task
55
+ end
21
56
  end
22
57
  rescue Errno::EADDRINUSE => e
23
58
  log "Cannot start: #{e.to_s}", level: :error
@@ -0,0 +1,29 @@
1
+ # Distributes messages to listeners
2
+
3
+ module RSMP
4
+ module Notifier
5
+ include Inspect
6
+
7
+ def inspect
8
+ "#<#{self.class.name}:#{self.object_id}, #{inspector(:@listeners)}>"
9
+ end
10
+
11
+ def initialize_distributor
12
+ @listeners = []
13
+ end
14
+
15
+ def add_listener listener
16
+ raise ArgumentError unless listener
17
+ @listeners << listener unless @listeners.include? listener
18
+ end
19
+
20
+ def remove_listener listener
21
+ raise ArgumentError unless listener
22
+ @listeners.delete listener
23
+ end
24
+
25
+ def notify message
26
+ @listeners.each { |listener| listener.notify message }
27
+ end
28
+ end
29
+ end
data/lib/rsmp/proxy.rb CHANGED
@@ -1,24 +1,61 @@
1
- # Base class for a connection to a remote site or supervisor.
1
+ # Logging class for a connection to a remote site or supervisor.
2
+
3
+ require 'rubygems'
2
4
 
3
5
  module RSMP
4
- class Proxy < Base
5
- attr_reader :state, :archive, :connection_info, :sxl
6
+ class Proxy
7
+ WRAPPING_DELIMITER = "\f"
8
+
9
+ include Logging
10
+ include Wait
11
+ include Notifier
12
+ include Inspect
13
+
14
+ attr_reader :state, :archive, :connection_info, :sxl, :task, :collector
6
15
 
7
16
  def initialize options
8
- super options
17
+ initialize_logging options
9
18
  @settings = options[:settings]
10
19
  @task = options[:task]
11
20
  @socket = options[:socket]
12
21
  @ip = options[:ip]
22
+ @port = options[:port]
13
23
  @connection_info = options[:info]
14
24
  @sxl = nil
25
+ initialize_distributor
26
+
27
+ prepare_collection options[:settings]['collect']
28
+
15
29
  clear
16
30
  end
17
31
 
32
+ def inspect
33
+ "#<#{self.class.name}:#{self.object_id}, #{inspector(
34
+ :@acknowledgements,:@settings,:@site_settings
35
+ )}>"
36
+ end
37
+
38
+ def clock
39
+ node.clock
40
+ end
41
+
42
+ def prepare_collection num
43
+ if num
44
+ @collector = RSMP::Collector.new self, num: num, ingoing: true, outgoing: true
45
+ add_listener @collector
46
+ end
47
+ end
48
+
49
+ def collect task, options, &block
50
+ collector = RSMP::Collector.new self, options
51
+ collector.collect task, &block
52
+ end
53
+
18
54
  def run
19
55
  start
20
56
  @reader.wait if @reader
21
- stop
57
+ ensure
58
+ stop unless [:stopped, :stopping].include? @state
22
59
  end
23
60
 
24
61
  def ready?
@@ -69,7 +106,7 @@ module RSMP
69
106
  @reader = @task.async do |task|
70
107
  task.annotate "reader"
71
108
  @stream = Async::IO::Stream.new(@socket)
72
- @protocol = Async::IO::Protocol::Line.new(@stream,RSMP::WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
109
+ @protocol = Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
73
110
 
74
111
  while json = @protocol.read_line
75
112
  beginning = Time.now
@@ -97,13 +134,15 @@ module RSMP
97
134
  log "Connection reset by peer", level: :warning
98
135
  rescue Errno::EPIPE
99
136
  log "Broken pipe", level: :warning
100
- rescue SystemCallError => e # all ERRNO errors
101
- log "Proxy exception: #{e.to_s}", level: :error
102
137
  rescue StandardError => e
103
- log ["Proxy exception: #{e.inspect}",e.backtrace].flatten.join("\n"), level: :error
138
+ notify_error e, level: :internal
104
139
  end
105
140
  end
106
141
 
142
+ def notify_error e, options={}
143
+ node.notify_error e, options
144
+ end
145
+
107
146
  def start_watchdog
108
147
  log "Starting watchdog with interval #{@settings["watchdog_interval"]} seconds", level: :debug
109
148
  send_watchdog
@@ -114,27 +153,41 @@ module RSMP
114
153
  name = "timer"
115
154
  interval = @settings["timer_interval"] || 1
116
155
  log "Starting #{name} with interval #{interval} seconds", level: :debug
117
- @latest_watchdog_received = RSMP.now_object
156
+ @latest_watchdog_received = Clock.now
157
+
118
158
  @timer = @task.async do |task|
119
159
  task.annotate "timer"
160
+ next_time = Time.now.to_f
120
161
  loop do
121
- now = RSMP.now_object
122
- break if timer(now) == false
123
- rescue StandardError => e
124
- log ["#{name} exception: #{e}",e.backtrace].flatten.join("\n"), level: :error
162
+ begin
163
+ now = Clock.now
164
+ timer(now)
165
+ rescue EOFError => e
166
+ log "Timer: Connection closed: #{e}", level: :warning
167
+ rescue IOError => e
168
+ log "Timer: IOError", level: :warning
169
+ rescue Errno::ECONNRESET
170
+ log "Timer: Connection reset by peer", level: :warning
171
+ rescue Errno::EPIPE => e
172
+ log "Timer: Broken pipe", level: :warning
173
+ rescue StandardError => e
174
+ notify_error e, level: :internal
175
+ end
125
176
  ensure
126
- task.sleep interval
177
+ next_time += interval
178
+ duration = next_time - Time.now.to_f
179
+ task.sleep duration
127
180
  end
128
181
  end
129
182
  end
130
183
 
131
184
  def timer now
132
- check_watchdog_send_time now
133
- return false if check_ack_timeout now
134
- return false if check_watchdog_timeout now
185
+ watchdog_send_timer now
186
+ check_ack_timeout now
187
+ check_watchdog_timeout now
135
188
  end
136
189
 
137
- def check_watchdog_send_time now
190
+ def watchdog_send_timer now
138
191
  return unless @watchdog_started
139
192
  return if @settings["watchdog_interval"] == :never
140
193
 
@@ -148,13 +201,10 @@ module RSMP
148
201
  send_watchdog now
149
202
  end
150
203
  end
151
- rescue StandardError => e
152
- log ["Watchdog error: #{e}",e.backtrace].flatten.join("\n"), level: :error
153
204
  end
154
205
 
155
- def send_watchdog now=nil
156
- now = RSMP.now_object unless nil
157
- message = Watchdog.new( {"wTs" => RSMP.now_object_to_string(now)})
206
+ def send_watchdog now=Clock.now
207
+ message = Watchdog.new( {"wTs" => clock.to_s})
158
208
  send_message message
159
209
  @latest_watchdog_send_at = now
160
210
  end
@@ -167,24 +217,18 @@ module RSMP
167
217
  if now > latest
168
218
  log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
169
219
  stop
170
- return true
171
220
  end
172
221
  end
173
- false
174
222
  end
175
223
 
176
224
  def check_watchdog_timeout now
177
-
178
225
  timeout = @settings["watchdog_timeout"]
179
226
  latest = @latest_watchdog_received + timeout
180
227
  left = latest - now
181
- #log "Check watchdog, time:#{timeout}, last:#{@latest_watchdog_received}, now: #{now}, latest:#{latest}, left #{left}, fail:#{left<0}", level: :debug
182
228
  if left < 0
183
229
  log "No Watchdog within #{timeout} seconds", level: :error
184
230
  stop
185
- return true
186
231
  end
187
- false
188
232
  end
189
233
 
190
234
  def stop_tasks
@@ -196,23 +240,36 @@ module RSMP
196
240
  super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
197
241
  end
198
242
 
199
- def send_message message, reason=nil
243
+ def get_schemas
244
+ # normally we have an sxl, but during connection, it hasn't been established yet
245
+ # at these times we only validate against the core schema
246
+ # TODO
247
+ # what schema should we use to validate the intial Version and MessageAck messages?
248
+ schemas = { core: '3.1.5' }
249
+ schemas[sxl] = sxl_version if sxl
250
+ schemas
251
+ end
252
+
253
+ def send_message message, reason=nil, validate: true
200
254
  raise IOError unless @protocol
201
- message.generate_json
202
- message.validate sxl
203
255
  message.direction = :out
256
+ message.generate_json
257
+ message.validate get_schemas unless validate==false
204
258
  expect_acknowledgement message
205
259
  @protocol.write_lines message.json
260
+ notify message
206
261
  log_send message, reason
207
262
  rescue EOFError, IOError
208
263
  buffer_message message
209
264
  rescue SchemaError => e
210
- log "Error sending #{message.type}, schema validation failed: #{e.message}", message: message, level: :error
265
+ str = "Could not send #{message.type} because schema validation failed: #{e.message}"
266
+ log str, message: message, level: :error
267
+ notify_error e.exception("#{str} #{message.json}")
211
268
  end
212
269
 
213
270
  def buffer_message message
214
271
  # TODO
215
- log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
272
+ #log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
216
273
  end
217
274
 
218
275
  def log_send message, reason=nil
@@ -232,25 +289,38 @@ module RSMP
232
289
  def process_packet json
233
290
  attributes = Message.parse_attributes json
234
291
  message = Message.build attributes, json
235
- message.validate sxl
292
+ message.validate get_schemas
293
+ notify message
236
294
  expect_version_message(message) unless @version_determined
237
295
  process_message message
296
+ process_deferred
238
297
  message
239
298
  rescue InvalidPacket => e
240
- warning "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
299
+ str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
300
+ notify_error e.exception(str)
301
+ log str, level: :warning
241
302
  nil
242
303
  rescue MalformedMessage => e
243
- warning "Received malformed message, #{e.message}", Malformed.new(attributes)
304
+ str = "Received malformed message, #{e.message}"
305
+ notify_error e.exception(str)
306
+ log str, message: Malformed.new(attributes), level: :warning
244
307
  # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
245
308
  nil
246
309
  rescue SchemaError => e
247
- dont_acknowledge message, "Received", "invalid #{message.type}, schema errors: #{e.message}"
310
+ str = "Received invalid #{message.type}, schema errors: #{e.message}"
311
+ log str, message: message, level: :warning
312
+ notify_error e.exception("#{str} #{message.json}")
313
+ dont_acknowledge message, str
248
314
  message
249
315
  rescue InvalidMessage => e
250
- dont_acknowledge message, "Received", "invalid #{message.type}, #{e.message}"
316
+ str = "Received", "invalid #{message.type}, #{e.message}"
317
+ notify_error e.exception("#{str} #{message.json}")
318
+ dont_acknowledge message, str
251
319
  message
252
320
  rescue FatalError => e
253
- dont_acknowledge message, "Rejected #{message.type},", "#{e.message}"
321
+ str = "Rejected #{message.type},"
322
+ notify_error e.exception("#{str} #{message.json}")
323
+ dont_acknowledge message, str, "#{e.message}"
254
324
  stop
255
325
  message
256
326
  end
@@ -294,7 +364,7 @@ module RSMP
294
364
  # find versions that both we and the client support
295
365
  candidates = message.versions & @settings["rsmp_versions"]
296
366
  if candidates.any?
297
- @rsmp_version = candidates.sort.last # pick latest version
367
+ @rsmp_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
298
368
  else
299
369
  raise FatalError.new "RSMP versions [#{message.versions.join(',')}] requested, but only [#{@settings["rsmp_versions"].join(',')}] supported."
300
370
  end
@@ -331,10 +401,12 @@ module RSMP
331
401
  def wait_for_state state, timeout
332
402
  states = [state].flatten
333
403
  return if states.include?(@state)
334
- RSMP::Wait.wait_for(@task,@state_condition,timeout) do |s|
404
+ wait_for(@state_condition,timeout) do
335
405
  states.include?(@state)
336
406
  end
337
407
  @state
408
+ rescue Async::TimeoutError
409
+ raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
338
410
  end
339
411
 
340
412
  def send_version site_id, rsmp_versions
@@ -424,7 +496,7 @@ module RSMP
424
496
 
425
497
  def process_watchdog message
426
498
  log "Received #{message.type}", message: message, level: :log
427
- @latest_watchdog_received = RSMP.now_object
499
+ @latest_watchdog_received = Clock.now
428
500
  acknowledge message
429
501
  end
430
502
 
@@ -441,27 +513,16 @@ module RSMP
441
513
  def version_acknowledged
442
514
  end
443
515
 
444
- def wait_for_acknowledgement original, timeout, options={}
516
+ def wait_for_acknowledgement original, timeout
445
517
  raise ArgumentError unless original
446
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
447
- message.is_a?(MessageAck) &&
448
- message.attributes["oMId"] == original.m_id
449
- end
450
- end
451
-
452
- def wait_for_not_acknowledged original, timeout
453
- raise ArgumentError unless original
454
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
455
- message.is_a?(MessageNotAck) &&
456
- message.attributes["oMId"] == original.m_id
457
- end
458
- end
459
-
460
- def wait_for_acknowledgements timeout
461
- return if @awaiting_acknowledgement.empty?
462
- RSMP::Wait.wait_for(@task,@acknowledgement_condition,timeout) do |message|
463
- @awaiting_acknowledgement.empty?
518
+ wait_for(@acknowledgement_condition,timeout) do |message|
519
+ if message.is_a?(MessageNotAck) && message.attributes["oMId"] == original.m_id
520
+ raise RSMP::MessageRejected.new(message.attributes['rea'])
521
+ end
522
+ message.is_a?(MessageAck) && message.attributes["oMId"] == original.m_id
464
523
  end
524
+ rescue Async::TimeoutError
525
+ raise RSMP::TimeoutError.new("Acknowledgement for #{original.type} #{original.m_id} not received within #{timeout}s")
465
526
  end
466
527
 
467
528
  def node