mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-10 08:28:25 +00:00
Http connection reset (#25434)
* Possible fix for connection reset happening during read(), since payload sizes are large there can be a connection reset mid read. Goal: Reproduce this minimally with aiohttp and write test cases * Add debug logs and manually capture 104 connection reset errors, and retry. This is a id fix and doesnt fix root cause. Root cause could be due to firewall or proxy resets or connection resets due as rate limiting. * Simplify exception handling in HTTP request methods Removed ReadError from exception handling in multiple request methods.
This commit is contained in:
parent
e7551a1d43
commit
94df3e30e4
2 changed files with 91 additions and 1 deletions
|
|
@ -113,6 +113,23 @@ def _prepare_request_data_and_content(
|
|||
return request_data, request_content
|
||||
|
||||
|
||||
def _is_connection_reset_error(e: Exception) -> bool:
|
||||
error_text = getattr(e, "text", str(e))
|
||||
error_response = getattr(e, "response", None)
|
||||
if error_response and hasattr(error_response, "text"):
|
||||
error_text = getattr(error_response, "text", error_text)
|
||||
|
||||
if isinstance(error_text, bytes):
|
||||
error_text = error_text.decode("utf-8", errors="ignore")
|
||||
|
||||
normalized_error_text = str(error_text).lower()
|
||||
return (
|
||||
"errno 104" in normalized_error_text
|
||||
or "connection reset by peer" in normalized_error_text
|
||||
or "connection reset" in normalized_error_text
|
||||
)
|
||||
|
||||
|
||||
# Cache for SSL contexts to avoid creating duplicate contexts with the same configuration
|
||||
# Key: tuple of (cafile, ssl_security_level, ssl_ecdh_curve)
|
||||
# Value: ssl.SSLContext
|
||||
|
|
@ -482,7 +499,14 @@ class AsyncHTTPHandler:
|
|||
params=params,
|
||||
headers=headers,
|
||||
stream=stream,
|
||||
content=content,
|
||||
)
|
||||
except Exception as retry_error:
|
||||
verbose_logger.debug(
|
||||
"httpx.ConnectError retry failed in AsyncHTTPHandler.post(): %s",
|
||||
str(retry_error),
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
await new_client.aclose()
|
||||
except httpx.TimeoutException as e:
|
||||
|
|
@ -512,6 +536,32 @@ class AsyncHTTPHandler:
|
|||
|
||||
raise e
|
||||
except Exception as e:
|
||||
if _is_connection_reset_error(e):
|
||||
verbose_logger.debug(
|
||||
"Detected connection reset error in AsyncHTTPHandler.post(); retrying with a new client"
|
||||
)
|
||||
new_client = self.create_client(
|
||||
timeout=timeout, event_hooks=self.event_hooks
|
||||
)
|
||||
try:
|
||||
return await self.single_connection_post_request(
|
||||
url=url,
|
||||
client=new_client,
|
||||
data=data,
|
||||
json=json,
|
||||
params=params,
|
||||
headers=headers,
|
||||
stream=stream,
|
||||
content=content,
|
||||
)
|
||||
except Exception as retry_error:
|
||||
verbose_logger.debug(
|
||||
"Connection reset retry failed in AsyncHTTPHandler.post(): %s",
|
||||
str(retry_error),
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
await new_client.aclose()
|
||||
raise e
|
||||
|
||||
async def put(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import os
|
|||
import pathlib
|
||||
import ssl
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import certifi
|
||||
import httpx
|
||||
|
|
@ -658,3 +658,43 @@ async def test_httpx_handler_uses_env_user_agent(monkeypatch):
|
|||
assert req.headers.get("User-Agent") == "Claude Code"
|
||||
finally:
|
||||
await handler.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_http_handler_retries_on_connection_reset_error_text():
|
||||
handler = AsyncHTTPHandler()
|
||||
retry_client = MagicMock()
|
||||
retry_client.aclose = AsyncMock()
|
||||
retry_response = MagicMock(spec=httpx.Response)
|
||||
|
||||
handler.client.send = AsyncMock(
|
||||
side_effect=Exception("[Errno 104] Connection reset by peer")
|
||||
)
|
||||
handler.create_client = MagicMock(return_value=retry_client)
|
||||
handler.single_connection_post_request = AsyncMock(return_value=retry_response)
|
||||
|
||||
try:
|
||||
response = await handler.post(
|
||||
url="https://example.com",
|
||||
data={"hello": "world"},
|
||||
headers={"x-test": "1"},
|
||||
content="payload",
|
||||
)
|
||||
|
||||
assert response is retry_response
|
||||
handler.create_client.assert_called_once_with(
|
||||
timeout=handler.timeout, event_hooks=handler.event_hooks
|
||||
)
|
||||
handler.single_connection_post_request.assert_awaited_once_with(
|
||||
url="https://example.com",
|
||||
client=retry_client,
|
||||
data={"hello": "world"},
|
||||
json=None,
|
||||
params=None,
|
||||
headers={"x-test": "1"},
|
||||
stream=False,
|
||||
content="payload",
|
||||
)
|
||||
retry_client.aclose.assert_awaited_once()
|
||||
finally:
|
||||
await handler.close()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue