ably-rest 0.7.1 → 0.7.3
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 +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
module Ably::Realtime
|
|
2
|
+
class Connection
|
|
3
|
+
# ConnectionManager is responsible for all actions relating to underlying connection and transports,
|
|
4
|
+
# such as opening, closing, attempting reconnects etc.
|
|
5
|
+
# Connection state changes are performed by this class and executed from {ConnectionStateMachine}
|
|
6
|
+
#
|
|
7
|
+
# This is a private class and should never be used directly by developers as the API is likely to change in future.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
class ConnectionManager
|
|
11
|
+
# Configuration for automatic recovery of failed connection attempts
|
|
12
|
+
CONNECT_RETRY_CONFIG = {
|
|
13
|
+
disconnected: { retry_every: 15, max_time_in_state: 120 },
|
|
14
|
+
suspended: { retry_every: 120, max_time_in_state: Float::INFINITY }
|
|
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
|
+
}
|
|
27
|
+
|
|
28
|
+
def initialize(connection)
|
|
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
|
|
39
|
+
|
|
40
|
+
EventMachine.next_tick do
|
|
41
|
+
# Connect once Connection object is initialised
|
|
42
|
+
connection.connect if client.connect_automatically
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Creates and sets up a new {Ably::Realtime::Connection::WebsocketTransport} available on attribute #transport
|
|
47
|
+
#
|
|
48
|
+
# @yield [Ably::Realtime::Connection::WebsocketTransport] block is called with new websocket transport
|
|
49
|
+
# @api private
|
|
50
|
+
def setup_transport
|
|
51
|
+
if transport && !transport.ready_for_release?
|
|
52
|
+
raise RuntimeError, 'Existing WebsocketTransport is connected, and must be closed first'
|
|
53
|
+
end
|
|
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
|
+
|
|
60
|
+
logger.debug 'ConnectionManager: Opening a websocket transport connection'
|
|
61
|
+
|
|
62
|
+
connection.create_websocket_transport do |websocket_transport|
|
|
63
|
+
subscribe_to_transport_events websocket_transport
|
|
64
|
+
yield websocket_transport if block_given?
|
|
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
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Called by the transport when a connection attempt fails
|
|
74
|
+
#
|
|
75
|
+
# @api private
|
|
76
|
+
def connection_opening_failed(error)
|
|
77
|
+
logger.warn "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}"
|
|
78
|
+
connection.transition_state_machine next_retry_state, Ably::Exceptions::ConnectionError.new("Connection failed; #{error.message}", nil, 80000)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Called whenever a new connection message is received with an error
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
def connected_with_error(error)
|
|
85
|
+
logger.warn "ConnectionManager: Connected with error; #{error.message}"
|
|
86
|
+
connection.trigger :error, error
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Ensures the underlying transport has been disconnected and all event emitter callbacks removed
|
|
90
|
+
#
|
|
91
|
+
# @api private
|
|
92
|
+
def destroy_transport
|
|
93
|
+
if transport
|
|
94
|
+
unsubscribe_from_transport_events transport
|
|
95
|
+
transport.close_connection
|
|
96
|
+
connection.release_websocket_transport
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Reconnect the {Ably::Realtime::Connection::WebsocketTransport} if possible, otherwise set up a new transport
|
|
101
|
+
#
|
|
102
|
+
# @api private
|
|
103
|
+
def reconnect_transport
|
|
104
|
+
if !transport || transport.disconnected?
|
|
105
|
+
setup_transport
|
|
106
|
+
else
|
|
107
|
+
transport.reconnect connection.current_host, connection.port
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Send a Close {Ably::Models::ProtocolMessage} to the server and release the transport
|
|
112
|
+
#
|
|
113
|
+
# @api private
|
|
114
|
+
def close_connection
|
|
115
|
+
connection.send_protocol_message(action: Ably::Models::ProtocolMessage::ACTION.Close)
|
|
116
|
+
|
|
117
|
+
create_timeout_timer_whilst_in_state(:close, TIMEOUTS.fetch(:close)) do
|
|
118
|
+
force_close_connection if connection.closing?
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Close the underlying transport immediately and set the connection state to closed
|
|
123
|
+
#
|
|
124
|
+
# @api private
|
|
125
|
+
def force_close_connection
|
|
126
|
+
destroy_transport
|
|
127
|
+
connection.transition_state_machine :closed
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Connection has failed
|
|
131
|
+
#
|
|
132
|
+
# @api private
|
|
133
|
+
def fail(error)
|
|
134
|
+
connection.logger.fatal "ConnectionManager: Connection failed - #{error}"
|
|
135
|
+
connection.manager.destroy_transport
|
|
136
|
+
connection.once(:failed) { connection.trigger :error, error }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# When a connection is disconnected whilst connecting, attempt reconnect and/or set state to :suspended or :failed
|
|
140
|
+
#
|
|
141
|
+
# @api private
|
|
142
|
+
def respond_to_transport_disconnected_when_connecting(current_transition)
|
|
143
|
+
return unless connection.disconnected? || connection.suspended? # do nothing if state has changed through an explicit request
|
|
144
|
+
return unless retry_connection? # do not always reattempt connection or change state as client may be re-authorising
|
|
145
|
+
|
|
146
|
+
unless connection_retry_from_suspended_state?
|
|
147
|
+
return if connection_retry_for(:disconnected, ignore_states: [:connecting])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
return if connection_retry_for(:suspended, ignore_states: [:connecting])
|
|
151
|
+
|
|
152
|
+
# Fallback if no other criteria met
|
|
153
|
+
connection.transition_state_machine :failed, current_transition.metadata
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# When a connection is disconnected after connecting, attempt reconnect and/or set state to :suspended or :failed
|
|
157
|
+
#
|
|
158
|
+
# @api private
|
|
159
|
+
def respond_to_transport_disconnected_whilst_connected(current_transition)
|
|
160
|
+
logger.warn "ConnectionManager: Connection to #{connection.transport.url} was disconnected unexpectedly"
|
|
161
|
+
|
|
162
|
+
if current_transition.metadata.kind_of?(Ably::Models::ErrorInfo)
|
|
163
|
+
connection.trigger :error, current_transition.metadata
|
|
164
|
+
logger.error "ConnectionManager: Error received when disconnected within ProtocolMessage - #{current_transition.metadata}"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
destroy_transport
|
|
168
|
+
respond_to_transport_disconnected_when_connecting current_transition
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# {Ably::Models::ProtocolMessage ProtocolMessage Error} received from server.
|
|
172
|
+
# Some error states can be resolved by the client library.
|
|
173
|
+
#
|
|
174
|
+
# @api private
|
|
175
|
+
def error_received_from_server(error)
|
|
176
|
+
case error.code
|
|
177
|
+
when RESOLVABLE_ERROR_CODES.fetch(:token_expired)
|
|
178
|
+
connection.transition_state_machine :disconnected
|
|
179
|
+
connection.once_or_if(:disconnected) do
|
|
180
|
+
renew_token_and_reconnect error
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
logger.error "ConnectionManager: Error #{error.class.name} code #{error.code} received from server '#{error.message}', transitioning to failed state"
|
|
184
|
+
connection.transition_state_machine :failed, error
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Number of consecutive attempts for provided state
|
|
189
|
+
# @return [Integer]
|
|
190
|
+
# @api private
|
|
191
|
+
def retry_count_for_state(state)
|
|
192
|
+
retries_for_state(state, ignore_states: [:connecting]).count
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
attr_reader :connection
|
|
197
|
+
|
|
198
|
+
# Timers used to manage connection state, for internal use by the client library
|
|
199
|
+
# @return [Hash]
|
|
200
|
+
attr_reader :timers
|
|
201
|
+
|
|
202
|
+
def transport
|
|
203
|
+
connection.transport
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def client
|
|
207
|
+
connection.client
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Create a timer that will execute in timeout_in seconds.
|
|
211
|
+
# If the connection state changes however, cancel the timer
|
|
212
|
+
def create_timeout_timer_whilst_in_state(timer_id, timeout_in)
|
|
213
|
+
raise ArgumentError, 'Block required' unless block_given?
|
|
214
|
+
|
|
215
|
+
timers[timer_id] << EventMachine::Timer.new(timeout_in) do
|
|
216
|
+
yield
|
|
217
|
+
end
|
|
218
|
+
connection.once_state_changed { clear_timers timer_id }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def clear_timers(key)
|
|
222
|
+
timers.fetch(key, []).each(&:cancel)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def next_retry_state
|
|
226
|
+
if connection_retry_from_suspended_state? || time_passed_since_disconnected > CONNECT_RETRY_CONFIG.fetch(:disconnected).fetch(:max_time_in_state)
|
|
227
|
+
:suspended
|
|
228
|
+
else
|
|
229
|
+
:disconnected
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def connection_retry_from_suspended_state?
|
|
234
|
+
!retries_for_state(:suspended, ignore_states: [:connecting]).empty?
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def time_passed_since_disconnected
|
|
238
|
+
time_spent_attempting_state(:disconnected, ignore_states: [:connecting])
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Reattempt a connection with a delay based on the CONNECT_RETRY_CONFIG for `from_state`
|
|
242
|
+
#
|
|
243
|
+
# @return [Boolean] True if a connection attempt has been set up, false if no further connection attempts can be made for this state
|
|
244
|
+
#
|
|
245
|
+
def connection_retry_for(from_state, options = {})
|
|
246
|
+
retry_params = CONNECT_RETRY_CONFIG.fetch(from_state)
|
|
247
|
+
|
|
248
|
+
if time_spent_attempting_state(from_state, options) <= retry_params.fetch(:max_time_in_state)
|
|
249
|
+
logger.debug "ConnectionManager: Pausing for #{retry_params.fetch(:retry_every)}s before attempting to reconnect"
|
|
250
|
+
create_timeout_timer_whilst_in_state(:reconnect, retry_params.fetch(:retry_every)) do
|
|
251
|
+
connection.connect if connection.state == from_state
|
|
252
|
+
end
|
|
253
|
+
true
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Returns a float representing the amount of time passed since the first consecutive attempt of this state
|
|
258
|
+
#
|
|
259
|
+
# @param (see #retries_for_state)
|
|
260
|
+
# @return [Float] time passed in seconds
|
|
261
|
+
#
|
|
262
|
+
def time_spent_attempting_state(state, options)
|
|
263
|
+
states = retries_for_state(state, options)
|
|
264
|
+
if states.empty?
|
|
265
|
+
0
|
|
266
|
+
else
|
|
267
|
+
Time.now.to_f - states.last[:transitioned_at].to_f
|
|
268
|
+
end.to_f
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Checks the state change history for the current connection and returns all matching consecutive states.
|
|
272
|
+
# This is useful to determine the number of retries of a particular state on a connection.
|
|
273
|
+
#
|
|
274
|
+
# @param state [Symbol]
|
|
275
|
+
# @param options [Hash]
|
|
276
|
+
# @option options [Array<Symbol>] :ignore_states states that should be ignored when determining consecutive historical retries for `state`.
|
|
277
|
+
# For example, when working out :connecting attempts, :disconnect state changes should be ignored as they are a side effect of a failed :connecting
|
|
278
|
+
#
|
|
279
|
+
# @return [Array<Hash>] Array of consecutive state attempts matching `state` in order of transitioned_at desc
|
|
280
|
+
#
|
|
281
|
+
def retries_for_state(state, options)
|
|
282
|
+
ignore_states = options.fetch(:ignore_states, [])
|
|
283
|
+
allowed_states = Array(state) + Array(ignore_states)
|
|
284
|
+
|
|
285
|
+
connection.state_history.reverse.take_while do |transition|
|
|
286
|
+
allowed_states.include?(transition[:state].to_sym)
|
|
287
|
+
end.select do |transition|
|
|
288
|
+
transition[:state] == state
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def subscribe_to_transport_events(transport)
|
|
293
|
+
transport.__incoming_protocol_msgbus__.on(:protocol_message) do |protocol_message|
|
|
294
|
+
connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
transport.on(:disconnected) do
|
|
298
|
+
if connection.closing?
|
|
299
|
+
connection.transition_state_machine :closed
|
|
300
|
+
elsif !connection.closed? && !connection.disconnected?
|
|
301
|
+
connection.transition_state_machine :disconnected
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def renew_token_and_reconnect(error)
|
|
307
|
+
if client.auth.token_renewable?
|
|
308
|
+
if @renewing_token
|
|
309
|
+
connection.transition_state_machine :failed, error
|
|
310
|
+
return
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
@renewing_token = true
|
|
314
|
+
logger.warn "ConnectionManager: Token has expired and is renewable, renewing token now"
|
|
315
|
+
|
|
316
|
+
operation = proc do
|
|
317
|
+
begin
|
|
318
|
+
client.auth.authorise
|
|
319
|
+
rescue StandardError => auth_error
|
|
320
|
+
connection.transition_state_machine :failed, auth_error
|
|
321
|
+
nil
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
callback = proc do |token|
|
|
326
|
+
state_changed_callback = proc do
|
|
327
|
+
@renewing_token = false
|
|
328
|
+
connection.off &state_changed_callback
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
connection.once :connected, :closed, :failed, &state_changed_callback
|
|
332
|
+
|
|
333
|
+
if token && !token.expired?
|
|
334
|
+
reconnect_transport
|
|
335
|
+
else
|
|
336
|
+
connection.transition_state_machine :failed, error unless connection.failed?
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
EventMachine.defer operation, callback
|
|
341
|
+
else
|
|
342
|
+
logger.warn "ConnectionManager: Token has expired and is not renewable"
|
|
343
|
+
connection.transition_state_machine :failed, error
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def unsubscribe_from_transport_events(transport)
|
|
348
|
+
transport.__incoming_protocol_msgbus__.unsubscribe
|
|
349
|
+
transport.off
|
|
350
|
+
logger.debug "ConnectionManager: Unsubscribed from all events from current transport"
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def close_connection_when_reactor_is_stopped
|
|
354
|
+
EventMachine.add_shutdown_hook do
|
|
355
|
+
connection.close unless connection.closed? || connection.failed?
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def retry_connection?
|
|
360
|
+
!@renewing_token
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def logger
|
|
364
|
+
connection.logger
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'ably/modules/state_machine'
|
|
2
|
+
|
|
3
|
+
module Ably::Realtime
|
|
4
|
+
class Connection
|
|
5
|
+
# Internal class to manage connection state, recovery and state transitions for {Ably::Realtime::Connection}
|
|
6
|
+
class ConnectionStateMachine
|
|
7
|
+
include Ably::Modules::StateMachine
|
|
8
|
+
|
|
9
|
+
# States supported by this StateMachine match #{Connection::STATE}s
|
|
10
|
+
# :initialized
|
|
11
|
+
# :connecting
|
|
12
|
+
# :connected
|
|
13
|
+
# :disconnected
|
|
14
|
+
# :suspended
|
|
15
|
+
# :closing
|
|
16
|
+
# :closed
|
|
17
|
+
# :failed
|
|
18
|
+
Connection::STATE.each_with_index do |state_enum, index|
|
|
19
|
+
state state_enum.to_sym, initial: index == 0
|
|
20
|
+
end
|
|
21
|
+
|
|
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]
|
|
28
|
+
transition :from => :closed, :to => [:connecting]
|
|
29
|
+
transition :from => :failed, :to => [:connecting]
|
|
30
|
+
|
|
31
|
+
after_transition do |connection, transition|
|
|
32
|
+
connection.synchronize_state_with_statemachine
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
after_transition(to: [:connecting], from: [:initialized, :closed, :failed]) do |connection|
|
|
36
|
+
connection.manager.setup_transport
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
after_transition(to: [:connecting], from: [:disconnected, :suspended]) do |connection|
|
|
40
|
+
connection.manager.reconnect_transport
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
after_transition(to: [:connected]) do |connection, current_transition|
|
|
44
|
+
connection.manager.connected_with_error current_transition.metadata if current_transition.metadata
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
after_transition(to: [:disconnected, :suspended], from: [:connecting]) do |connection, current_transition|
|
|
48
|
+
connection.manager.respond_to_transport_disconnected_when_connecting current_transition
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
after_transition(to: [:disconnected], from: [:connected]) do |connection, current_transition|
|
|
52
|
+
connection.manager.respond_to_transport_disconnected_whilst_connected current_transition
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
after_transition(to: [:disconnected, :suspended]) do |connection|
|
|
56
|
+
connection.manager.destroy_transport # never reuse a transport if the connection has failed
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
before_transition(to: [:failed]) do |connection, current_transition|
|
|
60
|
+
connection.manager.fail current_transition.metadata
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
after_transition(to: [:closing], from: [:initialized, :disconnected, :suspended]) do |connection|
|
|
64
|
+
connection.manager.force_close_connection
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
after_transition(to: [:closing], from: [:connecting, :connected]) do |connection|
|
|
68
|
+
connection.manager.close_connection
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
before_transition(to: [:closed], from: [:closing]) do |connection|
|
|
72
|
+
connection.manager.destroy_transport
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Transitions responsible for updating connection#error_reason
|
|
76
|
+
before_transition(to: [:connected, :closed, :disconnected, :suspended, :failed]) do |connection, current_transition|
|
|
77
|
+
reason = current_transition.metadata if is_error_type?(current_transition.metadata)
|
|
78
|
+
connection.set_failed_connection_error_reason reason
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
def connection
|
|
83
|
+
object
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def logger
|
|
87
|
+
connection.logger
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|