Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,25 @@ async def subscribe(client: PubSubRealtimeClient, name: str) -> None: ...
def publish_blocking(client: PubSubHttpClientSync, name: str) -> None: ...
```

### Releasing a realtime channel requires it to be detached

`channels.release(name)` on a realtime client now raises an `AblyException` with code 90011
if the channel is in any state other than `INITIALIZED`, `DETACHED` or `FAILED`, and leaves the
channel as it was. 3.x removed the channel from the collection whatever its state, which could
leave it attached in the Ably service, and later 3.x releases log a deprecation warning when
they do so. Detach the channel, and wait for that to complete, before releasing it:

Example 3.x code:
```python
realtime.channels.release('my-channel')
```

Example 4.0.0 code:
```python
await pubsub_realtime_client.channels.get('my-channel').detach()
pubsub_realtime_client.channels.release('my-channel')
```

## Version 2.x to 3.0.0

The 3.0.0 version of ably-python introduces several breaking changes to improve the realtime experience and align the API with the Ably specification. These include:
Expand Down
23 changes: 20 additions & 3 deletions ably/pubsub/realtime/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1085,17 +1085,34 @@ def release(self, name: str) -> None:
"""Releases a RealtimeChannel object, deleting it, and enabling it to be garbage collected

It also removes any listeners associated with the channel.
To release a channel, the channel state must be INITIALIZED, DETACHED, or FAILED.

A realtime channel can only be released when it is in the INITIALIZED, DETACHED, or FAILED state.

Parameters
----------
name: str
Channel name

Raises
------
AblyException
With code 90011 if the channel is in any other state. Await channel.detach() before
calling release().
"""
# RTS4c
if name not in self.__all:
return
channel = self.__all.pop(name)
channel = self.__all[name]
# RTS4e
if channel.state not in (ChannelState.INITIALIZED, ChannelState.DETACHED, ChannelState.FAILED):
raise AblyException(
'Can only release a channel in a state where there is no possibility of further updates '
'from the server being received (initialized, detached, or failed). The current state is '
f'{channel.state.value}',
400,
90011,
)
# RTS4d
del self.__all[name]
# The released channel receives nothing more, so its objects stop their GC timer and their waits
channel.object._release()

Expand Down
25 changes: 9 additions & 16 deletions test/unit/liveobjects/realtime_object_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,16 @@ def failing_replace_data(self, object_message):
# --- R2: the GC timer and waits of a released channel ---------------------------------------

async def test_rto10_a_released_channel_stops_its_gc_timer():
"""RTO10a: the GC timer every ATTACHED schedules stops when its channel is released. A
released channel is out of the client's collection, so closing the client never detaches
it; the release itself stops the timer, for good."""
"""RTO10a: the GC timer every ATTACHED schedules stops when its channel is detached, and
once the channel is released (RTS4) nothing schedules it again."""
clock = FakeClock()
client, channel, root, mock_ws = await setup_synced_channel('test', clock=clock)
timer = channel.object._gc_timer
assert timer is not None

await channel.detach()
client.channels.release('test')
channel.object._schedule_gc_timer()
await client.close()
await clock.advance(GC_INTERVAL_MS)

Expand All @@ -310,23 +311,15 @@ async def test_rto10_a_released_channel_stops_its_gc_timer():


async def test_rto23c1_a_wait_for_synced_fails_when_its_channel_is_released():
"""RTO23c1: a released channel receives nothing more, so it can never sync. A `get()`
waiting for SYNCED when the channel is released fails with 92008, and so does a later one,
at once."""
"""RTO23c1: a released channel receives nothing more, so it can never sync. A wait for
SYNCED begun after the channel is released fails with 92008 at once."""
client, channel, root, mock_ws = await setup_synced_channel('test')
await _restart_sync(channel, mock_ws)
task = asyncio.ensure_future(channel.object.get())
await settle()
assert not task.done()

await channel.detach()
client.channels.release('test')

with pytest.raises(AblyException) as excinfo:
await asyncio.wait_for(task, OPERATION_TIMEOUT)
_assert_sync_wait_failure(excinfo, 'released')

with pytest.raises(AblyException) as excinfo:
await asyncio.wait_for(channel.object.get(), OPERATION_TIMEOUT)
await asyncio.wait_for(channel.object._wait_for_synced('The object could not be retrieved'),
OPERATION_TIMEOUT)
_assert_sync_wait_failure(excinfo, 'released')


Expand Down
Loading
Loading