Deregister pull consumer heartbeat callbacks on stop - #963
Conversation
Pull message streams and batches register disconnect/reconnect callbacks on the client for the heartbeat timer but never removed them, so each created-then-stopped consumer left two callbacks — and the stream object they bound — alive for the connection's lifetime. Remove them from the same cleanup paths that already tear down the subscription.
|
Claude finished @caspervonb's task in 2m 28s —— View job PR Review: Deregister pull consumer heartbeat callbacks on stop
The fix is correct and minimal. The One issue worth addressing: The Tests cover the two main paths well: explicit |
| await self._subscription.unsubscribe() | ||
| self._deregister_callbacks() | ||
| self._terminated = True | ||
| raise StopAsyncIteration |
There was a problem hiding this comment.
The except (StopAsyncIteration, asyncio.TimeoutError) clause doesn't catch asyncio.CancelledError (a BaseException since Python 3.8). If the task iterating the batch is cancelled mid-flight, _deregister_callbacks() is skipped and the heartbeat callbacks leak — the same bug this PR fixes for the normal termination path.
This is technically a pre-existing gap (the subscription also isn't unsubscribed on cancellation), but it means the fix is incomplete for cancellation. Consider a try/finally wrapping both the unsubscribe and deregister calls.
CancelledError is a BaseException, so the StopAsyncIteration/timeout handler never saw it and a cancelled batch leaked its callbacks — the same leak this branch fixes for normal termination.
…ster-heartbeat-callbacks
A dedicated except clause for CancelledError still left gaps: any other unexpected exception escaping the iteration skipped both the unsubscribe and the heartbeat callback deregistration, leaking the callbacks on the client. Move the cleanup into a finally guarded by a delivering flag so every exit except a successful message delivery tears down the batch, and deregister before the awaited unsubscribe since that await can itself be interrupted by cancellation. Apply the same ordering to the exhausted-batch path at the top of __anext__.
|
Review comments addressed, PTAL @claude |
|
Claude finished @caspervonb's task in 1m 43s —— View job Re-reviewing PR #963
The previous review comment has been fully addressed. Here's what changed and how it holds up:
|
Pull message streams and batches register disconnect/reconnect callbacks on the client for the heartbeat timer (ADR-37) but never deregister them. Every created-then-stopped consumer therefore leaks two callbacks plus the stream object they bind, growing unbounded for any pattern that re-creates the message stream over one connection.
Removes them from the cleanup paths that already tear down the subscription, using the
remove_*_callbackmethods added in #957 (this PR is stacked on that branch).Closes #962.