fix(passthrough): keep the original exception when closing an abandoned upstream fails
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-26 17:19:15 +00:00
parent cf5659f025
commit 5beef60327
2 changed files with 43 additions and 2 deletions

View file

@ -920,7 +920,13 @@ class _PreviewReportingStream(httpx.AsyncByteStream):
async def abandon(self) -> None:
self._abandoned = True
self.dispatch()
await self._upstream.aclose()
try:
await self._upstream.aclose()
except Exception as err: # noqa: BLE001 # aclose failures must not mask the exception that abandoned the relay
self._log_warning(
"pass_through_endpoint: closing the abandoned upstream error response failed: %s",
type(err).__name__,
)
async def __aiter__(self) -> AsyncIterator[bytes]:
total = 0 # rebind-ok: running byte count against the preview budget

View file

@ -4173,12 +4173,15 @@ class _UpstreamErrorBodyStream(httpx.AsyncByteStream):
class _UpstreamErrorBodyStreamCloseTracking(_UpstreamErrorBodyStream):
def __init__(self, body: bytes) -> None:
def __init__(self, body: bytes, close_error: Exception | None = None) -> None:
super().__init__(body)
self._close_error: Final = close_error
self.closed = False
async def aclose(self) -> None:
self.closed = True
if self._close_error is not None:
raise self._close_error
def _upstream_error_request() -> MagicMock:
@ -5096,6 +5099,38 @@ async def test_preview_reporting_stream_abandon_does_not_log_a_client_disconnect
assert all("client disconnected" not in call[0] for call in warnings), warnings
@pytest.mark.asyncio
async def test_preview_reporting_stream_abandon_keeps_the_original_exception_when_aclose_fails():
"""A failing upstream close must not replace the exception that abandoned the
relay: abandon returns without raising, still reports, and logs the close failure."""
upstream_stream: Final = _UpstreamErrorBodyStreamCloseTracking(
b"body", close_error=httpx.CloseError("close failed")
)
upstream_response: Final = httpx.Response(
status_code=500,
headers={"content-type": "text/event-stream"},
stream=upstream_stream,
request=httpx.Request("POST", "http://target-api.com/v1beta/models/claude-nope-9:streamGenerateContent"),
)
reported: list[bytes] = []
log_warning: Final = MagicMock()
async def report(preview: bytes) -> None:
reported.append(preview)
relay: Final = _PreviewReportingStream(
upstream=upstream_response,
report=report,
log_warning=log_warning,
)
await relay.abandon()
await relay.dispatch()
assert reported == [b""], reported
warnings: Final = [call.args[0] for call in log_warning.call_args_list]
assert any("closing the abandoned upstream error response failed" in message for message in warnings), warnings
@pytest.mark.asyncio
async def test_preview_report_collected_runs_without_disconnect_warning_after_clean_end():
upstream_response: Final = httpx.Response(