Skip to content
Open
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
4 changes: 3 additions & 1 deletion livekit-agents/livekit/agents/utils/http_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@ async def _close_http_ctx() -> None:
val = _ContextVar.get(None)
if val is not None:
logger.debug("http_session(): closing the httpclient ctx")
await val().close()
session = val()
if not session.closed:
await session.close()
Comment on lines +58 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 _close_http_ctx still creates a new HTTP session just to close it

The PR adds a session.closed check in _close_http_ctx with the stated goal of "avoid creating a new session just to immediately close it." However, the check is ineffective because val() calls the _new_session factory, which at http_context.py:19 creates a new aiohttp.ClientSession whenever g_session is None or g_session.closed. The newly created session will never have closed == True, so the guard on line 59 never prevents the close.

Root Cause

The _new_session closure (line 17-34) always returns a non-closed session. When g_session is None (HTTP was never used during the job) or already closed, val() creates a brand new aiohttp.ClientSession with a TCPConnector. The new session's .closed property is False, so the check if not session.closed on line 59 passes and the session is closed immediately after creation.

Scenario: A job runs without ever calling http_session(). At shutdown, _close_http_ctx() is called (job_proc_lazy_main.py:367). val is not None (set by _new_session_ctx() at line 283), so val() is invoked, creating an unnecessary aiohttp.ClientSession and TCPConnector just to immediately close them.

The fix should avoid calling val() entirely when no session was ever created, or the factory should be modified to not create a session when one doesn't already exist.

Impact: Unnecessary resource allocation (TCP connector, HTTP session) during every job shutdown where HTTP was unused. Not a crash, but defeats the stated purpose of the fix.

Prompt for agents
The _close_http_ctx function needs to avoid calling val() (the factory) when no session has been created yet, because the factory always creates a new session if one doesn't exist. One approach: modify _new_session_ctx to expose a way to check if g_session exists without creating one. For example, add a separate function or attribute to the closure that returns g_session directly (without the lazy-creation logic). Alternatively, restructure _new_session_ctx to use a wrapper object that tracks whether a session was ever created, and check that in _close_http_ctx before calling val(). The simplest fix might be to change _new_session to not auto-create when g_session is None, and instead have a separate creation path, but that would break the existing http_session() API. A practical approach: store g_session as an attribute on the _new_session function itself (e.g., _new_session._session = g_session) so _close_http_ctx can check _new_session._session directly without triggering creation.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

_ContextVar.set(None)
5 changes: 4 additions & 1 deletion livekit-agents/livekit/agents/voice/agent_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ async def start(
room_input_options=room_input_options,
room_output_options=room_output_options,
)
room_options = copy.copy(room_options) # shadow copy is enough
room_options = copy.copy(room_options) # shallow copy is enough

if self.input.audio is not None:
if room_options.audio_input:
Expand Down Expand Up @@ -829,6 +829,9 @@ async def _aclose_impl(
if self._forward_audio_atask is not None:
await utils.aio.cancel_and_wait(self._forward_audio_atask)

if self._forward_video_atask is not None:
await utils.aio.cancel_and_wait(self._forward_video_atask)

if self._recorder_io:
await self._recorder_io.aclose()

Expand Down
1 change: 1 addition & 0 deletions livekit-agents/livekit/agents/voice/room_io/room_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def room(self) -> rtc.Room:
async def aclose(self) -> None:
self._room.off("participant_connected", self._on_participant_connected)
self._room.off("connection_state_changed", self._on_connection_state_changed)
self._room.off("participant_disconnected", self._on_participant_disconnected)
self._agent_session.off("agent_state_changed", self._on_agent_state_changed)
self._agent_session.off("user_input_transcribed", self._on_user_input_transcribed)
self._agent_session.off("close", self._on_agent_session_close)
Expand Down
Loading