rsmp 0.8.6 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rsmp/proxy.rb CHANGED
@@ -1,32 +1,104 @@
1
- # Logging class for a connection to a remote site or supervisor.
1
+ # A connection to a remote site or supervisor.
2
+ # Uses the Task module to handle asyncronous work, but adds
3
+ # the concept of a connection that can be connected or disconnected.
2
4
 
3
5
  require 'rubygems'
4
6
 
5
- module RSMP
7
+ module RSMP
6
8
  class Proxy
7
9
  WRAPPING_DELIMITER = "\f"
8
10
 
9
11
  include Logging
10
- include Wait
11
12
  include Notifier
12
13
  include Inspect
14
+ include Task
13
15
 
14
- attr_reader :state, :archive, :connection_info, :sxl, :task, :collector, :ip, :port
16
+ attr_reader :state, :archive, :connection_info, :sxl, :collector, :ip, :port
15
17
 
16
18
  def initialize options
17
19
  initialize_logging options
18
20
  initialize_distributor
21
+ initialize_task
19
22
  setup options
20
23
  clear
24
+ @state = :disconnected
21
25
  end
22
26
 
27
+ def disconnect
28
+ end
29
+
30
+ # wait for the reader task to complete,
31
+ # which is not expected to happen before the connection is closed
32
+ def wait_for_reader
33
+ @reader.wait if @reader
34
+ end
35
+
36
+ # close connection, but keep our main task running so we can reconnect
37
+ def close
38
+ log "Closing connection", level: :warning
39
+ close_stream
40
+ close_socket
41
+ set_state :disconnected
42
+ notify_error DisconnectError.new("Connection was closed")
43
+ stop_timer
44
+ end
45
+
46
+ def stop_subtasks
47
+ stop_timer
48
+ stop_reader
49
+ clear
50
+ super
51
+ end
52
+
53
+ def stop_timer
54
+ return unless @timer
55
+ @timer.stop
56
+ @timer = nil
57
+ end
58
+
59
+ def stop_reader
60
+ return unless @reader
61
+ @reader.stop
62
+ @reader = nil
63
+ end
64
+
65
+ def close_stream
66
+ return unless @stream
67
+ @stream.close
68
+ @stream = nil
69
+ end
70
+
71
+ def close_socket
72
+ return unless @socket
73
+ @socket.close
74
+ @socket = nil
75
+ end
76
+
77
+ def stop_task
78
+ close
79
+ super
80
+ end
81
+
82
+ # change our state
83
+ def set_state state
84
+ return if state == @state
85
+ @state = state
86
+ state_changed
87
+ end
88
+
89
+ # the state changed
90
+ # override to to things like notifications
91
+ def state_changed
92
+ @state_condition.signal @state
93
+ end
94
+
95
+ # revive after a reconnect
23
96
  def revive options
24
97
  setup options
25
98
  end
26
99
 
27
100
  def setup options
28
101
  @settings = options[:settings]
29
- @task = options[:task]
30
102
  @socket = options[:socket]
31
103
  @stream = options[:stream]
32
104
  @protocol = options[:protocol]
@@ -35,7 +107,6 @@ module RSMP
35
107
  @connection_info = options[:info]
36
108
  @sxl = nil
37
109
  @site_settings = nil # can't pick until we know the site id
38
- @state = :stopped
39
110
  if options[:collect]
40
111
  @collector = RSMP::Collector.new self, options[:collect]
41
112
  @collector.start
@@ -52,35 +123,16 @@ module RSMP
52
123
  node.clock
53
124
  end
54
125
 
55
- def run
56
- start
57
- @reader.wait if @reader
58
- ensure
59
- stop unless [:stopped, :stopping].include? @state
60
- end
61
-
62
126
  def ready?
63
127
  @state == :ready
64
128
  end
65
129
 
66
130
  def connected?
67
- @state == :starting || @state == :ready
131
+ @state == :connected || @state == :ready
68
132
  end
69
133
 
70
-
71
- def start
72
- set_state :starting
73
- end
74
-
75
- def stop
76
- return if @state == :stopped
77
- set_state :stopping
78
- stop_tasks
79
- notify_error DisconnectError.new("Connection was closed")
80
- ensure
81
- close_socket
82
- clear
83
- set_state :stopped
134
+ def disconnected?
135
+ @state == :disconnected
84
136
  end
85
137
 
86
138
  def clear
@@ -97,56 +149,57 @@ module RSMP
97
149
  @acknowledgement_condition = Async::Notification.new
98
150
  end
99
151
 
100
- def close_socket
101
- if @stream
102
- @stream.close
103
- @stream = nil
104
- end
105
-
106
- if @socket
107
- @socket.close
108
- @socket = nil
152
+ # run an async task that reads from @socket
153
+ def start_reader
154
+ @reader = @task.async do |task|
155
+ task.annotate "reader"
156
+ run_reader
109
157
  end
110
158
  end
111
159
 
112
- def start_reader
113
- @reader = @task.async do |task|
114
- task.annotate "reader"
115
- @stream ||= Async::IO::Stream.new(@socket)
116
- @protocol ||= Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
117
- while json = @protocol.read_line
118
- beginning = Time.now
119
- message = process_packet json
120
- duration = Time.now - beginning
121
- ms = (duration*1000).round(4)
122
- if duration > 0
123
- per_second = (1.0 / duration).round
124
- else
125
- per_second = Float::INFINITY
126
- end
127
- if message
128
- type = message.type
129
- m_id = Logger.shorten_message_id(message.m_id)
130
- else
131
- type = 'Unknown'
132
- m_id = nil
133
- end
134
- str = [type,m_id,"processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
135
- log str, level: :statistics
136
- end
137
- rescue Async::Wrapper::Cancelled
138
- # ignore
139
- rescue EOFError
140
- log "Connection closed", level: :warning
141
- rescue IOError => e
142
- log "IOError: #{e}", level: :warning
143
- rescue Errno::ECONNRESET
144
- log "Connection reset by peer", level: :warning
145
- rescue Errno::EPIPE
146
- log "Broken pipe", level: :warning
147
- rescue StandardError => e
148
- notify_error e, level: :internal
160
+ def run_reader
161
+ @stream ||= Async::IO::Stream.new(@socket)
162
+ @protocol ||= Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
163
+ loop do
164
+ read_line
165
+ end
166
+ rescue Restart
167
+ log "Closing connection", level: :warning
168
+ raise
169
+ rescue Async::Wrapper::Cancelled
170
+ # ignore exceptions raised when a wait is aborted because a task is stopped
171
+ rescue EOFError, Async::Stop
172
+ log "Connection closed", level: :warning
173
+ rescue IOError => e
174
+ log "IOError: #{e}", level: :warning
175
+ rescue Errno::ECONNRESET
176
+ log "Connection reset by peer", level: :warning
177
+ rescue Errno::EPIPE
178
+ log "Broken pipe", level: :warning
179
+ rescue StandardError => e
180
+ notify_error e, level: :internal
181
+ end
182
+
183
+ def read_line
184
+ json = @protocol.read_line
185
+ beginning = Time.now
186
+ message = process_packet json
187
+ duration = Time.now - beginning
188
+ ms = (duration*1000).round(4)
189
+ if duration > 0
190
+ per_second = (1.0 / duration).round
191
+ else
192
+ per_second = Float::INFINITY
193
+ end
194
+ if message
195
+ type = message.type
196
+ m_id = Logger.shorten_message_id(message.m_id)
197
+ else
198
+ type = 'Unknown'
199
+ m_id = nil
149
200
  end
201
+ str = [type,m_id,"processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
202
+ log str, level: :statistics
150
203
  end
151
204
 
152
205
  def notify_error e, options={}
@@ -160,36 +213,40 @@ module RSMP
160
213
  end
161
214
 
162
215
  def start_timer
216
+ return if @timer
163
217
  name = "timer"
164
218
  interval = @site_settings['intervals']['timer'] || 1
165
219
  log "Starting #{name} with interval #{interval} seconds", level: :debug
166
220
  @latest_watchdog_received = Clock.now
167
-
168
221
  @timer = @task.async do |task|
169
222
  task.annotate "timer"
170
- next_time = Time.now.to_f
171
- loop do
172
- begin
173
- now = Clock.now
174
- timer(now)
175
- rescue RSMP::Schemer::Error => e
176
- puts "Timer: Schema error: #{e}"
177
- rescue EOFError => e
178
- log "Timer: Connection closed: #{e}", level: :warning
179
- rescue IOError => e
180
- log "Timer: IOError", level: :warning
181
- rescue Errno::ECONNRESET
182
- log "Timer: Connection reset by peer", level: :warning
183
- rescue Errno::EPIPE => e
184
- log "Timer: Broken pipe", level: :warning
185
- rescue StandardError => e
186
- notify_error e, level: :internal
187
- end
188
- ensure
189
- next_time += interval
190
- duration = next_time - Time.now.to_f
191
- task.sleep duration
223
+ run_timer task, interval
224
+ end
225
+ end
226
+
227
+ def run_timer task, interval
228
+ next_time = Time.now.to_f
229
+ loop do
230
+ begin
231
+ now = Clock.now
232
+ timer(now)
233
+ rescue RSMP::Schemer::Error => e
234
+ log "Timer: Schema error: #{e}", level: :warning
235
+ rescue EOFError => e
236
+ log "Timer: Connection closed: #{e}", level: :warning
237
+ rescue IOError => e
238
+ log "Timer: IOError", level: :warning
239
+ rescue Errno::ECONNRESET
240
+ log "Timer: Connection reset by peer", level: :warning
241
+ rescue Errno::EPIPE => e
242
+ log "Timer: Broken pipe", level: :warning
243
+ rescue StandardError => e
244
+ notify_error e, level: :internal
192
245
  end
246
+ ensure
247
+ next_time += interval
248
+ duration = next_time - Time.now.to_f
249
+ task.sleep duration
193
250
  end
194
251
  end
195
252
 
@@ -200,7 +257,7 @@ module RSMP
200
257
  end
201
258
 
202
259
  def watchdog_send_timer now
203
- return unless @watchdog_started
260
+ return unless @watchdog_started
204
261
  return if @site_settings['intervals']['watchdog'] == :never
205
262
  if @latest_watchdog_send_at == nil
206
263
  send_watchdog now
@@ -226,9 +283,13 @@ module RSMP
226
283
  @awaiting_acknowledgement.clone.each_pair do |m_id, message|
227
284
  latest = message.timestamp + timeout
228
285
  if now > latest
229
- log "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds", level: :error
230
- stop
231
- notify_error MissingAcknowledgment.new('No ack')
286
+ str = "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds"
287
+ log str, level: :error
288
+ begin
289
+ close
290
+ ensure
291
+ notify_error MissingAcknowledgment.new(str)
292
+ end
232
293
  end
233
294
  end
234
295
  end
@@ -238,16 +299,16 @@ module RSMP
238
299
  latest = @latest_watchdog_received + timeout
239
300
  left = latest - now
240
301
  if left < 0
241
- log "No Watchdog within #{timeout} seconds", level: :error
242
- stop
302
+ str = "No Watchdog within #{timeout} seconds"
303
+ log str, level: :error
304
+ begin
305
+ close # this will stop the current task (ourself)
306
+ ensure
307
+ notify_error MissingWatchdog.new(str) # but ensure block will still be reached
308
+ end
243
309
  end
244
310
  end
245
311
 
246
- def stop_tasks
247
- @timer.stop if @timer
248
- @reader.stop if @reader
249
- end
250
-
251
312
  def log str, options={}
252
313
  super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
253
314
  end
@@ -355,7 +416,7 @@ module RSMP
355
416
  str = "Rejected #{message.type},"
356
417
  notify_error e.exception(str), message: message
357
418
  dont_acknowledge message, str, reason
358
- stop
419
+ close
359
420
  message
360
421
  ensure
361
422
  node.clear_deferred
@@ -436,19 +497,14 @@ module RSMP
436
497
  send_message message, "for #{original.type} #{original.m_id_short}"
437
498
  end
438
499
 
439
- def set_state state
440
- @state = state
441
- @state_condition.signal @state
442
- end
443
-
444
- def wait_for_state state, timeout
500
+ def wait_for_state state, timeout:
445
501
  states = [state].flatten
446
502
  return if states.include?(@state)
447
- wait_for(@state_condition,timeout) do
503
+ wait_for_condition(@state_condition,timeout: timeout) do
448
504
  states.include?(@state)
449
505
  end
450
506
  @state
451
- rescue Async::TimeoutError
507
+ rescue RSMP::TimeoutError
452
508
  raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
453
509
  end
454
510
 
@@ -557,10 +613,10 @@ module RSMP
557
613
  end
558
614
  end
559
615
 
560
- def connection_complete
616
+ def handshake_complete
561
617
  set_state :ready
562
618
  end
563
-
619
+
564
620
  def version_acknowledged
565
621
  end
566
622
 
@@ -576,6 +632,7 @@ module RSMP
576
632
  collect_options = options[:collect] || options[:collect!]
577
633
  if collect_options
578
634
  task = @task.async do |task|
635
+ task.annotate 'send_and_optionally_collect'
579
636
  collector = yield collect_options # call block to create collector
580
637
  collector.collect
581
638
  collector.ok! if options[:collect!] # raise any errors if the bang version was specified
data/lib/rsmp/rsmp.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # Get the current time in UTC, with optional adjustment
2
- # Convertion to string uses the RSMP format 2015-06-08T12:01:39.654Z
2
+ # Convertion to string uses the RSMP format 2015-06-08T12:01:39.654Z
3
3
  # Note that using to_s on a my_clock.to_s will not produce an RSMP formatted timestamp,
4
4
  # you need to use Clock.to_s my_clock
5
5
 
data/lib/rsmp/site.rb CHANGED
@@ -15,6 +15,8 @@ module RSMP
15
15
  @proxies = []
16
16
  @sleep_condition = Async::Notification.new
17
17
  @proxies_condition = Async::Notification.new
18
+
19
+ build_proxies
18
20
  end
19
21
 
20
22
  def site_id
@@ -62,25 +64,29 @@ module RSMP
62
64
  RSMP::Schemer::find_schema! sxl, version, lenient: true
63
65
  end
64
66
 
65
- def reconnect
66
- @sleep_condition.signal
67
+ def run
68
+ log "Starting site #{@site_settings["site_id"]}",
69
+ level: :info,
70
+ timestamp: @clock.now
71
+ @proxies.each { |proxy| proxy.start }
72
+ @proxies.each { |proxy| proxy.wait }
67
73
  end
68
74
 
69
- def start_action
75
+ def build_proxies
70
76
  @site_settings["supervisors"].each do |supervisor_settings|
71
- @task.async do |task|
72
- task.annotate "site proxy"
73
- connect_to_supervisor task, supervisor_settings
74
- rescue StandardError => e
75
- notify_error e, level: :internal
76
- end
77
+ @proxies << SupervisorProxy.new({
78
+ site: self,
79
+ task: @task,
80
+ settings: @site_settings,
81
+ ip: supervisor_settings['ip'],
82
+ port: supervisor_settings['port'],
83
+ logger: @logger,
84
+ archive: @archive,
85
+ collect: @collect
86
+ })
77
87
  end
78
88
  end
79
89
 
80
- def build_proxy settings
81
- SupervisorProxy.new settings
82
- end
83
-
84
90
  def aggregated_status_changed component, options={}
85
91
  @proxies.each do |proxy|
86
92
  proxy.send_aggregated_status component, options if proxy.ready?
@@ -91,7 +97,7 @@ module RSMP
91
97
  proxy = build_proxy({
92
98
  site: self,
93
99
  task: @task,
94
- settings: @site_settings,
100
+ settings: @site_settings,
95
101
  ip: supervisor_settings['ip'],
96
102
  port: supervisor_settings['port'],
97
103
  logger: @logger,
@@ -99,63 +105,20 @@ module RSMP
99
105
  collect: @collect
100
106
  })
101
107
  @proxies << proxy
108
+ proxy.start
102
109
  @proxies_condition.signal
103
- run_site_proxy task, proxy
104
- ensure
105
- @proxies.delete proxy
106
- @proxies_condition.signal
107
- end
108
-
109
- def run_site_proxy task, proxy
110
- loop do
111
- proxy.run # run until disconnected
112
- rescue IOError => e
113
- log "Stream error: #{e}", level: :warning
114
- rescue StandardError => e
115
- notify_error e, level: :internal
116
- ensure
117
- begin
118
- if @site_settings['intervals']['watchdog'] != :no
119
- # sleep until waken by reconnect() or the reconnect interval passed
120
- proxy.set_state :wait_for_reconnect
121
- task.with_timeout(@site_settings['intervals']['watchdog']) do
122
- @sleep_condition.wait
123
- end
124
- else
125
- proxy.set_state :cannot_connect
126
- break
127
- end
128
- rescue Async::TimeoutError
129
- # ignore
130
- end
131
- end
132
110
  end
133
111
 
112
+ # stop
134
113
  def stop
135
114
  log "Stopping site #{@site_settings["site_id"]}", level: :info
136
- @proxies.each do |proxy|
137
- proxy.stop
138
- end
139
- @proxies.clear
140
115
  super
141
116
  end
142
-
143
- def starting
144
- log "Starting site #{@site_settings["site_id"]}",
145
- level: :info,
146
- timestamp: @clock.now
147
- end
148
-
149
- def alarm
150
- @proxies.each do |proxy|
151
- proxy.stop
152
- end
153
- end
154
117
 
155
118
  def wait_for_supervisor ip, timeout
156
119
  supervisor = find_supervisor ip
157
120
  return supervisor if supervisor
158
- wait_for(@proxy_condition,timeout) { find_supervisor ip }
121
+ wait_for_condition(@proxy_condition,timeout:timeout) { find_supervisor ip }
159
122
  rescue Async::TimeoutError
160
123
  raise RSMP::TimeoutError.new "Supervisor '#{ip}' did not connect within #{timeout}s"
161
124
  end
@@ -1,6 +1,6 @@
1
1
  # Handles a supervisor connection to a remote client
2
2
 
3
- module RSMP
3
+ module RSMP
4
4
  class SiteProxy < Proxy
5
5
  include Components
6
6
 
@@ -14,33 +14,37 @@ module RSMP
14
14
  @site_id = options[:site_id]
15
15
  end
16
16
 
17
+ # handle communication
18
+ # when we're created, the socket is already open
19
+ def run
20
+ set_state :connected
21
+ start_reader
22
+ wait_for_reader # run until disconnected
23
+ rescue RSMP::ConnectionError => e
24
+ log e, level: :error
25
+ rescue StandardError => e
26
+ notify_error e, level: :internal
27
+ ensure
28
+ close
29
+ end
30
+
17
31
  def revive options
18
32
  super options
19
33
  @supervisor = options[:supervisor]
20
34
  @settings = @supervisor.supervisor_settings.clone
21
35
  end
22
36
 
23
-
24
37
  def inspect
25
38
  "#<#{self.class.name}:#{self.object_id}, #{inspector(
26
39
  :@acknowledgements,:@settings,:@site_settings,:@components
27
40
  )}>"
28
41
  end
42
+
29
43
  def node
30
44
  supervisor
31
45
  end
32
46
 
33
- def start
34
- super
35
- start_reader
36
- end
37
-
38
- def stop
39
- log "Closing connection to site", level: :info
40
- super
41
- end
42
-
43
- def connection_complete
47
+ def handshake_complete
44
48
  super
45
49
  sanitized_sxl_version = RSMP::Schemer.sanitize_version(@site_sxl_version)
46
50
  log "Connection to site #{@site_id} established, using core #{@rsmp_version}, #{@sxl} #{sanitized_sxl_version}", level: :info
@@ -68,7 +72,7 @@ module RSMP
68
72
  else
69
73
  super message
70
74
  end
71
- rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, TimestampError => e
75
+ rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError
72
76
  str = "Rejected #{message.type} message,"
73
77
  dont_acknowledge message, str, "#{e}"
74
78
  notify_error e.exception("#{str}#{e.message} #{message.json}")
@@ -148,7 +152,7 @@ module RSMP
148
152
  end
149
153
 
150
154
  def version_acknowledged
151
- connection_complete
155
+ handshake_complete
152
156
  end
153
157
 
154
158
  def process_watchdog message
@@ -196,7 +200,7 @@ module RSMP
196
200
  def subscribe_to_status component_id, status_list, options={}
197
201
  validate_ready 'subscribe to status'
198
202
  m_id = options[:m_id] || RSMP::Message.make_m_id
199
-
203
+
200
204
  # additional items can be used when verifying the response,
201
205
  # but must to remove from the subscribe message
202
206
  subscribe_list = status_list.map { |item| item.slice('sCI','n','uRt') }
@@ -324,7 +328,7 @@ module RSMP
324
328
  log "Using site settings for guest", level: :debug
325
329
  return @settings['guest']
326
330
  end
327
-
331
+
328
332
  nil
329
333
  end
330
334