Skip to content

Commit 3f49877

Browse files
author
Lucas Wang
committed
Fix voice STT task cleanup to properly await cancelled tasks
Problem: The _cleanup_tasks() method in OpenAISTTTranscriptionSession was only calling task.cancel() on pending tasks (listener, process_events, stream_audio, connection) but not awaiting them. This could lead to: 1. Unhandled task exception warnings 2. Potential resource leaks (websocket connections, file descriptors) 3. Improper cleanup of background tasks Evidence: - Similar to recently fixed guardrail tasks cleanup (PR openai#1976) - Similar to fixed websocket task cleanup (PR openai#1955) - asyncio best practices require awaiting cancelled tasks Solution: 1. Made _cleanup_tasks() async 2. Collect all real asyncio.Task objects that need to be awaited 3. Added await asyncio.gather() with return_exceptions=True to properly collect exceptions from cancelled tasks 4. Updated close() method to await _cleanup_tasks() Testing: - All existing voice/STT tests pass (17 passed) - Uses isinstance check to support mock objects in tests - Follows the same pattern as PR openai#1976 and PR openai#1955
1 parent 8c4d4d0 commit 3f49877

File tree

1 file changed

+21
-2
lines changed

1 file changed

+21
-2
lines changed

src/agents/voice/models/openai_stt.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,18 +321,37 @@ def _check_errors(self) -> None:
321321
if exc and isinstance(exc, Exception):
322322
self._stored_exception = exc
323323

324-
def _cleanup_tasks(self) -> None:
324+
async def _cleanup_tasks(self) -> None:
325+
"""Cancel all pending tasks and wait for them to complete.
326+
327+
This ensures that any exceptions raised by the tasks are properly handled
328+
and prevents warnings about unhandled task exceptions.
329+
"""
330+
tasks = []
331+
325332
if self._listener_task and not self._listener_task.done():
326333
self._listener_task.cancel()
334+
if isinstance(self._listener_task, asyncio.Task):
335+
tasks.append(self._listener_task)
327336

328337
if self._process_events_task and not self._process_events_task.done():
329338
self._process_events_task.cancel()
339+
if isinstance(self._process_events_task, asyncio.Task):
340+
tasks.append(self._process_events_task)
330341

331342
if self._stream_audio_task and not self._stream_audio_task.done():
332343
self._stream_audio_task.cancel()
344+
if isinstance(self._stream_audio_task, asyncio.Task):
345+
tasks.append(self._stream_audio_task)
333346

334347
if self._connection_task and not self._connection_task.done():
335348
self._connection_task.cancel()
349+
if isinstance(self._connection_task, asyncio.Task):
350+
tasks.append(self._connection_task)
351+
352+
# Wait for all cancelled tasks to complete and collect exceptions
353+
if tasks:
354+
await asyncio.gather(*tasks, return_exceptions=True)
336355

337356
async def transcribe_turns(self) -> AsyncIterator[str]:
338357
self._connection_task = asyncio.create_task(self._process_websocket_connection())
@@ -367,7 +386,7 @@ async def close(self) -> None:
367386
if self._websocket:
368387
await self._websocket.close()
369388

370-
self._cleanup_tasks()
389+
await self._cleanup_tasks()
371390

372391

373392
class OpenAISTTModel(STTModel):

0 commit comments

Comments
 (0)