diff --git a/lib/ably/realtime/channels.rb b/lib/ably/realtime/channels.rb index e7cf788d..ead0114d 100644 --- a/lib/ably/realtime/channels.rb +++ b/lib/ably/realtime/channels.rb @@ -34,17 +34,37 @@ def fetch(*args) super end - # Detaches the {Ably::Realtime::Channel Realtime Channel} and releases all associated resources. + # Releases the {Ably::Realtime::Channel Realtime Channel} with the given name and all associated resources. # # Releasing a Realtime Channel is not typically necessary as a channel, once detached, consumes no resources other than # the memory footprint of the {Ably::Realtime::Channel Realtime Channel object}. Release channels to free up resources if required # + # A realtime channel should only be released when it is in the +INITIALIZED+, +DETACHED+, or +FAILED+ state; + # releasing a realtime channel in any other state is deprecated and will raise an error in the next major version. + # + # @spec RTS4b, RTS4c, RTS4d + # + # @param channel [String] The name of the channel + # # @return [void] # def release(channel) - get(channel).detach do + return unless @channels.has_key?(channel) + + released_channel = get(channel) + if released_channel.initialized? || released_channel.detached? || released_channel.failed? @channels.delete(channel) - end if @channels.has_key?(channel) + return + end + + logger.warn do + "Channels#release: Calling release on a channel in the #{released_channel.state.to_sym} state is deprecated, " \ + "and will raise an error in the next major version. " \ + "Call Channel#detach and wait for it to complete before calling Channels#release" + end + released_channel.detach do + @channels.delete(channel) + end end # Sets channel serial to each channel from given serials hashmap diff --git a/spec/unit/realtime/channels_spec.rb b/spec/unit/realtime/channels_spec.rb index a415a714..78d0c429 100644 --- a/spec/unit/realtime/channels_spec.rb +++ b/spec/unit/realtime/channels_spec.rb @@ -98,11 +98,37 @@ end context 'destroying channels' do - it '#release detaches and then releases the channel resources' do - released_channel = subject.get(channel_name, options) - expect(released_channel).to receive(:detach).and_yield - subject.release(channel_name) - expect(subject.get(channel_name, options).object_id).to_not eql(released_channel.object_id) + let(:released_channel) { subject.get(channel_name, options) } + + def stub_state(state) + allow(released_channel).to receive(:state).and_return(Ably::Realtime::Channel::STATE(state)) + end + + it '#release does nothing if the channel does not exist (RTS4c)' do + expect { subject.release(channel_name) }.to_not raise_error + expect(subject.length).to eql(0) + end + + [:initialized, :detached, :failed].each do |state| + it "#release releases a channel in the #{state} state without detaching or logging a warning (RTS4d)" do + stub_state state + expect(released_channel).to_not receive(:detach) + expect(client.logger).to_not receive(:warn) + subject.release(channel_name) + expect(subject.fetch(channel_name) { nil }).to be_nil + end + end + + [:attaching, :attached, :detaching, :suspended].each do |state| + it "#release logs a deprecation warning, then detaches and releases a channel in the #{state} state (RTS4b)" do + stub_state state + expect(client.logger).to receive(:warn) do |&block| + expect(block.call).to match(/release on a channel in the #{state} state is deprecated/) + end + expect(released_channel).to receive(:detach).and_yield + subject.release(channel_name) + expect(subject.get(channel_name, options).object_id).to_not eql(released_channel.object_id) + end end end