ably-rest 0.9.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ably-rest.gemspec +2 -1
- data/lib/submodules/ably-ruby/.travis.yml +6 -4
- data/lib/submodules/ably-ruby/CHANGELOG.md +52 -61
- data/lib/submodules/ably-ruby/README.md +10 -0
- data/lib/submodules/ably-ruby/SPEC.md +1473 -852
- data/lib/submodules/ably-ruby/ably.gemspec +2 -1
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +57 -25
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +10 -1
- data/lib/submodules/ably-ruby/lib/ably/models/auth_details.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +18 -4
- data/lib/submodules/ably-ruby/lib/ably/models/connection_details.rb +6 -3
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +4 -3
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +12 -1
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +101 -97
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +13 -1
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +20 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +17 -7
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +29 -14
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +7 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -4
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +7 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +2 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +79 -31
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +62 -26
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +154 -65
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +14 -15
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +16 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +38 -29
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +6 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +108 -49
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +165 -59
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +22 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +19 -10
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +67 -45
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +198 -36
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_manager.rb +30 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/presence_state_machine.rb +5 -12
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +21 -8
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +1 -3
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/util/safe_deferrable.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +416 -99
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +1011 -160
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +458 -27
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +436 -97
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +52 -23
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +5 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1160 -105
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +151 -22
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +88 -27
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +42 -15
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +4 -4
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +2 -1
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +2 -2
- data/lib/submodules/ably-ruby/spec/shared/safe_deferrable_behaviour.rb +6 -2
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +20 -4
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +32 -1
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +4 -11
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +28 -2
- data/lib/submodules/ably-ruby/spec/unit/models/auth_details_spec.rb +49 -0
- data/lib/submodules/ably-ruby/spec/unit/models/channel_state_change_spec.rb +23 -3
- data/lib/submodules/ably-ruby/spec/unit/models/connection_details_spec.rb +12 -1
- data/lib/submodules/ably-ruby/spec/unit/models/connection_state_change_spec.rb +15 -4
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +34 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +73 -2
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +64 -6
- data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +2 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +69 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +149 -22
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +9 -3
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +8 -5
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +4 -3
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +3 -3
- metadata +7 -5
@@ -12,13 +12,12 @@ module Ably::Realtime
|
|
12
12
|
class ConnectionManager
|
13
13
|
# Error codes from the server that can potentially be resolved
|
14
14
|
RESOLVABLE_ERROR_CODES = {
|
15
|
-
token_expired: Ably::
|
15
|
+
token_expired: Ably::Exceptions::TOKEN_EXPIRED_CODE
|
16
16
|
}
|
17
17
|
|
18
18
|
def initialize(connection)
|
19
19
|
@connection = connection
|
20
20
|
@timers = Hash.new { |hash, key| hash[key] = [] }
|
21
|
-
@renewing_token = false
|
22
21
|
|
23
22
|
connection.unsafe_on(:closed) do
|
24
23
|
connection.reset_resume_info
|
@@ -48,7 +47,7 @@ module Ably::Realtime
|
|
48
47
|
return
|
49
48
|
end
|
50
49
|
|
51
|
-
logger.debug 'ConnectionManager: Opening a websocket transport connection'
|
50
|
+
logger.debug { 'ConnectionManager: Opening a websocket transport connection' }
|
52
51
|
|
53
52
|
connection.create_websocket_transport.tap do |socket_deferrable|
|
54
53
|
socket_deferrable.callback do |websocket_transport|
|
@@ -60,7 +59,7 @@ module Ably::Realtime
|
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
63
|
-
logger.debug "ConnectionManager: Setting up automatic connection timeout timer for #{realtime_request_timeout}s"
|
62
|
+
logger.debug { "ConnectionManager: Setting up automatic connection timeout timer for #{realtime_request_timeout}s" }
|
64
63
|
create_timeout_timer_whilst_in_state(:connecting, realtime_request_timeout) do
|
65
64
|
connection_opening_failed Ably::Exceptions::ConnectionTimeout.new("Connection to Ably timed out after #{realtime_request_timeout}s", nil, 80014)
|
66
65
|
end
|
@@ -70,34 +69,72 @@ module Ably::Realtime
|
|
70
69
|
#
|
71
70
|
# @api private
|
72
71
|
def connection_opening_failed(error)
|
73
|
-
if error.kind_of?(Ably::Exceptions::
|
74
|
-
|
75
|
-
|
72
|
+
if error.kind_of?(Ably::Exceptions::BaseAblyException)
|
73
|
+
# Authentication errors that indicate the authentication failure is terminal should move to the failed state
|
74
|
+
if ([401, 403].include?(error.status) && !RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)) ||
|
75
|
+
(error.code == Ably::Exceptions::INVALID_CLIENT_ID)
|
76
|
+
connection.transition_state_machine :failed, reason: error
|
77
|
+
return
|
78
|
+
end
|
76
79
|
end
|
77
80
|
|
78
|
-
logger.warn "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}"
|
81
|
+
logger.warn { "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}" }
|
79
82
|
next_state = get_next_retry_state_info
|
80
|
-
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, 80000)
|
83
|
+
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, 80000, error)
|
81
84
|
end
|
82
85
|
|
83
86
|
# Called whenever a new connection is made
|
84
87
|
#
|
85
88
|
# @api private
|
86
89
|
def connected(protocol_message)
|
90
|
+
# ClientID validity is already checked as part of the incoming message processing
|
91
|
+
client.auth.configure_client_id protocol_message.connection_details.client_id
|
92
|
+
|
93
|
+
# Update the connection details and any associated defaults
|
94
|
+
connection.set_connection_details protocol_message.connection_details
|
95
|
+
|
87
96
|
if connection.key
|
88
97
|
if protocol_message.connection_id == connection.id
|
89
|
-
logger.debug "ConnectionManager: Connection resumed successfully - ID #{connection.id} and key #{connection.key}"
|
90
|
-
EventMachine.next_tick { connection.
|
98
|
+
logger.debug { "ConnectionManager: Connection resumed successfully - ID #{connection.id} and key #{connection.key}" }
|
99
|
+
EventMachine.next_tick { connection.trigger_resumed }
|
100
|
+
resend_pending_message_ack_queue
|
91
101
|
else
|
92
|
-
logger.debug "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new
|
93
|
-
|
102
|
+
logger.debug { "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connection ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
103
|
+
connection.reset_client_serial
|
104
|
+
nack_messages_on_all_channels protocol_message.error
|
105
|
+
force_reattach_on_channels protocol_message.error
|
94
106
|
end
|
95
107
|
else
|
96
|
-
logger.debug "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}"
|
108
|
+
logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
109
|
+
connection.reset_client_serial
|
97
110
|
end
|
111
|
+
|
112
|
+
reattach_suspended_channels protocol_message.error
|
113
|
+
|
98
114
|
connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
|
99
115
|
end
|
100
116
|
|
117
|
+
# When connection is CONNECTED and receives an update
|
118
|
+
# Update the Connection details and emit an UPDATE event #RTN4h
|
119
|
+
def connected_update(protocol_message)
|
120
|
+
# ClientID validity is already checked as part of the incoming message processing
|
121
|
+
client.auth.configure_client_id protocol_message.connection_details.client_id
|
122
|
+
|
123
|
+
# Update the connection details and any associated defaults
|
124
|
+
connection.set_connection_details protocol_message.connection_details
|
125
|
+
|
126
|
+
connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
|
127
|
+
|
128
|
+
state_change = Ably::Models::ConnectionStateChange.new(
|
129
|
+
current: connection.state,
|
130
|
+
previous: connection.state,
|
131
|
+
event: Ably::Realtime::Connection::EVENT(:update),
|
132
|
+
reason: protocol_message.error,
|
133
|
+
protocol_message: protocol_message
|
134
|
+
)
|
135
|
+
connection.emit :update, state_change
|
136
|
+
end
|
137
|
+
|
101
138
|
# Ensures the underlying transport has been disconnected and all event emitter callbacks removed
|
102
139
|
#
|
103
140
|
# @api private
|
@@ -109,6 +146,12 @@ module Ably::Realtime
|
|
109
146
|
end
|
110
147
|
end
|
111
148
|
|
149
|
+
# @api private
|
150
|
+
def release_and_establish_new_transport
|
151
|
+
destroy_transport
|
152
|
+
setup_transport
|
153
|
+
end
|
154
|
+
|
112
155
|
# Reconnect the {Ably::Realtime::Connection::WebsocketTransport} if possible, otherwise set up a new transport
|
113
156
|
#
|
114
157
|
# @api private
|
@@ -143,9 +186,12 @@ module Ably::Realtime
|
|
143
186
|
#
|
144
187
|
# @api private
|
145
188
|
def fail(error)
|
146
|
-
connection.logger.fatal "ConnectionManager: Connection failed - #{error}"
|
147
|
-
|
148
|
-
|
189
|
+
connection.logger.fatal { "ConnectionManager: Connection failed - #{error}" }
|
190
|
+
destroy_transport
|
191
|
+
channels.each do |channel|
|
192
|
+
next if channel.detached? || channel.initialized?
|
193
|
+
channel.transition_state_machine :failed, reason: error if channel.can_transition_to?(:failed)
|
194
|
+
end
|
149
195
|
end
|
150
196
|
|
151
197
|
# When a connection is disconnected whilst connecting, attempt reconnect and/or set state to :suspended or :failed
|
@@ -153,12 +199,12 @@ module Ably::Realtime
|
|
153
199
|
# @api private
|
154
200
|
def respond_to_transport_disconnected_when_connecting(error)
|
155
201
|
return unless connection.disconnected? || connection.suspended? # do nothing if state has changed through an explicit request
|
156
|
-
return
|
202
|
+
return if currently_renewing_token? # do not always reattempt connection or change state as client may be re-authorising
|
157
203
|
|
158
204
|
if error.kind_of?(Ably::Models::ErrorInfo)
|
159
205
|
if RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)
|
160
|
-
next_state = get_next_retry_state_info
|
161
|
-
logger.debug "ConnectionManager: Transport disconnected because of token expiry, pausing #{next_state.fetch(:pause)}s before reattempting to connect"
|
206
|
+
next_state = get_next_retry_state_info(1)
|
207
|
+
logger.debug { "ConnectionManager: Transport disconnected because of token expiry, pausing #{next_state.fetch(:pause)}s before reattempting to connect" }
|
162
208
|
EventMachine.add_timer(next_state.fetch(:pause)) { renew_token_and_reconnect error }
|
163
209
|
return
|
164
210
|
end
|
@@ -179,14 +225,13 @@ module Ably::Realtime
|
|
179
225
|
# @api private
|
180
226
|
def respond_to_transport_disconnected_whilst_connected(error)
|
181
227
|
unless connection.disconnected? || connection.suspended?
|
182
|
-
logger.warn "ConnectionManager: Connection #{"to #{connection.transport.url}" if connection.transport} was disconnected unexpectedly"
|
228
|
+
logger.warn { "ConnectionManager: Connection #{"to #{connection.transport.url}" if connection.transport} was disconnected unexpectedly" }
|
183
229
|
else
|
184
|
-
logger.debug "ConnectionManager: Transport disconnected whilst connection in #{connection.state} state"
|
230
|
+
logger.debug { "ConnectionManager: Transport disconnected whilst connection in #{connection.state} state" }
|
185
231
|
end
|
186
232
|
|
187
233
|
if error.kind_of?(Ably::Models::ErrorInfo) && !RESOLVABLE_ERROR_CODES.fetch(:token_expired).include?(error.code)
|
188
|
-
|
189
|
-
logger.error "ConnectionManager: Error in Disconnected ProtocolMessage received from the server - #{error}"
|
234
|
+
logger.error { "ConnectionManager: Error in Disconnected ProtocolMessage received from the server - #{error}" }
|
190
235
|
end
|
191
236
|
|
192
237
|
destroy_transport
|
@@ -200,10 +245,10 @@ module Ably::Realtime
|
|
200
245
|
def error_received_from_server(error)
|
201
246
|
case error.code
|
202
247
|
when RESOLVABLE_ERROR_CODES.fetch(:token_expired)
|
203
|
-
next_state = get_next_retry_state_info
|
248
|
+
next_state = get_next_retry_state_info(1)
|
204
249
|
connection.transition_state_machine next_state.fetch(:state), retry_in: next_state.fetch(:pause), reason: error
|
205
250
|
else
|
206
|
-
logger.error "ConnectionManager: Error #{error.class.name} code #{error.code} received from server '#{error.message}', transitioning to failed state"
|
251
|
+
logger.error { "ConnectionManager: Error #{error.class.name} code #{error.code} received from server '#{error.message}', transitioning to failed state" }
|
207
252
|
connection.transition_state_machine :failed, reason: error
|
208
253
|
end
|
209
254
|
end
|
@@ -215,6 +260,71 @@ module Ably::Realtime
|
|
215
260
|
retries_for_state(state, ignore_states: [:connecting]).count
|
216
261
|
end
|
217
262
|
|
263
|
+
# Any message sent before an ACK/NACK was received on the previous transport
|
264
|
+
# need to be resent to the Ably service so that a subsequent ACK/NACK is received.
|
265
|
+
# It is up to Ably to ensure that duplicate messages are not retransmitted on the channel
|
266
|
+
# base on the serial numbers
|
267
|
+
#
|
268
|
+
# @api private
|
269
|
+
def resend_pending_message_ack_queue
|
270
|
+
connection.__pending_message_ack_queue__.delete_if do |protocol_message|
|
271
|
+
if protocol_message.ack_required?
|
272
|
+
connection.__outgoing_message_queue__ << protocol_message
|
273
|
+
connection.__outgoing_protocol_msgbus__.publish :protocol_message
|
274
|
+
true
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# @api private
|
280
|
+
def suspend_active_channels(error)
|
281
|
+
channels.select do |channel|
|
282
|
+
channel.attached? || channel.attaching? || channel.detaching?
|
283
|
+
end.each do |channel|
|
284
|
+
channel.transition_state_machine! :suspended, reason: error
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# @api private
|
289
|
+
def detach_active_channels
|
290
|
+
channels.select do |channel|
|
291
|
+
channel.attached? || channel.attaching? || channel.detaching?
|
292
|
+
end.each do |channel|
|
293
|
+
channel.transition_state_machine! :detaching # will always move to detached immediately if connection is closed
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# @api private
|
298
|
+
def fail_active_channels(error)
|
299
|
+
channels.select do |channel|
|
300
|
+
channel.attached? || channel.attaching? || channel.detaching? || channel.suspended?
|
301
|
+
end.each do |channel|
|
302
|
+
channel.transition_state_machine! :failed, reason: error
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# When continuity on a connection is lost all messages
|
307
|
+
# whether queued or awaiting an ACK must be NACK'd as we now have a new connection
|
308
|
+
def nack_messages_on_all_channels(error)
|
309
|
+
channels.each do |channel|
|
310
|
+
channel.manager.fail_messages_awaiting_ack error, immediately: true
|
311
|
+
channel.manager.fail_queued_messages error
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Liveness timer ensures a connection that has not heard from Ably in heartbeat_interval
|
316
|
+
# is moved to the disconnected state automatically
|
317
|
+
def reset_liveness_timer
|
318
|
+
@liveness_timer.cancel if @liveness_timer
|
319
|
+
@liveness_timer = EventMachine::Timer.new(connection.heartbeat_interval + 0.1) do
|
320
|
+
if connection.connected? && (connection.time_since_connection_confirmed_alive? >= connection.heartbeat_interval)
|
321
|
+
msg = "No activity seen from realtime in #{connection.heartbeat_interval}; assuming connection has dropped";
|
322
|
+
error = Ably::Exceptions::ConnectionTimeout.new(msg, 80003, 408)
|
323
|
+
connection.transition_state_machine! :disconnected, reason: error
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
218
328
|
private
|
219
329
|
def connection
|
220
330
|
@connection
|
@@ -265,7 +375,7 @@ module Ably::Realtime
|
|
265
375
|
timers.fetch(key, []).each(&:cancel)
|
266
376
|
end
|
267
377
|
|
268
|
-
def get_next_retry_state_info
|
378
|
+
def get_next_retry_state_info(allow_extra_immediate_retries = 0)
|
269
379
|
retry_state = if connection_retry_from_suspended_state? || !can_reattempt_connect_for_state?(:disconnected)
|
270
380
|
:suspended
|
271
381
|
else
|
@@ -273,14 +383,14 @@ module Ably::Realtime
|
|
273
383
|
end
|
274
384
|
{
|
275
385
|
state: retry_state,
|
276
|
-
pause: next_retry_pause(retry_state)
|
386
|
+
pause: next_retry_pause(retry_state, allow_extra_immediate_retries)
|
277
387
|
}
|
278
388
|
end
|
279
389
|
|
280
|
-
def next_retry_pause(retry_state)
|
390
|
+
def next_retry_pause(retry_state, allow_extra_immediate_retries = 0)
|
281
391
|
return nil unless state_has_retry_timeout?(retry_state)
|
282
392
|
|
283
|
-
if retries_for_state(retry_state, ignore_states: [:connecting]).
|
393
|
+
if retries_for_state(retry_state, ignore_states: [:connecting]).count <= allow_extra_immediate_retries
|
284
394
|
0
|
285
395
|
else
|
286
396
|
retry_timeout_for(retry_state)
|
@@ -302,10 +412,10 @@ module Ably::Realtime
|
|
302
412
|
def connection_retry_for(from_state)
|
303
413
|
if can_reattempt_connect_for_state?(from_state)
|
304
414
|
if connection.state == :disconnected && retries_for_state(from_state, ignore_states: [:connecting]).empty?
|
305
|
-
logger.debug "ConnectionManager: Will attempt reconnect immediately as no previous reconnect attempts made in state #{from_state}"
|
415
|
+
logger.debug { "ConnectionManager: Will attempt reconnect immediately as no previous reconnect attempts made in state #{from_state}" }
|
306
416
|
EventMachine.next_tick { connection.connect }
|
307
417
|
else
|
308
|
-
logger.debug "ConnectionManager: Pausing for #{retry_timeout_for(from_state)}s before attempting to reconnect"
|
418
|
+
logger.debug { "ConnectionManager: Pausing for #{retry_timeout_for(from_state)}s before attempting to reconnect" }
|
309
419
|
create_timeout_timer_whilst_in_state(from_state, retry_timeout_for(from_state)) do
|
310
420
|
connection.connect if connection.state == from_state
|
311
421
|
end
|
@@ -380,7 +490,7 @@ module Ably::Realtime
|
|
380
490
|
transport.unsafe_on(:disconnected) do |reason|
|
381
491
|
if connection.closing?
|
382
492
|
connection.transition_state_machine :closed
|
383
|
-
elsif !connection.closed? && !connection.disconnected?
|
493
|
+
elsif !connection.closed? && !connection.disconnected? && !connection.failed? && !connection.suspended?
|
384
494
|
exception = if reason
|
385
495
|
Ably::Exceptions::TransportClosed.new(reason, nil, 80003)
|
386
496
|
else
|
@@ -394,35 +504,21 @@ module Ably::Realtime
|
|
394
504
|
|
395
505
|
def renew_token_and_reconnect(error)
|
396
506
|
if client.auth.token_renewable?
|
397
|
-
if
|
398
|
-
logger.error 'ConnectionManager: Attempting to renew token whilst another token renewal is underway. Aborting current renew token request'
|
507
|
+
if currently_renewing_token?
|
508
|
+
logger.error { 'ConnectionManager: Attempting to renew token whilst another token renewal is underway. Aborting current renew token request' }
|
399
509
|
return
|
400
510
|
end
|
401
511
|
|
402
|
-
|
403
|
-
logger.info "ConnectionManager: Token has expired and is renewable, renewing token now"
|
512
|
+
logger.info { "ConnectionManager: Token has expired and is renewable, renewing token now" }
|
404
513
|
|
514
|
+
# Authorize implicitly reconnects, see #RTC8
|
405
515
|
client.auth.authorize.tap do |authorize_deferrable|
|
406
516
|
authorize_deferrable.callback do |token_details|
|
407
|
-
logger.info 'ConnectionManager: Token renewed succesfully following expiration'
|
408
|
-
|
409
|
-
connection.once_state_changed { @renewing_token = false }
|
410
|
-
|
411
|
-
if token_details && !token_details.expired?
|
412
|
-
connection.connect
|
413
|
-
else
|
414
|
-
connection.transition_state_machine :failed, reason: error unless connection.failed?
|
415
|
-
end
|
416
|
-
end
|
417
|
-
|
418
|
-
authorize_deferrable.errback do |auth_error|
|
419
|
-
@renewing_token = false
|
420
|
-
logger.error "ConnectionManager: Error authorising following token expiry: #{auth_error}"
|
421
|
-
connection.transition_state_machine :failed, reason: auth_error
|
517
|
+
logger.info { 'ConnectionManager: Token renewed succesfully following expiration' }
|
422
518
|
end
|
423
519
|
end
|
424
520
|
else
|
425
|
-
logger.error "ConnectionManager: Token has expired and is not renewable - #{error}"
|
521
|
+
logger.error { "ConnectionManager: Token has expired and is not renewable - #{error}" }
|
426
522
|
connection.transition_state_machine :failed, reason: error
|
427
523
|
end
|
428
524
|
end
|
@@ -430,7 +526,7 @@ module Ably::Realtime
|
|
430
526
|
def unsubscribe_from_transport_events(transport)
|
431
527
|
transport.__incoming_protocol_msgbus__.unsubscribe
|
432
528
|
transport.off
|
433
|
-
logger.debug "ConnectionManager: Unsubscribed from all events from current transport"
|
529
|
+
logger.debug { "ConnectionManager: Unsubscribed from all events from current transport" }
|
434
530
|
end
|
435
531
|
|
436
532
|
def close_connection_when_reactor_is_stopped
|
@@ -439,16 +535,26 @@ module Ably::Realtime
|
|
439
535
|
end
|
440
536
|
end
|
441
537
|
|
442
|
-
def
|
443
|
-
|
538
|
+
def currently_renewing_token?
|
539
|
+
client.auth.authorization_in_flight?
|
540
|
+
end
|
541
|
+
|
542
|
+
def reattach_suspended_channels(error)
|
543
|
+
channels.select do |channel|
|
544
|
+
channel.suspended?
|
545
|
+
end.each do |channel|
|
546
|
+
channel.transition_state_machine :attaching
|
547
|
+
end
|
444
548
|
end
|
445
549
|
|
446
|
-
|
550
|
+
# When continuity on a connection is lost all messages
|
551
|
+
# Channels in the ATTACHED or ATTACHING state should explicitly be re-attached
|
552
|
+
# by sending a new ATTACH to Ably
|
553
|
+
def force_reattach_on_channels(error)
|
447
554
|
channels.select do |channel|
|
448
555
|
channel.attached? || channel.attaching?
|
449
556
|
end.each do |channel|
|
450
|
-
|
451
|
-
channel.manager.suspend error
|
557
|
+
channel.manager.request_reattach reason: error
|
452
558
|
end
|
453
559
|
end
|
454
560
|
|
@@ -47,8 +47,7 @@ module Ably::Realtime
|
|
47
47
|
after_transition(to: [:connected]) do |connection, current_transition|
|
48
48
|
error = current_transition.metadata.reason
|
49
49
|
if is_error_type?(error)
|
50
|
-
connection.logger.warn "ConnectionManager: Connected with error - #{error.message}"
|
51
|
-
connection.emit :error, error
|
50
|
+
connection.logger.warn { "ConnectionManager: Connected with error - #{error.message}" }
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
@@ -62,6 +61,11 @@ module Ably::Realtime
|
|
62
61
|
connection.manager.respond_to_transport_disconnected_whilst_connected err
|
63
62
|
end
|
64
63
|
|
64
|
+
after_transition(to: [:suspended]) do |connection, current_transition|
|
65
|
+
err = error_from_state_change(current_transition)
|
66
|
+
connection.manager.suspend_active_channels err
|
67
|
+
end
|
68
|
+
|
65
69
|
after_transition(to: [:disconnected, :suspended]) do |connection|
|
66
70
|
connection.manager.destroy_transport # never reuse a transport if the connection has failed
|
67
71
|
end
|
@@ -71,6 +75,18 @@ module Ably::Realtime
|
|
71
75
|
connection.manager.fail err
|
72
76
|
end
|
73
77
|
|
78
|
+
after_transition(to: [:failed]) do |connection, current_transition|
|
79
|
+
err = error_from_state_change(current_transition)
|
80
|
+
connection.manager.fail_active_channels err
|
81
|
+
end
|
82
|
+
|
83
|
+
# RTN7C - If a connection enters the SUSPENDED, CLOSED or FAILED state...
|
84
|
+
# the client should consider the delivery of those messages as failed
|
85
|
+
after_transition(to: [:suspended, :closed, :failed]) do |connection, current_transition|
|
86
|
+
err = error_from_state_change(current_transition)
|
87
|
+
connection.manager.nack_messages_on_all_channels err
|
88
|
+
end
|
89
|
+
|
74
90
|
after_transition(to: [:closing], from: [:initialized, :disconnected, :suspended]) do |connection|
|
75
91
|
connection.manager.force_close_connection
|
76
92
|
end
|
@@ -83,6 +99,10 @@ module Ably::Realtime
|
|
83
99
|
connection.manager.destroy_transport
|
84
100
|
end
|
85
101
|
|
102
|
+
after_transition(to: [:closed]) do |connection|
|
103
|
+
connection.manager.detach_active_channels
|
104
|
+
end
|
105
|
+
|
86
106
|
# Transitions responsible for updating connection#error_reason
|
87
107
|
before_transition(to: [:disconnected, :suspended, :failed]) do |connection, current_transition|
|
88
108
|
err = error_from_state_change(current_transition)
|
@@ -91,7 +111,6 @@ module Ably::Realtime
|
|
91
111
|
|
92
112
|
before_transition(to: [:connected, :closed]) do |connection, current_transition|
|
93
113
|
err = error_from_state_change(current_transition)
|
94
|
-
|
95
114
|
if err
|
96
115
|
connection.set_failed_connection_error_reason err
|
97
116
|
else
|
@@ -119,14 +119,14 @@ module Ably::Realtime
|
|
119
119
|
when :msgpack
|
120
120
|
driver.binary(object.to_msgpack.unpack('C*'))
|
121
121
|
else
|
122
|
-
client.logger.fatal "WebsocketTransport: Unsupported protocol '#{client.protocol}' for serialization, object cannot be serialized and sent to Ably over this WebSocket"
|
122
|
+
client.logger.fatal { "WebsocketTransport: Unsupported protocol '#{client.protocol}' for serialization, object cannot be serialized and sent to Ably over this WebSocket" }
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
def setup_event_handlers
|
127
127
|
__outgoing_protocol_msgbus__.subscribe(:protocol_message) do |protocol_message|
|
128
128
|
send_object protocol_message
|
129
|
-
client.logger.debug "WebsocketTransport: Prot msg sent =>: #{protocol_message.action} #{protocol_message}"
|
129
|
+
client.logger.debug { "WebsocketTransport: Prot msg sent =>: #{protocol_message.action} #{protocol_message}" }
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
@@ -147,32 +147,41 @@ module Ably::Realtime
|
|
147
147
|
@driver = WebSocket::Driver.client(self)
|
148
148
|
|
149
149
|
driver.on("open") do
|
150
|
-
logger.debug "WebsocketTransport: socket opened to #{url}, waiting for Connected protocol message"
|
150
|
+
logger.debug { "WebsocketTransport: socket opened to #{url}, waiting for Connected protocol message" }
|
151
151
|
end
|
152
152
|
|
153
153
|
driver.on("message") do |event|
|
154
154
|
event_data = parse_event_data(event.data).freeze
|
155
155
|
protocol_message = Ably::Models::ProtocolMessage.new(event_data, logger: logger)
|
156
156
|
action_name = Ably::Models::ProtocolMessage::ACTION[event_data['action']] rescue event_data['action']
|
157
|
-
logger.debug "WebsocketTransport: Prot msg recv <=: #{action_name} - #{event_data}"
|
157
|
+
logger.debug { "WebsocketTransport: Prot msg recv <=: #{action_name} - #{event_data}" }
|
158
158
|
|
159
159
|
if protocol_message.invalid?
|
160
|
-
error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\
|
161
|
-
|
162
|
-
|
160
|
+
error = Ably::Exceptions::ProtocolError.new("Invalid Protocol Message received: #{event_data}\nConnection moving to the failed state as the protocol is invalid and unsupported", 400, 80013)
|
161
|
+
logger.fatal { "WebsocketTransport: #{error.message}" }
|
162
|
+
failed_protocol_message = Ably::Models::ProtocolMessage.new(
|
163
|
+
action: Ably::Models::ProtocolMessage::ACTION.Error,
|
164
|
+
error: error.as_json,
|
165
|
+
logger: logger
|
166
|
+
)
|
167
|
+
__incoming_protocol_msgbus__.publish :protocol_message, failed_protocol_message
|
163
168
|
else
|
164
169
|
__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
|
165
170
|
end
|
166
171
|
end
|
167
172
|
|
173
|
+
driver.on("ping") do
|
174
|
+
__incoming_protocol_msgbus__.publish :protocol_message, Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Heartbeat, source: :websocket)
|
175
|
+
end
|
176
|
+
|
168
177
|
driver.on("error") do |error|
|
169
|
-
logger.error "WebsocketTransport: Protocol Error on transports - #{error.message}"
|
178
|
+
logger.error { "WebsocketTransport: Protocol Error on transports - #{error.message}" }
|
170
179
|
end
|
171
180
|
|
172
181
|
@reason_closed = nil
|
173
182
|
driver.on("closed") do |event|
|
174
183
|
@reason_closed = "#{event.code}: #{event.reason}"
|
175
|
-
logger.warn "WebsocketTransport: Driver reported transport as closed - #{reason_closed}"
|
184
|
+
logger.warn { "WebsocketTransport: Driver reported transport as closed - #{reason_closed}" }
|
176
185
|
end
|
177
186
|
end
|
178
187
|
|
@@ -192,7 +201,7 @@ module Ably::Realtime
|
|
192
201
|
when :msgpack
|
193
202
|
MessagePack.unpack(data.pack('C*'))
|
194
203
|
else
|
195
|
-
client.logger.fatal "WebsocketTransport: Unsupported Protocol Message format #{client.protocol}"
|
204
|
+
client.logger.fatal { "WebsocketTransport: Unsupported Protocol Message format #{client.protocol}" }
|
196
205
|
data
|
197
206
|
end
|
198
207
|
end
|