From 1bbbf7bff2e26092664414c2003017c64310b4e3 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 9 Oct 2026 04:09:16 +0100 Subject: [PATCH] Deprecate releasing a realtime channel that isn't detached (RTS4b) Spec 6.3.0 deletes RTS4a, under which Channels#release detaches the channel before removing it, and replaces it with RTS4e, under which release raises error 90011 unless the channel is initialized, detached or failed. RTS4b lets SDKs that already implement the old behaviour keep it until their next major version, provided they log a deprecation warning when it takes effect. Channels in the initialized, detached or failed state are now removed immediately (RTS4d) rather than via detach. Previously a failed channel could never be released, since detaching it fails. Co-Authored-By: Claude Opus 5.5 (1M context) --- lib/ably/realtime/channels.rb | 26 ++++++++++++++++++--- spec/unit/realtime/channels_spec.rb | 36 +++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 8 deletions(-) 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