ably 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.ruby-version.old +1 -0
- data/.travis.yml +0 -2
- data/Rakefile +22 -4
- data/SPEC.md +1676 -0
- data/ably.gemspec +1 -1
- data/lib/ably.rb +0 -8
- data/lib/ably/auth.rb +54 -46
- data/lib/ably/exceptions.rb +19 -5
- data/lib/ably/logger.rb +1 -1
- data/lib/ably/models/error_info.rb +1 -1
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +11 -9
- data/lib/ably/models/message.rb +15 -12
- data/lib/ably/models/message_encoders/base.rb +6 -5
- data/lib/ably/models/message_encoders/base64.rb +1 -0
- data/lib/ably/models/message_encoders/cipher.rb +6 -3
- data/lib/ably/models/message_encoders/json.rb +1 -0
- data/lib/ably/models/message_encoders/utf8.rb +2 -9
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_resource.rb +5 -2
- data/lib/ably/models/presence_message.rb +21 -12
- data/lib/ably/models/protocol_message.rb +22 -6
- data/lib/ably/modules/ably.rb +11 -0
- data/lib/ably/modules/async_wrapper.rb +2 -0
- data/lib/ably/modules/conversions.rb +23 -3
- data/lib/ably/modules/encodeable.rb +2 -1
- data/lib/ably/modules/enum.rb +2 -0
- data/lib/ably/modules/event_emitter.rb +7 -1
- data/lib/ably/modules/event_machine_helpers.rb +2 -0
- data/lib/ably/modules/http_helpers.rb +2 -0
- data/lib/ably/modules/model_common.rb +12 -2
- data/lib/ably/modules/state_emitter.rb +76 -0
- data/lib/ably/modules/state_machine.rb +53 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/ably/realtime.rb +4 -2
- data/lib/ably/realtime/channel.rb +51 -58
- data/lib/ably/realtime/channel/channel_manager.rb +91 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +68 -0
- data/lib/ably/realtime/client.rb +70 -26
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +31 -13
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/ably/realtime/connection.rb +135 -92
- data/lib/ably/realtime/connection/connection_manager.rb +216 -33
- data/lib/ably/realtime/connection/connection_state_machine.rb +30 -73
- data/lib/ably/realtime/models/nil_channel.rb +10 -1
- data/lib/ably/realtime/presence.rb +336 -92
- data/lib/ably/rest.rb +2 -2
- data/lib/ably/rest/channel.rb +13 -4
- data/lib/ably/rest/client.rb +138 -38
- data/lib/ably/rest/middleware/logger.rb +24 -3
- data/lib/ably/rest/presence.rb +12 -7
- data/lib/ably/version.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +101 -85
- data/spec/acceptance/realtime/channel_spec.rb +461 -120
- data/spec/acceptance/realtime/client_spec.rb +119 -0
- data/spec/acceptance/realtime/connection_failures_spec.rb +499 -0
- data/spec/acceptance/realtime/connection_spec.rb +571 -97
- data/spec/acceptance/realtime/message_spec.rb +347 -333
- data/spec/acceptance/realtime/presence_history_spec.rb +35 -40
- data/spec/acceptance/realtime/presence_spec.rb +769 -239
- data/spec/acceptance/realtime/stats_spec.rb +14 -22
- data/spec/acceptance/realtime/time_spec.rb +16 -20
- data/spec/acceptance/rest/auth_spec.rb +425 -364
- data/spec/acceptance/rest/base_spec.rb +108 -176
- data/spec/acceptance/rest/channel_spec.rb +89 -89
- data/spec/acceptance/rest/channels_spec.rb +30 -32
- data/spec/acceptance/rest/client_spec.rb +273 -0
- data/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/spec/acceptance/rest/message_spec.rb +186 -163
- data/spec/acceptance/rest/presence_spec.rb +150 -111
- data/spec/acceptance/rest/stats_spec.rb +45 -40
- data/spec/acceptance/rest/time_spec.rb +8 -10
- data/spec/rspec_config.rb +10 -1
- data/spec/shared/client_initializer_behaviour.rb +212 -0
- data/spec/{support/model_helper.rb → shared/model_behaviour.rb} +6 -6
- data/spec/{support/protocol_msgbus_helper.rb → shared/protocol_msgbus_behaviour.rb} +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/support/api_helper.rb +11 -0
- data/spec/support/event_machine_helper.rb +101 -3
- data/spec/support/markdown_spec_formatter.rb +90 -0
- data/spec/support/private_api_formatter.rb +36 -0
- data/spec/support/protocol_helper.rb +32 -0
- data/spec/support/random_helper.rb +15 -0
- data/spec/support/test_app.rb +4 -0
- data/spec/unit/auth_spec.rb +68 -0
- data/spec/unit/logger_spec.rb +77 -66
- data/spec/unit/models/error_info_spec.rb +1 -1
- data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +2 -3
- data/spec/unit/models/message_encoders/base64_spec.rb +2 -2
- data/spec/unit/models/message_encoders/cipher_spec.rb +2 -2
- data/spec/unit/models/message_encoders/utf8_spec.rb +2 -46
- data/spec/unit/models/message_spec.rb +160 -15
- data/spec/unit/models/paginated_resource_spec.rb +29 -27
- data/spec/unit/models/presence_message_spec.rb +163 -20
- data/spec/unit/models/protocol_message_spec.rb +43 -8
- data/spec/unit/modules/async_wrapper_spec.rb +2 -3
- data/spec/unit/modules/conversions_spec.rb +1 -1
- data/spec/unit/modules/enum_spec.rb +2 -3
- data/spec/unit/modules/event_emitter_spec.rb +62 -5
- data/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/spec/unit/realtime/channel_spec.rb +107 -2
- data/spec/unit/realtime/channels_spec.rb +1 -0
- data/spec/unit/realtime/client_spec.rb +8 -48
- data/spec/unit/realtime/connection_spec.rb +3 -3
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +2 -2
- data/spec/unit/realtime/presence_spec.rb +13 -4
- data/spec/unit/realtime/realtime_spec.rb +0 -11
- data/spec/unit/realtime/websocket_transport_spec.rb +2 -2
- data/spec/unit/rest/channel_spec.rb +109 -0
- data/spec/unit/rest/channels_spec.rb +4 -3
- data/spec/unit/rest/client_spec.rb +30 -125
- data/spec/unit/rest/rest_spec.rb +10 -0
- data/spec/unit/util/crypto_spec.rb +10 -5
- data/spec/unit/util/pub_sub_spec.rb +5 -5
- metadata +44 -12
- data/spec/integration/modules/state_emitter_spec.rb +0 -80
- 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
|
-
|
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
|
-
|
16
|
-
|
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
|
40
|
-
logger.
|
41
|
-
connection.transition_state_machine
|
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
|
-
|
71
|
-
|
72
|
-
unsubscribe_from_transport_events transport
|
107
|
+
connection.send_protocol_message(action: Ably::Models::ProtocolMessage::ACTION.Close)
|
73
108
|
|
74
|
-
|
75
|
-
|
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
|
-
#
|
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
|
84
|
-
|
117
|
+
def force_close_connection
|
118
|
+
destroy_transport
|
119
|
+
connection.transition_state_machine :closed
|
85
120
|
end
|
86
121
|
|
87
|
-
#
|
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
|
92
|
-
|
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
|
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
|
99
|
-
|
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
|
102
|
-
|
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
|
-
|
106
|
-
|
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
|
129
|
-
if
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
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.
|
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 '
|
1
|
+
require 'ably/modules/state_machine'
|
2
2
|
|
3
3
|
module Ably::Realtime
|
4
4
|
class Connection
|
5
|
-
|
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
|
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, :
|
52
|
-
transition :from => :connecting, :to => [:connected, :failed, :
|
53
|
-
transition :from => :connected, :to => [:disconnected, :suspended, :
|
54
|
-
transition :from => :disconnected, :to => [:connecting, :
|
55
|
-
transition :from => :suspended, :to => [:connecting, :
|
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
|
-
|
72
|
-
connection.manager.
|
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
|
76
|
-
connection.manager.
|
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
|
-
|
84
|
-
connection.manager.
|
60
|
+
after_transition(to: [:closing], from: [:initialized, :disconnected, :suspended]) do |connection|
|
61
|
+
connection.manager.force_close_connection
|
85
62
|
end
|
86
63
|
|
87
|
-
|
64
|
+
after_transition(to: [:closing], from: [:connecting, :connected]) do |connection|
|
88
65
|
connection.manager.close_connection
|
89
66
|
end
|
90
67
|
|
91
|
-
|
92
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
105
|
-
|
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
|