litellm/tests/test_litellm/proxy/client/conftest.py
ryan-crabbe-berri 3ebf09464a fix(proxy): give every requests call a timeout so a silent server cannot hang the caller
`requests` has no default timeout, so a host that accepts the connection and
never answers blocks the calling thread forever.

The one on the request path is the HiddenLayer guardrail's `_get_jwt`. It runs
synchronously inside `_call_hiddenlayer` whenever the hour-long JWT expires and
the API answers 401, so a stalled auth host parked the worker's whole event
loop, not just the guarded request. The other eight are the teams and users CLI
clients, which pin the operator's terminal instead.

`TeamsManagementClient` and `UsersManagementClient` now take the same
`timeout: int = 30` their `HTTPClient` sibling already had, and `Client` threads
its own timeout down to teams. `_poll_for_ready_data` already passed a timeout
through a TypedDict that ruff could not see into; passing the argument directly
retires both the TypedDict and the suppression it would have needed.

Graduate S113 into ruff.toml so the next `requests` call without a timeout fails
the lint step.
2026-08-25 10:12:33 -07:00

38 lines
1,020 B
Python

import threading
import pytest
@pytest.fixture
def hanging_server():
"""A server that accepts the connection and never answers, so only a timeout ends the call."""
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
stop: threading.Event = threading.Event()
class SilentRequestHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def _hang(self):
stop.wait(timeout=30)
do_GET = _hang
do_POST = _hang
def log_message(self, format, *args):
pass
class ThreadedServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
server = ThreadedServer(("127.0.0.1", 0), SilentRequestHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
yield f"http://127.0.0.1:{server.server_port}"
finally:
stop.set()
server.shutdown()
server.server_close()
thread.join(timeout=5)