chore(release): backport #31733 to stable/1.89.x - #31779
Conversation
…er (#31733) RealTimeStreaming.log_messages dispatched the success handler with a bare asyncio.create_task, bypassing GLOBAL_LOGGING_WORKER (which gives a per-coroutine timeout and a concurrency cap). On a long-lived realtime websocket a slow logging callback left one suspended task per logged turn, each pinning that turn's assembled response, accumulating without bound (~12-15k in-flight under load in a repro) until OOM. Route realtime success logging through the bounded worker so in-flight logging is capped and a hung callback is cancelled at the worker timeout. The chat and responses streaming success-logging paths are intentionally left unchanged: their success callbacks must complete within the call's event-loop run (the non-streaming path pairs the worker with a synchronous callback; the streaming path has no such companion), so deferring them through the worker would drop logs for one-shot SDK calls and breaks test_async_custom_handler_stream. Bounding those paths needs a load-shedding approach and is left to a follow-up. (cherry picked from commit d4c33b2)
Greptile SummaryThis backport routes realtime success logging through
Confidence Score: 5/5The change is a minimal, targeted fix — one import and one line replaced in the production file, plus one new regression test. The bounded worker path is already exercised elsewhere in the codebase; this just connects realtime logging to it. Both changed files are focused and correct. The production change swaps an unbounded No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/realtime_streaming.py | One-line fix replacing bare asyncio.create_task with GLOBAL_LOGGING_WORKER.ensure_initialized_and_enqueue for realtime success logging, preventing unbounded task accumulation on long-lived websockets. |
| tests/test_litellm/litellm_core_utils/test_realtime_streaming.py | Adds test_log_messages_routes_async_logging_through_bounded_worker regression test that verifies GLOBAL_LOGGING_WORKER.ensure_initialized_and_enqueue is called and bare asyncio.create_task is not used for success logging. |
Reviews (1): Last reviewed commit: "fix(logging): route realtime success log..." | Re-trigger Greptile
Relevant issues
Backports #31733 onto stable/1.89.x. RealTimeStreaming.log_messages dispatched the realtime success handler with a bare asyncio.create_task, bypassing GLOBAL_LOGGING_WORKER (the bounded logging worker that provides a per-coroutine timeout and a concurrency cap). On a long-lived realtime websocket a slow logging callback left one suspended task per logged turn, each pinning that turn's assembled response, accumulating without bound (~12-15k in-flight under load in a repro) until the proxy ran out of memory. Routing realtime success logging through the bounded worker caps in-flight logging and cancels a hung callback at the worker timeout
No version bump. The line tip is already at 1.89.5 (bumped by the prior backport and not yet released), so this pick rides the pending 1.89.5
Linear ticket
Pre-Submission checklist
What is included
Originally requested alongside #31519, but #31519 was dropped from this line. #31519 is built on top of #30960, a large realtime restructure that is not on stable/1.89.x, so #31519's changes reference machinery (the setup-vs-content send loop and the renamed guardrail helper) that this line does not have. Backporting #31519 here would mean also backporting that restructure, which is out of scope for this patch; #31733 is independent of it and stands alone
Adaptation notes
#31733 is ADAPTED (context-only, no logic change). The fix's own lines are byte-identical to the source commit; two divergences come from this line predating the realtime restructure:
Known noise on this line
None. The targeted test set was green at baseline (112 passed, 0 failed) before any pick
Screenshots / Proof of Fix
Live proxy on localhost, hitting the real OpenAI API (sanity floor, judged as a delta against the pre-pick baseline):
The bug itself is a memory leak on long-lived realtime (Gemini Live) websockets under load, which is not reproducible with a single curl. The behavioral proof is the regression test, which is mutation-checked: with the fix in place test_log_messages_routes_async_logging_through_bounded_worker passes; reverting the one-line change back to the bare asyncio.create_task makes it fail. Targeted test delta: 112 passed at baseline, 113 passed after the pick (the +1 is the new regression test), 0 new failures
Gauntlet (behavioral, universal): SURVIVED. All three sub-claims held; every identifier the pick references resolves on this line, the new regression test passes as a clean +1 delta, and the sole production caller of log_messages is structurally unaffected. Five investigator lenses (including two tasked as refuters) converged with no verified refutation; the only artifact surfaced was a benign pre-existing RuntimeWarning under certain test orderings that fails no test and is not introduced by this pick
Type
🐛 Bug Fix
Changes
Routes realtime success logging through GLOBAL_LOGGING_WORKER so in-flight logging on realtime websockets is bounded