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.
- 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
|