Skip to content

RealtimeSession.close() can leave event iterators blocked #3284

@Aphroq

Description

@Aphroq

Please read this first

  • Have you read the docs? Yes.
  • Have you searched for related issues? Yes. I searched issues and PRs for RealtimeSession close iterator and Realtime close event_queue; I did not find a direct duplicate.

Describe the bug

RealtimeSession.__aiter__() waits on self._event_queue.get() while the session is open. RealtimeSession.close() eventually sets _closed=True, but it does not wake a consumer that is already blocked in Queue.get().

This can leave an async for event in session consumer blocked after another task closes the session.

Related paths and boundaries checked:

  • The existing cancellation behavior still exits cleanly when the consumer task is cancelled.
  • Closing an already closed session should not enqueue internal wakeup markers when no iterator is waiting.
  • Multiple simultaneous event iterators should all be woken when the session closes.
  • This is separate from tool-call cleanup and unknown-tool handling, which are covered by separate candidates.

Debug information

  • Agents SDK version: main at 683b6e79 (latest release tag checked locally: v0.17.0)
  • Python version: Python 3.12.1

Repro steps

Run this script against current main:

import asyncio
from typing import Any

from agents.realtime.agent import RealtimeAgent
from agents.realtime.model import RealtimeModel, RealtimeModelConfig
from agents.realtime.model_inputs import RealtimeModelSendEvent
from agents.realtime.session import RealtimeSession


class DummyModel(RealtimeModel):
    async def connect(self, options: RealtimeModelConfig) -> None:
        pass

    def add_listener(self, listener: Any) -> None:
        pass

    def remove_listener(self, listener: Any) -> None:
        pass

    async def send_event(self, event: RealtimeModelSendEvent) -> None:
        pass

    async def close(self) -> None:
        pass


async def main() -> None:
    session = RealtimeSession(DummyModel(), RealtimeAgent(name="agent"), None)
    iterator = session.__aiter__()
    next_event = asyncio.ensure_future(iterator.__anext__())

    await asyncio.sleep(0.01)
    await session.close()

    done, pending = await asyncio.wait({next_event}, timeout=0.1)
    print("done:", bool(done))
    print("pending:", bool(pending))

    for task in pending:
        task.cancel()
    await asyncio.gather(*pending, return_exceptions=True)


asyncio.run(main())

Actual output:

done: False
pending: True

Expected behavior

Closing a realtime session should wake iterators that are already waiting for the next event. The pending __anext__() should complete with StopAsyncIteration instead of staying blocked.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions