spec/acceptance/realtime/connection_failures_spec.rb in ably-0.7.2 vs spec/acceptance/realtime/connection_failures_spec.rb in ably-0.7.4
- old
+ new
@@ -64,11 +64,12 @@
disconnected: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
suspended: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
)
end
- let(:expected_retry_attempts) { (max_time_in_state_for_tests / retry_every_for_tests).round }
+ # retry immediately after failure, then one retry every :retry_every_for_tests
+ let(:expected_retry_attempts) { 1 + (max_time_in_state_for_tests / retry_every_for_tests).round }
let(:state_changes) { Hash.new { |hash, key| hash[key] = 0 } }
let(:timer) { Hash.new }
let(:client_options) do
client_failure_options.merge(realtime_host: 'non.existent.host')
@@ -286,25 +287,80 @@
connection.on(:suspended) { raise 'Connection should not have reached :suspended state' }
connection.on(:failed) { raise 'Connection should not have reached :failed state' }
end
context 'when DISCONNECTED ProtocolMessage received from the server' do
- it 'reconnects automatically' do
+ it 'reconnects automatically and immediately' do
fail_if_suspended_or_failed
connection.once(:connected) do
connection.once(:disconnected) do
- connection.once(:connected) do
- state_history = connection.state_history.map { |transition| transition[:state].to_sym }
- expect(state_history).to eql([:connecting, :connected, :disconnected, :connecting, :connected])
- stop_reactor
+ disconnected_at = Time.now.to_f
+ connection.once(:connecting) do
+ expect(Time.now.to_f).to be_within(0.25).of(disconnected_at)
+ connection.once(:connected) do
+ state_history = connection.state_history.map { |transition| transition[:state].to_sym }
+ expect(state_history).to eql([:connecting, :connected, :disconnected, :connecting, :connected])
+ stop_reactor
+ end
end
end
protocol_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Disconnected.to_i)
connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
end
end
+
+ context 'and subsequently fails to reconnect' do
+ let(:retry_every) { 1.5 }
+
+ before do
+ # Reconfigure client library retry periods and timeouts so that tests run quickly
+ stub_const 'Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG',
+ Ably::Realtime::Connection::ConnectionManager::CONNECT_RETRY_CONFIG.merge(
+ disconnected: { retry_every: retry_every, max_time_in_state: 60 })
+ end
+
+ it "retries every CONNECT_RETRY_CONFIG[:disconnected][:retry_every] seconds" do
+ fail_if_suspended_or_failed
+
+ stubbed_first_attempt = false
+
+ connection.once(:connected) do
+ connection.once(:disconnected) do
+ connection.once(:connecting) do
+ connection.once(:disconnected) do
+ disconnected_at = Time.now.to_f
+ connection.once(:connecting) do
+ expect(Time.now.to_f - disconnected_at).to be > retry_every
+ state_history = connection.state_history.map { |transition| transition[:state].to_sym }
+ expect(state_history).to eql([:connecting, :connected, :disconnected, :connecting, :disconnected, :connecting])
+
+ # allow one more recoonect when reactor stopped
+ expect(connection.manager).to receive(:reconnect_transport)
+ stop_reactor
+ end
+ end
+
+ # When reconnect called simply open the transport and close immediately
+ expect(connection.manager).to receive(:reconnect_transport) do
+ next if stubbed_first_attempt
+
+ connection.manager.setup_transport do
+ EventMachine.next_tick do
+ connection.transport.unbind
+ stubbed_first_attempt = true
+ end
+ end
+ end
+ end
+ end
+
+ protocol_message = Ably::Models::ProtocolMessage.new(action: Ably::Models::ProtocolMessage::ACTION.Disconnected.to_i)
+ connection.__incoming_protocol_msgbus__.publish :protocol_message, protocol_message
+ end
+ end
+ end
end
context 'when websocket transport is closed' do
it 'reconnects automatically' do
fail_if_suspended_or_failed
@@ -357,10 +413,20 @@
end
end
end
end
+ it 'triggers the resume callback', api_private: true do
+ channel.attach do
+ connection.transport.close_connection_after_writing
+ connection.on_resume do
+ expect(connection).to be_connected
+ stop_reactor
+ end
+ end
+ end
+
context 'when messages were published whilst the client was disconnected' do
it 'receives the messages published whilst offline' do
messages_received = false
channel.subscribe('event') do |message|
@@ -392,59 +458,61 @@
end
end
end
end
- context 'when failing to resume because the connection_key is not or no longer valid' do
- def kill_connection_transport_and_prevent_valid_resume
- connection.transport.close_connection_after_writing
- connection.configure_new '0123456789abcdef', '0123456789abcdef', -1 # force the resume connection key to be invalid
- end
+ context 'when failing to resume' do
+ context 'because the connection_key is not or no longer valid' do
+ def kill_connection_transport_and_prevent_valid_resume
+ connection.transport.close_connection_after_writing
+ connection.configure_new '0123456789abcdef', '0123456789abcdef', -1 # force the resume connection key to be invalid
+ end
- it 'updates the connection_id and connection_key' do
- connection.once(:connected) do
- previous_connection_id = connection.id
- previous_connection_key = connection.key
-
+ it 'updates the connection_id and connection_key' do
connection.once(:connected) do
- expect(connection.key).to_not eql(previous_connection_key)
- expect(connection.id).to_not eql(previous_connection_id)
- stop_reactor
- end
+ previous_connection_id = connection.id
+ previous_connection_key = connection.key
- kill_connection_transport_and_prevent_valid_resume
- end
- end
-
- it 'detaches all channels' do
- channel_count = 10
- channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
- when_all(*channels.map(&:attach)) do
- detached_channels = []
- channels.each do |channel|
- channel.on(:detached) do
- detached_channels << channel
- next unless detached_channels.count == channel_count
- expect(detached_channels.count).to eql(channel_count)
+ connection.once(:connected) do
+ expect(connection.key).to_not eql(previous_connection_key)
+ expect(connection.id).to_not eql(previous_connection_id)
stop_reactor
end
- end
- kill_connection_transport_and_prevent_valid_resume
+ kill_connection_transport_and_prevent_valid_resume
+ end
end
- end
- it 'emits an error on the channel and sets the error reason' do
- client.channel(random_str).attach do |channel|
- channel.on(:error) do |error|
- expect(error.message).to match(/Invalid connection key/i)
- expect(error.code).to eql(80008)
- expect(channel.error_reason).to eql(error)
- stop_reactor
+ it 'detaches all channels' do
+ channel_count = 10
+ channels = channel_count.times.map { |index| client.channel("channel-#{index}") }
+ when_all(*channels.map(&:attach)) do
+ detached_channels = []
+ channels.each do |channel|
+ channel.on(:detached) do
+ detached_channels << channel
+ next unless detached_channels.count == channel_count
+ expect(detached_channels.count).to eql(channel_count)
+ stop_reactor
+ end
+ end
+
+ kill_connection_transport_and_prevent_valid_resume
end
+ end
- kill_connection_transport_and_prevent_valid_resume
+ it 'emits an error on the channel and sets the error reason' do
+ client.channel(random_str).attach do |channel|
+ channel.on(:error) do |error|
+ expect(error.message).to match(/Invalid connection key/i)
+ expect(error.code).to eql(80008)
+ expect(channel.error_reason).to eql(error)
+ stop_reactor
+ end
+
+ kill_connection_transport_and_prevent_valid_resume
+ end
end
end
end
end
@@ -459,10 +527,12 @@
disconnected: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
suspended: { retry_every: retry_every_for_tests, max_time_in_state: max_time_in_state_for_tests },
)
end
- let(:expected_retry_attempts) { (max_time_in_state_for_tests / retry_every_for_tests).round }
+ # Retry immediately and then wait retry_every before every subsequent attempt
+ let(:expected_retry_attempts) { 1 + (max_time_in_state_for_tests / retry_every_for_tests).round }
+
let(:retry_count_for_one_state) { 1 + expected_retry_attempts } # initial connect then disconnected
let(:retry_count_for_all_states) { 1 + expected_retry_attempts * 2 } # initial connection, disconnected & then suspended
context 'with custom realtime websocket host option' do
let(:expected_host) { 'this.host.does.not.exist' }