ably 0.6.2 → 0.7.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.ruby-version.old +1 -0
  4. data/.travis.yml +0 -2
  5. data/Rakefile +22 -4
  6. data/SPEC.md +1676 -0
  7. data/ably.gemspec +1 -1
  8. data/lib/ably.rb +0 -8
  9. data/lib/ably/auth.rb +54 -46
  10. data/lib/ably/exceptions.rb +19 -5
  11. data/lib/ably/logger.rb +1 -1
  12. data/lib/ably/models/error_info.rb +1 -1
  13. data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
  14. data/lib/ably/models/message.rb +15 -12
  15. data/lib/ably/models/message_encoders/base.rb +6 -5
  16. data/lib/ably/models/message_encoders/base64.rb +1 -0
  17. data/lib/ably/models/message_encoders/cipher.rb +6 -3
  18. data/lib/ably/models/message_encoders/json.rb +1 -0
  19. data/lib/ably/models/message_encoders/utf8.rb +2 -9
  20. data/lib/ably/models/nil_logger.rb +20 -0
  21. data/lib/ably/models/paginated_resource.rb +5 -2
  22. data/lib/ably/models/presence_message.rb +21 -12
  23. data/lib/ably/models/protocol_message.rb +22 -6
  24. data/lib/ably/modules/ably.rb +11 -0
  25. data/lib/ably/modules/async_wrapper.rb +2 -0
  26. data/lib/ably/modules/conversions.rb +23 -3
  27. data/lib/ably/modules/encodeable.rb +2 -1
  28. data/lib/ably/modules/enum.rb +2 -0
  29. data/lib/ably/modules/event_emitter.rb +7 -1
  30. data/lib/ably/modules/event_machine_helpers.rb +2 -0
  31. data/lib/ably/modules/http_helpers.rb +2 -0
  32. data/lib/ably/modules/model_common.rb +12 -2
  33. data/lib/ably/modules/state_emitter.rb +76 -0
  34. data/lib/ably/modules/state_machine.rb +53 -0
  35. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  36. data/lib/ably/modules/uses_state_machine.rb +74 -0
  37. data/lib/ably/realtime.rb +4 -2
  38. data/lib/ably/realtime/channel.rb +51 -58
  39. data/lib/ably/realtime/channel/channel_manager.rb +91 -0
  40. data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
  41. data/lib/ably/realtime/client.rb +70 -26
  42. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
  43. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  44. data/lib/ably/realtime/connection.rb +135 -92
  45. data/lib/ably/realtime/connection/connection_manager.rb +216 -33
  46. data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
  47. data/lib/ably/realtime/models/nil_channel.rb +10 -1
  48. data/lib/ably/realtime/presence.rb +336 -92
  49. data/lib/ably/rest.rb +2 -2
  50. data/lib/ably/rest/channel.rb +13 -4
  51. data/lib/ably/rest/client.rb +138 -38
  52. data/lib/ably/rest/middleware/logger.rb +24 -3
  53. data/lib/ably/rest/presence.rb +12 -7
  54. data/lib/ably/version.rb +1 -1
  55. data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
  56. data/spec/acceptance/realtime/channel_spec.rb +461 -120
  57. data/spec/acceptance/realtime/client_spec.rb +119 -0
  58. data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
  59. data/spec/acceptance/realtime/connection_spec.rb +571 -97
  60. data/spec/acceptance/realtime/message_spec.rb +347 -333
  61. data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
  62. data/spec/acceptance/realtime/presence_spec.rb +769 -239
  63. data/spec/acceptance/realtime/stats_spec.rb +14 -22
  64. data/spec/acceptance/realtime/time_spec.rb +16 -20
  65. data/spec/acceptance/rest/auth_spec.rb +425 -364
  66. data/spec/acceptance/rest/base_spec.rb +108 -176
  67. data/spec/acceptance/rest/channel_spec.rb +89 -89
  68. data/spec/acceptance/rest/channels_spec.rb +30 -32
  69. data/spec/acceptance/rest/client_spec.rb +273 -0
  70. data/spec/acceptance/rest/encoders_spec.rb +185 -0
  71. data/spec/acceptance/rest/message_spec.rb +186 -163
  72. data/spec/acceptance/rest/presence_spec.rb +150 -111
  73. data/spec/acceptance/rest/stats_spec.rb +45 -40
  74. data/spec/acceptance/rest/time_spec.rb +8 -10
  75. data/spec/rspec_config.rb +10 -1
  76. data/spec/shared/client_initializer_behaviour.rb +212 -0
  77. data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
  78. data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
  79. data/spec/spec_helper.rb +9 -0
  80. data/spec/support/api_helper.rb +11 -0
  81. data/spec/support/event_machine_helper.rb +101 -3
  82. data/spec/support/markdown_spec_formatter.rb +90 -0
  83. data/spec/support/private_api_formatter.rb +36 -0
  84. data/spec/support/protocol_helper.rb +32 -0
  85. data/spec/support/random_helper.rb +15 -0
  86. data/spec/support/test_app.rb +4 -0
  87. data/spec/unit/auth_spec.rb +68 -0
  88. data/spec/unit/logger_spec.rb +77 -66
  89. data/spec/unit/models/error_info_spec.rb +1 -1
  90. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
  91. data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
  92. data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
  93. data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
  94. data/spec/unit/models/message_spec.rb +160 -15
  95. data/spec/unit/models/paginated_resource_spec.rb +29 -27
  96. data/spec/unit/models/presence_message_spec.rb +163 -20
  97. data/spec/unit/models/protocol_message_spec.rb +43 -8
  98. data/spec/unit/modules/async_wrapper_spec.rb +2 -3
  99. data/spec/unit/modules/conversions_spec.rb +1 -1
  100. data/spec/unit/modules/enum_spec.rb +2 -3
  101. data/spec/unit/modules/event_emitter_spec.rb +62 -5
  102. data/spec/unit/modules/state_emitter_spec.rb +283 -0
  103. data/spec/unit/realtime/channel_spec.rb +107 -2
  104. data/spec/unit/realtime/channels_spec.rb +1 -0
  105. data/spec/unit/realtime/client_spec.rb +8 -48
  106. data/spec/unit/realtime/connection_spec.rb +3 -3
  107. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
  108. data/spec/unit/realtime/presence_spec.rb +13 -4
  109. data/spec/unit/realtime/realtime_spec.rb +0 -11
  110. data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
  111. data/spec/unit/rest/channel_spec.rb +109 -0
  112. data/spec/unit/rest/channels_spec.rb +4 -3
  113. data/spec/unit/rest/client_spec.rb +30 -125
  114. data/spec/unit/rest/rest_spec.rb +10 -0
  115. data/spec/unit/util/crypto_spec.rb +10 -5
  116. data/spec/unit/util/pub_sub_spec.rb +5 -5
  117. metadata +44 -12
  118. data/spec/integration/modules/state_emitter_spec.rb +0 -80
  119. data/spec/integration/rest/auth.rb +0 -9
@@ -2,18 +2,45 @@ module Ably::Realtime
2
2
  class Connection
3
3
  # ConnectionManager is responsible for all actions relating to underlying connection and transports,
4
4
  # such as opening, closing, attempting reconnects etc.
5
+ # Connection state changes are performed by this class and executed from {ConnectionStateMachine}
5
6
  #
6
7
  # This is a private class and should never be used directly by developers as the API is likely to change in future.
7
8
  #
8
9
  # @api private
9
10
  class ConnectionManager
10
- CONNECTION_FAILED = { retry_after: 0.5, max_retries: 2, code: 80000 }.freeze
11
+ # Configuration for automatic recovery of failed connection attempts
12
+ CONNECT_RETRY_CONFIG = {
13
+ disconnected: { retry_every: 0.5, max_time_in_state: 10 },
14
+ suspended: { retry_every: 5, max_time_in_state: 60 }
15
+ }.freeze
16
+
17
+ # Time to wait following a connection state request before it's considered a failure
18
+ TIMEOUTS = {
19
+ open: 15,
20
+ close: 10
21
+ }
22
+
23
+ # Error codes from the server that can potentially be resolved
24
+ RESOLVABLE_ERROR_CODES = {
25
+ token_expired: 40140
26
+ }
11
27
 
12
28
  def initialize(connection)
13
29
  @connection = connection
30
+ @timers = Hash.new { |hash, key| hash[key] = [] }
31
+
32
+ connection.on(:closed) do
33
+ connection.reset_resume_info
34
+ end
35
+
36
+ connection.once(:connecting) do
37
+ close_connection_when_reactor_is_stopped
38
+ end
14
39
 
15
- @timers = Hash.new { |hash, key| hash[key] = [] }
16
- @timers[:initializer] << EventMachine::Timer.new(0.01) { connection.connect }
40
+ EventMachine.next_tick do
41
+ # Connect once Connection object is initialised
42
+ connection.connect if client.connect_automatically
43
+ end
17
44
  end
18
45
 
19
46
  # Creates and sets up a new {Ably::Realtime::Connection::WebsocketTransport} available on attribute #transport
@@ -25,20 +52,30 @@ module Ably::Realtime
25
52
  raise RuntimeError, 'Existing WebsocketTransport is connected, and must be closed first'
26
53
  end
27
54
 
55
+ unless client.auth.authentication_security_requirements_met?
56
+ connection.transition_state_machine :failed, Ably::Exceptions::InsecureRequestError.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
57
+ return
58
+ end
59
+
28
60
  logger.debug "ConnectionManager: Opening connection to #{connection.host}:#{connection.port}"
29
61
 
30
62
  connection.create_websocket_transport do |websocket_transport|
31
63
  subscribe_to_transport_events websocket_transport
32
64
  yield websocket_transport if block_given?
33
65
  end
66
+
67
+ logger.debug 'ConnectionManager: Setting up automatic connection timeout timer for #{TIMEOUTS.fetch(:open)}s'
68
+ create_timeout_timer_whilst_in_state(:connect, TIMEOUTS.fetch(:open)) do
69
+ connection_opening_failed Ably::Exceptions::ConnectionTimeoutError.new("Connection to Ably timed out after #{TIMEOUTS.fetch(:open)}s", nil, 80014)
70
+ end
34
71
  end
35
72
 
36
73
  # Called by the transport when a connection attempt fails
37
74
  #
38
75
  # @api private
39
- def connection_failed(error)
40
- logger.info "ConnectionManager: Connection to #{connection.host}:#{connection.port} failed; #{error.message}"
41
- connection.transition_state_machine :disconnected, Ably::Models::ErrorInfo.new(message: "Connection failed; #{error.message}", code: 80000)
76
+ def connection_opening_failed(error)
77
+ logger.warn "ConnectionManager: Connection to #{connection.host}:#{connection.port} failed; #{error.message}"
78
+ connection.transition_state_machine next_retry_state, Ably::Exceptions::ConnectionError.new("Connection failed; #{error.message}", nil, 80000)
42
79
  end
43
80
 
44
81
  # Ensures the underlying transport has been disconnected and all event emitter callbacks removed
@@ -67,43 +104,75 @@ module Ably::Realtime
67
104
  #
68
105
  # @api private
69
106
  def close_connection
70
- protocol_message = connection.send_protocol_message(action: Ably::Models::ProtocolMessage::ACTION.Close)
71
-
72
- unsubscribe_from_transport_events transport
107
+ connection.send_protocol_message(action: Ably::Models::ProtocolMessage::ACTION.Close)
73
108
 
74
- protocol_message.callback do
75
- destroy_transport
109
+ create_timeout_timer_whilst_in_state(:close, TIMEOUTS.fetch(:close)) do
110
+ force_close_connection if connection.closing?
76
111
  end
77
112
  end
78
113
 
79
- # Remove all timers set up as part of the initialize process.
80
- # Typically called by StateMachine when connection is closed and can no longer process the timers
114
+ # Close the underlying transport immediately and set the connection state to closed
81
115
  #
82
116
  # @api private
83
- def cancel_initialized_timers
84
- clear_timers :initializer
117
+ def force_close_connection
118
+ destroy_transport
119
+ connection.transition_state_machine :closed
85
120
  end
86
121
 
87
- # Remove all timers related to connection attempt retries following a disconnect or suspended connection state.
88
- # Typically called by StateMachine when connection is opened to ensure no further connection attempts are made
122
+ # When a connection is disconnected whilst connecting, attempt reconnect and/or set state to :suspended or :failed
89
123
  #
90
124
  # @api private
91
- def cancel_connection_retry_timers
92
- clear_timers :connection_retry_timers
125
+ def respond_to_transport_disconnected_when_connecting(current_transition)
126
+ return unless connection.disconnected? || connection.suspended? # do nothing if state has changed through an explicit request
127
+ return unless retry_connection? # do not always reattempt connection or change state as client may be re-authorising
128
+
129
+ unless connection_retry_from_suspended_state?
130
+ return if connection_retry_for(:disconnected, ignore_states: [:connecting])
131
+ end
132
+
133
+ return if connection_retry_for(:suspended, ignore_states: [:connecting])
134
+
135
+ # Fallback if no other criteria met
136
+ connection.transition_state_machine :failed, current_transition.metadata
93
137
  end
94
138
 
95
- # When a connection is disconnected try and reconnect or set the connection state to :failed
139
+ # When a connection is disconnected after connecting, attempt reconnect and/or set state to :suspended or :failed
96
140
  #
97
141
  # @api private
98
- def respond_to_transport_disconnected(current_transition)
99
- error_code = current_transition && current_transition.metadata && current_transition.metadata.code
142
+ def respond_to_transport_disconnected_whilst_connected(current_transition)
143
+ logger.warn "ConnectionManager: Connection to #{connection.transport.url} was disconnected unexpectedly"
100
144
 
101
- if connection.previous_state == :connecting && error_code == CONNECTION_FAILED[:code]
102
- return if retry_connection_failed
145
+ if current_transition.metadata.kind_of?(Ably::Models::ErrorInfo)
146
+ connection.trigger :error, current_transition.metadata
147
+ logger.error "ConnectionManager: Error received when disconnected within ProtocolMessage - #{current_transition.metadata}"
103
148
  end
104
149
 
105
- # Fallback if no other criteria met
106
- connection.transition_state_machine :failed, current_transition.metadata
150
+ destroy_transport
151
+ respond_to_transport_disconnected_when_connecting current_transition
152
+ end
153
+
154
+ # {Ably::Models::ProtocolMessage ProtocolMessage Error} received from server.
155
+ # Some error states can be resolved by the client library.
156
+ #
157
+ # @api private
158
+ def error_received_from_server(error)
159
+ case error.code
160
+ when RESOLVABLE_ERROR_CODES.fetch(:token_expired)
161
+ connection.transition_state_machine :disconnected
162
+ connection.once_or_if(:disconnected) do
163
+ renew_token_and_reconnect error
164
+ end
165
+ else
166
+ logger.error "ConnectionManager: Error #{error.class.name} code #{error.code} received from server '#{error.message}', transitioning to failed state"
167
+ connection.transition_state_machine :failed, error
168
+ end
169
+ end
170
+
171
+ # Number of consecutive attempts for provided state
172
+ # @return [Integer]
173
+ # @api private
174
+ def retry_count_for_state(state)
175
+ retries_for_state(state, ignore_states: [:connecting]).count
107
176
  end
108
177
 
109
178
  private
@@ -121,20 +190,79 @@ module Ably::Realtime
121
190
  connection.client
122
191
  end
123
192
 
193
+ # Create a timer that will execute in timeout_in seconds.
194
+ # If the connection state changes however, cancel the timer
195
+ def create_timeout_timer_whilst_in_state(timer_id, timeout_in, &block)
196
+ raise 'Block required for timer' unless block_given?
197
+
198
+ timers[timer_id] << EventMachine::Timer.new(timeout_in) do
199
+ block.call
200
+ end
201
+ connection.once_state_changed { clear_timers timer_id }
202
+ end
203
+
124
204
  def clear_timers(key)
125
205
  timers.fetch(key, []).each(&:cancel)
126
206
  end
127
207
 
128
- def retry_connection_failed
129
- if retries_for_state(:disconnected, ignore_states: [:connecting]).count < CONNECTION_FAILED[:max_retries]
130
- logger.debug "ConnectionManager: Pausing for #{CONNECTION_FAILED[:retry_after]}s before attempting to reconnect"
131
- @timers[:connection_retry_timers] << EventMachine::Timer.new(CONNECTION_FAILED[:retry_after]) do
132
- connection.connect
208
+ def next_retry_state
209
+ if connection_retry_from_suspended_state? || time_passed_since_disconnected > CONNECT_RETRY_CONFIG.fetch(:disconnected).fetch(:max_time_in_state)
210
+ :suspended
211
+ else
212
+ :disconnected
213
+ end
214
+ end
215
+
216
+ def connection_retry_from_suspended_state?
217
+ !retries_for_state(:suspended, ignore_states: [:connecting]).empty?
218
+ end
219
+
220
+ def time_passed_since_disconnected
221
+ time_spent_attempting_state(:disconnected, ignore_states: [:connecting])
222
+ end
223
+
224
+ # Reattempt a connection with a delay based on the CONNECT_RETRY_CONFIG for `from_state`
225
+ #
226
+ # @return [Boolean] True if a connection attempt has been set up, false if no further connection attempts can be made for this state
227
+ #
228
+ def connection_retry_for(from_state, options = {})
229
+ retry_params = CONNECT_RETRY_CONFIG.fetch(from_state)
230
+
231
+ if time_spent_attempting_state(from_state, options) <= retry_params.fetch(:max_time_in_state)
232
+ logger.debug "ConnectionManager: Pausing for #{retry_params.fetch(:retry_every)}s before attempting to reconnect"
233
+ create_timeout_timer_whilst_in_state(:reconnect, retry_params.fetch(:retry_every)) do
234
+ connection.connect if connection.state == from_state
133
235
  end
236
+ true
134
237
  end
135
238
  end
136
239
 
137
- def retries_for_state(state, ignore_states: [])
240
+ # Returns a float representing the amount of time passed since the first consecutive attempt of this state
241
+ #
242
+ # @param (see #retries_for_state)
243
+ # @return [Float] time passed in seconds
244
+ #
245
+ def time_spent_attempting_state(state, options)
246
+ states = retries_for_state(state, options)
247
+ if states.empty?
248
+ 0
249
+ else
250
+ Time.now.to_f - states.last[:transitioned_at].to_f
251
+ end.to_f
252
+ end
253
+
254
+ # Checks the state change history for the current connection and returns all matching consecutive states.
255
+ # This is useful to determine the number of retries of a particular state on a connection.
256
+ #
257
+ # @param state [Symbol]
258
+ # @param options [Hash]
259
+ # @option options [Array<Symbol>] :ignore_states states that should be ignored when determining consecutive historical retries for `state`.
260
+ # For example, when working out :connecting attempts, :disconnect state changes should be ignored as they are a side effect of a failed :connecting
261
+ #
262
+ # @return [Array<Hash>] Array of consecutive state attempts matching `state` in order of transitioned_at desc
263
+ #
264
+ def retries_for_state(state, options)
265
+ ignore_states = options.fetch(:ignore_states, [])
138
266
  allowed_states = Array(state) + Array(ignore_states)
139
267
 
140
268
  connection.state_history.reverse.take_while do |transition|
@@ -150,7 +278,52 @@ module Ably::Realtime
150
278
  end
151
279
 
152
280
  transport.on(:disconnected) do
153
- connection.transition_state_machine :disconnected
281
+ if connection.closing?
282
+ connection.transition_state_machine :closed
283
+ elsif !connection.closed? && !connection.disconnected?
284
+ connection.transition_state_machine :disconnected
285
+ end
286
+ end
287
+ end
288
+
289
+ def renew_token_and_reconnect(error)
290
+ if client.auth.token_renewable?
291
+ if @renewing_token
292
+ connection.transition_state_machine :failed, error
293
+ return
294
+ end
295
+
296
+ @renewing_token = true
297
+ logger.warn "ConnectionManager: Token has expired and is renewable, renewing token now"
298
+
299
+ operation = proc do
300
+ begin
301
+ client.auth.authorise
302
+ rescue StandardError => auth_error
303
+ connection.transition_state_machine :failed, auth_error
304
+ nil
305
+ end
306
+ end
307
+
308
+ callback = proc do |token|
309
+ state_changed_callback = proc do
310
+ @renewing_token = false
311
+ connection.off &state_changed_callback
312
+ end
313
+
314
+ connection.once :connected, :closed, :failed, &state_changed_callback
315
+
316
+ if token && !token.expired?
317
+ reconnect_transport
318
+ else
319
+ connection.transition_state_machine :failed, error unless connection.failed?
320
+ end
321
+ end
322
+
323
+ EventMachine.defer operation, callback
324
+ else
325
+ logger.warn "ConnectionManager: Token has expired and is not renewable"
326
+ connection.transition_state_machine :failed, error
154
327
  end
155
328
  end
156
329
 
@@ -160,6 +333,16 @@ module Ably::Realtime
160
333
  logger.debug "ConnectionManager: Unsubscribed from all events from current transport"
161
334
  end
162
335
 
336
+ def close_connection_when_reactor_is_stopped
337
+ EventMachine.add_shutdown_hook do
338
+ connection.close unless connection.closed? || connection.failed?
339
+ end
340
+ end
341
+
342
+ def retry_connection?
343
+ !@renewing_token
344
+ end
345
+
163
346
  def logger
164
347
  connection.logger
165
348
  end
@@ -1,40 +1,10 @@
1
- require 'statesman'
1
+ require 'ably/modules/state_machine'
2
2
 
3
3
  module Ably::Realtime
4
4
  class Connection
5
- module StatesmanMonkeyPatch
6
- # Override Statesman's #before_transition to support :from arrays
7
- # This can be removed once https://github.com/gocardless/statesman/issues/95 is solved
8
- def before_transition(options = nil, &block)
9
- arrayify_transition(options) do |options_without_from_array|
10
- super *options_without_from_array, &block
11
- end
12
- end
13
-
14
- def after_transition(options = nil, &block)
15
- arrayify_transition(options) do |options_without_from_array|
16
- super *options_without_from_array, &block
17
- end
18
- end
19
-
20
- private
21
- def arrayify_transition(options, &block)
22
- if options.nil?
23
- yield []
24
- elsif options.fetch(:from, nil).kind_of?(Array)
25
- options[:from].each do |from_state|
26
- yield [options.merge(from: from_state)]
27
- end
28
- else
29
- yield [options]
30
- end
31
- end
32
- end
33
-
34
- # Internal class to manage connection state, recovery and state transitions for an {Ably::Realtime::Connection}
5
+ # Internal class to manage connection state, recovery and state transitions for {Ably::Realtime::Connection}
35
6
  class ConnectionStateMachine
36
- include Statesman::Machine
37
- extend StatesmanMonkeyPatch
7
+ include Ably::Modules::StateMachine
38
8
 
39
9
  # States supported by this StateMachine match #{Connection::STATE}s
40
10
  # :initialized
@@ -42,17 +12,19 @@ module Ably::Realtime
42
12
  # :connected
43
13
  # :disconnected
44
14
  # :suspended
15
+ # :closing
45
16
  # :closed
46
17
  # :failed
47
18
  Connection::STATE.each_with_index do |state_enum, index|
48
19
  state state_enum.to_sym, initial: index == 0
49
20
  end
50
21
 
51
- transition :from => :initialized, :to => [:connecting, :closed]
52
- transition :from => :connecting, :to => [:connected, :failed, :closed, :disconnected]
53
- transition :from => :connected, :to => [:disconnected, :suspended, :closed, :failed]
54
- transition :from => :disconnected, :to => [:connecting, :closed, :failed]
55
- transition :from => :suspended, :to => [:connecting, :closed, :failed]
22
+ transition :from => :initialized, :to => [:connecting, :closing]
23
+ transition :from => :connecting, :to => [:connected, :failed, :closing, :disconnected, :suspended]
24
+ transition :from => :connected, :to => [:disconnected, :suspended, :closing, :failed]
25
+ transition :from => :disconnected, :to => [:connecting, :closing, :suspended, :failed]
26
+ transition :from => :suspended, :to => [:connecting, :closing, :failed]
27
+ transition :from => :closing, :to => [:closed]
56
28
  transition :from => :closed, :to => [:connecting]
57
29
  transition :from => :failed, :to => [:connecting]
58
30
 
@@ -68,60 +40,45 @@ module Ably::Realtime
68
40
  connection.manager.reconnect_transport
69
41
  end
70
42
 
71
- before_transition(to: [:connected]) do |connection|
72
- connection.manager.cancel_connection_retry_timers
43
+ after_transition(to: [:disconnected, :suspended], from: [:connecting]) do |connection, current_transition|
44
+ connection.manager.respond_to_transport_disconnected_when_connecting current_transition
45
+ end
46
+
47
+ after_transition(to: [:disconnected], from: [:connected]) do |connection, current_transition|
48
+ connection.manager.respond_to_transport_disconnected_whilst_connected current_transition
73
49
  end
74
50
 
75
- after_transition(to: [:disconnected], from: [:connecting]) do |connection, current_transition|
76
- connection.manager.respond_to_transport_disconnected current_transition
51
+ after_transition(to: [:disconnected, :suspended]) do |connection|
52
+ connection.manager.destroy_transport # never reuse a transport if the connection has failed
77
53
  end
78
54
 
79
- after_transition(to: [:failed]) do |connection|
55
+ after_transition(to: [:failed]) do |connection, current_transition|
56
+ connection.logger.fatal "ConnectionStateMachine: Connection failed - #{current_transition.metadata}"
80
57
  connection.manager.destroy_transport
81
58
  end
82
59
 
83
- before_transition(to: [:closed], from: [:initialized]) do |connection|
84
- connection.manager.cancel_initialized_timers
60
+ after_transition(to: [:closing], from: [:initialized, :disconnected, :suspended]) do |connection|
61
+ connection.manager.force_close_connection
85
62
  end
86
63
 
87
- before_transition(to: [:closed], from: [:connecting, :connected, :disconnected, :suspended]) do |connection|
64
+ after_transition(to: [:closing], from: [:connecting, :connected]) do |connection|
88
65
  connection.manager.close_connection
89
66
  end
90
67
 
91
- # Override Statesman's #transition_to so that:
92
- # * log state change failures to {Logger}
93
- def transition_to(state, *args)
94
- unless result = super(state, *args)
95
- logger.fatal "ConnectionStateMachine: Unable to transition from #{current_state} => #{state}"
96
- end
97
- result
68
+ before_transition(to: [:closed], from: [:closing]) do |connection|
69
+ connection.manager.destroy_transport
98
70
  end
99
71
 
100
- def previous_transition
101
- history[-2]
72
+ # Transitions responsible for updating connection#error_reason
73
+ before_transition(to: [:disconnected, :suspended, :failed]) do |connection, current_transition|
74
+ connection.set_failed_connection_error_reason current_transition.metadata
102
75
  end
103
76
 
104
- def previous_state
105
- previous_transition.to_state if previous_transition
77
+ before_transition(to: [:connected, :closed]) do |connection, current_transition|
78
+ connection.set_failed_connection_error_reason nil
106
79
  end
107
80
 
108
81
  private
109
- # TODO: Implement once CLOSED ProtocolMessage is sent back from Ably in response to a CLOSE message
110
- #
111
- # FORCE_CONNECTION_CLOSED_TIMEOUT = 5
112
- #
113
- # def force_closed_unless_server_acknowledge_closed
114
- # timeouts[:close_connection] << EventMachine::Timer.new(FORCE_CONNECTION_CLOSED_TIMEOUT) do
115
- # transition_to :closed
116
- # end
117
- # end
118
- #
119
- # def clear_force_closed_timeouts
120
- # timeouts[:close_connection].each do |timeout|
121
- # timeout.cancel
122
- # end.clear
123
- # end
124
-
125
82
  def connection
126
83
  object
127
84
  end