From b68ac32580445b261cc8bde5dee8037da9ab6917 Mon Sep 17 00:00:00 2001 From: Vamsi-2581 <245859299+Vamsi-2581@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:00:36 +0530 Subject: [PATCH 1/2] fix(proxy): release Presidio session lock before requests --- litellm/proxy/guardrails/guardrail_hooks/presidio.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/presidio.py b/litellm/proxy/guardrails/guardrail_hooks/presidio.py index cadc9ca689b..cc11c09ec64 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/presidio.py +++ b/litellm/proxy/guardrails/guardrail_hooks/presidio.py @@ -388,7 +388,8 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): async with self._session_lock: if self._http_session is None or self._http_session.closed: self._http_session = aiohttp.ClientSession() - yield self._http_session + session = self._http_session + yield session else: # Background thread/loop -> use loop-bound session cache # This avoids "attached to a different loop" or "no running event loop" errors From 181d841e294912f82c78a0912d4b42ddbb24360a Mon Sep 17 00:00:00 2001 From: Vamsi-2581 <245859299+Vamsi-2581@users.noreply.github.com> Date: Tue, 20 Oct 2026 21:00:59 +0530 Subject: [PATCH 2/2] test(proxy): cover concurrent Presidio session use --- .../guardrail_hooks/test_presidio.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/unit/proxy/guardrails/guardrail_hooks/test_presidio.py b/tests/unit/proxy/guardrails/guardrail_hooks/test_presidio.py index bd1e9ae4068..e9c6f3ae6f5 100644 --- a/tests/unit/proxy/guardrails/guardrail_hooks/test_presidio.py +++ b/tests/unit/proxy/guardrails/guardrail_hooks/test_presidio.py @@ -5070,3 +5070,24 @@ def test_get_session_iterator_reuses_one_session_per_background_loop( assert len(sessions) == 10 assert all(session is sessions[0] for session in sessions) assert presidio_guardrail._http_session is None + + +@pytest.mark.asyncio +async def test_get_session_iterator_does_not_hold_lock_while_session_is_in_use( + presidio_guardrail: OPTIONAL_PresidioPIIMasking, +) -> None: + both_entered = asyncio.Event() + active_users = 0 + + async def use_session() -> None: + nonlocal active_users + async with presidio_guardrail._get_session_iterator(): + active_users += 1 + if active_users == 2: + both_entered.set() + await asyncio.wait_for(both_entered.wait(), timeout=1) + + try: + await asyncio.gather(use_session(), use_session()) + finally: + await presidio_guardrail._close_http_session()