rsmp 0.8.6 → 0.9.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.
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