diff --git a/changelog/4440.fixed.md b/changelog/4440.fixed.md new file mode 100644 index 000000000..7be44d193 --- /dev/null +++ b/changelog/4440.fixed.md @@ -0,0 +1 @@ +- Fixed a race condition in the Daily transport that caused `AttributeError: 'NoneType' object has no attribute 'send_app_message'` when tearing down a pipeline. Both `DailyInputTransport` and `DailyOutputTransport` share the same `DailyTransportClient` and both call `cleanup()`, which was releasing the underlying `CallClient` on the first call — leaving the second caller with a `None` client. diff --git a/src/pipecat/transports/daily/transport.py b/src/pipecat/transports/daily/transport.py index 2d30ceecb..c4c76c0b0 100644 --- a/src/pipecat/transports/daily/transport.py +++ b/src/pipecat/transports/daily/transport.py @@ -526,6 +526,7 @@ class DailyTransportClient(EventHandler): self._joined = False self._joined_event = asyncio.Event() self._leave_counter = 0 + self._cleanup_counter = 0 self._task_manager: BaseTaskManager | None = None @@ -741,6 +742,7 @@ class DailyTransportClient(EventHandler): Args: setup: The frame processor setup configuration. """ + self._cleanup_counter += 1 if self._task_manager: return @@ -754,6 +756,12 @@ class DailyTransportClient(EventHandler): async def cleanup(self): """Cleanup client resources and cancel tasks.""" + # Decrement cleanup counter. DailyInputTransport and DailyOutputTransport + # share this client and both call cleanup(), so only run on the last call. + self._cleanup_counter -= 1 + if self._cleanup_counter > 0: + return + if self._event_task and self._task_manager: await self._task_manager.cancel_task(self._event_task) self._event_task = None