diff --git a/lib/ably/realtime/channel/channel_state_machine.rb b/lib/ably/realtime/channel/channel_state_machine.rb index 179ef39da..12526e18a 100644 --- a/lib/ably/realtime/channel/channel_state_machine.rb +++ b/lib/ably/realtime/channel/channel_state_machine.rb @@ -26,12 +26,16 @@ class ChannelStateMachine transition :from => :detaching, :to => [:detached, :attaching, :attached, :failed, :suspended] transition :from => :detached, :to => [:attaching, :attached, :failed] transition :from => :suspended, :to => [:attaching, :attached, :detached, :failed] - transition :from => :failed, :to => [:attaching] + transition :from => :failed, :to => [:attaching, :initialized] after_transition do |channel, transition| channel.synchronize_state_with_statemachine end + after_transition(to: [:initialized]) do |channel| + channel.clear_error_reason + end + after_transition(to: [:attaching]) do |channel| channel.manager.attach end diff --git a/lib/ably/realtime/connection/connection_manager.rb b/lib/ably/realtime/connection/connection_manager.rb index 4672d0596..dd6f99fca 100644 --- a/lib/ably/realtime/connection/connection_manager.rb +++ b/lib/ably/realtime/connection/connection_manager.rb @@ -319,6 +319,15 @@ def fail_active_channels(error) end end + # @api private + def reintialize_failed_chanels + channels.select do |channel| + channel.failed? + end.each do |channel| + channel.transition_state_machine :initialized + end + end + # When continuity on a connection is lost all messages # whether queued or awaiting an ACK must be NACK'd as we now have a new connection def nack_messages_on_all_channels(error) diff --git a/lib/ably/realtime/connection/connection_state_machine.rb b/lib/ably/realtime/connection/connection_state_machine.rb index c93c6a9af..e3650efaa 100644 --- a/lib/ably/realtime/connection/connection_state_machine.rb +++ b/lib/ably/realtime/connection/connection_state_machine.rb @@ -36,6 +36,10 @@ class ConnectionStateMachine connection.manager.setup_transport end + after_transition(to: [:connecting], from: [:failed]) do |connection| + connection.manager.reintialize_failed_chanels + end + after_transition(to: [:connecting], from: [:disconnected, :suspended]) do |connection| connection.manager.reconnect_transport end diff --git a/spec/acceptance/realtime/connection_spec.rb b/spec/acceptance/realtime/connection_spec.rb index 196224733..074dfd5b4 100644 --- a/spec/acceptance/realtime/connection_spec.rb +++ b/spec/acceptance/realtime/connection_spec.rb @@ -565,6 +565,35 @@ def expect_ordered_phases end end + context 'when reconnecting a failed connection' do + let(:channel) { client.channel(random_str) } + let(:client_options) { default_options.merge(log_level: :none) } + + it 'transitions all channels state to initialized and cleares error_reason' do + connection.on(:failed) do |connection_state_change| + expect(connection.error_reason).to be_a(Ably::Exceptions::BaseAblyException) + expect(channel.error_reason).to be_a(Ably::Exceptions::BaseAblyException) + expect(channel).to be_failed + + connection.on(:connected) do + expect(connection.error_reason).to eq(nil) + expect(channel).to be_initialized + expect(channel.error_reason).to eq(nil) + stop_reactor + end + + connection.connect + end + + connection.connect do + channel.attach do + error = Ably::Exceptions::ConnectionFailed.new('forced failure', 500, 50000) + client.connection.manager.error_received_from_server error + end + end + end + end + context 'with invalid auth details' do let(:client_options) { default_options.merge(key: 'this.is:invalid', log_level: :none) }