From 861bdc769ae58ee4c6f272313c2ac6fcc4527f02 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Fri, 17 Apr 2026 11:25:26 +0100 Subject: [PATCH] channels#release: change semantics to implement RTS4a ie implicit detach rather than error on unexpected channel state. (I honestly would prefer an error, this is a dangerous method and making it automagic is a step in the wrong direction, but all other SDKs implement the other behaviour so, shrug..) --- ably.d.ts | 2 +- src/common/lib/client/baserealtime.ts | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index 4f7c82fb5..c80717665 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2718,7 +2718,7 @@ export declare interface Channels { */ getDerived(name: string, deriveOptions: DeriveOptions, channelOptions?: ChannelOptions): T; /** - * Releases a {@link Channel} or {@link RealtimeChannel} object, deleting it, and enabling it to be garbage collected. To release a channel, the {@link ChannelState} must be `INITIALIZED`, `DETACHED`, or `FAILED`. + * Releases all SDK-held references to a {@link Channel} or {@link RealtimeChannel} object, enabling it to be garbage collected. Warning: this method has no guardrails; using a channel reference after it has been released is undefined behaviour. It can be useful for applications that work with a continually changing set of channels on a single client and need to avoid unbounded memory growth; if this does not describe you, don't call it. Realtime channels not already in the `INITIALIZED`, `DETACHED`, or `FAILED` state are detached before release. * * @param name - The channel name. */ diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 73a5411fb..c32251397 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -223,15 +223,29 @@ class Channels extends EventEmitter { * Please do not use this unless you know what you're doing */ release(name: string) { name = String(name); + Logger.logAction(this.logger, Logger.LOG_MAJOR, 'Channels.release()', 'Releasing references to channel ' + name); const channel = this.all[name]; if (!channel) { return; } - const releaseErr = channel.getReleaseErr(); - if (releaseErr) { - throw releaseErr; + const s = channel.state; + if (s === 'initialized' || s === 'detached' || s === 'failed') { + delete this.all[name]; + return; } - delete this.all[name]; + channel + .detach() + .catch((err) => { + Logger.logAction( + this.logger, + Logger.LOG_ERROR, + 'Channels.release()', + 'Error detaching channel ' + name + ' prior to release: ' + Utils.inspectError(err), + ); + }) + .then(() => { + delete this.all[name]; + }); } }