Class: Ably::Realtime::Connection::ConnectionManager Private

Inherits:
Object
  • Object
show all
Defined in:
lib/ably/realtime/connection/connection_manager.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

ConnectionManager is responsible for all actions relating to underlying connection and transports, such as opening, closing, attempting reconnects etc. Connection state changes are performed by this class and executed from ConnectionStateMachine

This is a private class and should never be used directly by developers as the API is likely to change in future.

Constant Summary collapse

CONNECT_RETRY_CONFIG =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Configuration for automatic recovery of failed connection attempts

{
  disconnected: { retry_every: 15,  max_time_in_state: 120 },
  suspended:    { retry_every: 120, max_time_in_state: Float::INFINITY }
}.freeze
TIMEOUTS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Time to wait following a connection state request before it’s considered a failure

{
  open:  15,
  close: 10
}
RESOLVABLE_ERROR_CODES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Error codes from the server that can potentially be resolved

{
  token_expired: 40140
}

Instance Method Summary collapse

Constructor Details

#initialize(connection) ⇒ ConnectionManager

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of ConnectionManager.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/ably/realtime/connection/connection_manager.rb', line 28

def initialize(connection)
  @connection = connection
  @timers     = Hash.new { |hash, key| hash[key] = [] }

  connection.unsafe_on(:closed) do
    connection.reset_resume_info
  end

  connection.unsafe_once(:connecting) do
    close_connection_when_reactor_is_stopped
  end

  EventMachine.next_tick do
    # Connect once Connection object is initialised
    connection.connect if client.auto_connect
  end
end

Instance Method Details

#close_connectionObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Send a Close Models::ProtocolMessage to the server and release the transport



125
126
127
128
129
130
131
# File 'lib/ably/realtime/connection/connection_manager.rb', line 125

def close_connection
  connection.send_protocol_message(action: Ably::Models::ProtocolMessage::ACTION.Close)

  create_timeout_timer_whilst_in_state(:close, TIMEOUTS.fetch(:close)) do
    force_close_connection if connection.closing?
  end
end

#connected(protocol_message) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Called whenever a new connection is made



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ably/realtime/connection/connection_manager.rb', line 84

def connected(protocol_message)
  if connection.key
    if protocol_message.connection_key == connection.key
      logger.debug "ConnectionManager: Connection resumed successfully - ID #{connection.id} and key #{connection.key}"
      EventMachine.next_tick { connection.resumed }
    else
      logger.debug "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connect ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}"
      detach_attached_channels protocol_message.error
      connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
    end
  else
    logger.debug "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}"
    connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
  end
end

#connection_opening_failed(error) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Called by the transport when a connection attempt fails



76
77
78
79
# File 'lib/ably/realtime/connection/connection_manager.rb', line 76

def connection_opening_failed(error)
  logger.warn "ConnectionManager: Connection to #{connection.current_host}:#{connection.port} failed; #{error.message}"
  connection.transition_state_machine next_retry_state, Ably::Exceptions::ConnectionError.new("Connection failed: #{error.message}", nil, 80000)
end

#destroy_transportObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Ensures the underlying transport has been disconnected and all event emitter callbacks removed



103
104
105
106
107
108
109
# File 'lib/ably/realtime/connection/connection_manager.rb', line 103

def destroy_transport
  if transport
    unsubscribe_from_transport_events transport
    transport.close_connection
    connection.release_websocket_transport
  end
end

#error_received_from_server(error) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

ProtocolMessage Error received from server. Some error states can be resolved by the client library.



193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/ably/realtime/connection/connection_manager.rb', line 193

def error_received_from_server(error)
  case error.code
  when RESOLVABLE_ERROR_CODES.fetch(:token_expired)
    connection.transition_state_machine :disconnected
    connection.unsafe_once_or_if(:disconnected) do
      renew_token_and_reconnect error
    end
  else
    logger.error "ConnectionManager: Error #{error.class.name} code #{error.code} received from server '#{error.message}', transitioning to failed state"
    connection.transition_state_machine :failed, error
  end
end

#fail(error) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Connection has failed



144
145
146
147
148
# File 'lib/ably/realtime/connection/connection_manager.rb', line 144

def fail(error)
  connection.logger.fatal "ConnectionManager: Connection failed - #{error}"
  connection.manager.destroy_transport
  connection.unsafe_once(:failed) { connection.emit :error, error }
end

#force_close_connectionObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Close the underlying transport immediately and set the connection state to closed



136
137
138
139
# File 'lib/ably/realtime/connection/connection_manager.rb', line 136

def force_close_connection
  destroy_transport
  connection.transition_state_machine :closed
end

#reconnect_transportObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reconnect the WebsocketTransport if possible, otherwise set up a new transport



114
115
116
117
118
119
120
# File 'lib/ably/realtime/connection/connection_manager.rb', line 114

def reconnect_transport
  if !transport || transport.disconnected?
    setup_transport
  else
    transport.reconnect connection.current_host, connection.port
  end
end

#respond_to_transport_disconnected_when_connecting(current_transition) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

When a connection is disconnected whilst connecting, attempt reconnect and/or set state to :suspended or :failed



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/ably/realtime/connection/connection_manager.rb', line 153

def respond_to_transport_disconnected_when_connecting(current_transition)
  return unless connection.disconnected? || connection.suspended? # do nothing if state has changed through an explicit request
  return unless retry_connection? # do not always reattempt connection or change state as client may be re-authorising

  error = current_transition.
  if error.kind_of?(Ably::Models::ErrorInfo)
    renew_token_and_reconnect error if error.code == RESOLVABLE_ERROR_CODES.fetch(:token_expired)
    return
  end

  unless connection_retry_from_suspended_state?
    return if connection_retry_for(:disconnected, ignore_states: [:connecting])
  end

  return if connection_retry_for(:suspended, ignore_states: [:connecting])

  # Fallback if no other criteria met
  connection.transition_state_machine :failed, current_transition.
end

#respond_to_transport_disconnected_whilst_connected(current_transition) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

When a connection is disconnected after connecting, attempt reconnect and/or set state to :suspended or :failed



176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/ably/realtime/connection/connection_manager.rb', line 176

def respond_to_transport_disconnected_whilst_connected(current_transition)
  logger.warn "ConnectionManager: Connection to #{connection.transport.url} was disconnected unexpectedly"

  error = current_transition.
  if error.kind_of?(Ably::Models::ErrorInfo) && error.code != RESOLVABLE_ERROR_CODES.fetch(:token_expired)
    connection.emit :error, error
    logger.error "ConnectionManager: Error in Disconnected ProtocolMessage received from the server - #{error}"
  end

  destroy_transport
  respond_to_transport_disconnected_when_connecting current_transition
end

#retry_count_for_state(state) ⇒ Integer

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Number of consecutive attempts for provided state

Returns:

  • (Integer)


209
210
211
# File 'lib/ably/realtime/connection/connection_manager.rb', line 209

def retry_count_for_state(state)
  retries_for_state(state, ignore_states: [:connecting]).count
end

#setup_transport {|Ably::Realtime::Connection::WebsocketTransport| ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates and sets up a new WebsocketTransport available on attribute #transport

Yields:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ably/realtime/connection/connection_manager.rb', line 50

def setup_transport
  if transport && !transport.ready_for_release?
    raise RuntimeError, 'Existing WebsocketTransport is connected, and must be closed first'
  end

  unless client.auth.authentication_security_requirements_met?
    connection.transition_state_machine :failed, Ably::Exceptions::InsecureRequestError.new('Cannot use Basic Auth over non-TLS connections', 401, 40103)
    return
  end

  logger.debug 'ConnectionManager: Opening a websocket transport connection'

  connection.create_websocket_transport do |websocket_transport|
    subscribe_to_transport_events websocket_transport
    yield websocket_transport if block_given?
  end

  logger.debug "ConnectionManager: Setting up automatic connection timeout timer for #{TIMEOUTS.fetch(:open)}s"
  create_timeout_timer_whilst_in_state(:connect, TIMEOUTS.fetch(:open)) do
    connection_opening_failed Ably::Exceptions::ConnectionTimeoutError.new("Connection to Ably timed out after #{TIMEOUTS.fetch(:open)}s", nil, 80014)
  end
end