Problem
The stalling_gateway fixture in tests/test_stream_idle_timeout.py can hang indefinitely during teardown if its HTTP server accepts a connection that does not complete a request. This blocks the remaining test suite rather than reporting a bounded test failure.
Confirmed on current main 06015506625a78b981ae0b34be40239fc1b389be, Python 3.12.14. A full-suite run stalled with the fixture finalizer waiting in server.shutdown() while its server thread waited in socket.readinto() → BaseHTTPRequestHandler.handle_one_request().
The fixture uses a single-threaded HTTPServer. Its handler inherits timeout = None; setting _StallingHandler.stop only releases the deliberate wait inside do_POST, so it cannot interrupt a handler waiting for a request line. HTTPServer.timeout would not solve this: the required bound is on the accepted socket's reads.
Local reproduction
Use the project's frozen Python environment. This only connects to the fixture's loopback server, requires no model credentials, and closes the controlled socket after observing the blocked finalizer so the reproduction itself exits.
import importlib.util
import socket
import threading
from urllib.parse import urlparse
spec = importlib.util.spec_from_file_location(
"idle_tests", "tests/test_stream_idle_timeout.py"
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
accepted = threading.Event()
original_setup = module._StallingHandler.setup
def observed_setup(self):
original_setup(self)
accepted.set()
module._StallingHandler.setup = observed_setup
fixture = module.stalling_gateway.__wrapped__()
address = urlparse(next(fixture))
client = socket.create_connection((address.hostname, address.port), timeout=2)
finished = threading.Event()
def teardown():
try:
next(fixture, None)
finally:
finished.set()
try:
assert accepted.wait(2)
thread = threading.Thread(target=teardown, daemon=True)
thread.start()
print("finished with idle socket open:", finished.wait(1))
finally:
client.close()
print("finished after socket close:", finished.wait(2))Output on unchanged main:
finished with idle socket open: False
finished after socket close: TrueAn in-memory control setting _StallingHandler.timeout = 0.25 before starting the fixture changes the first result to True while the client socket is still open. The repository source was unchanged for this comparison.
Proposed scope
Give the fixture's accepted sockets a finite read timeout and add a regression that proves teardown completes while an accepted idle connection remains open. Keep the deliberate SSE stall and production stream-watchdog behavior unchanged. The standard handler already catches TimeoutError and closes the idle connection.
This establishes the fixture's unbounded teardown defect. It does not establish exactly which client lifecycle path left the connection open in the original full-suite run, or claim an observed failure in remote GitHub CI.
The fixture originated in #978 and was extended in #1487. The latter's intentional handling of already-buffered stream events is unrelated and does not need to change.
Activity
0No activity yet.