ably-rest 0.9.3 → 1.0.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/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
|