Skip to content

Commit ad87cfa

Browse files
BabyChrist666claude
andcommitted
fix: add HTTP readiness check to wait_for_server and remove dead code (#1777)
The flaky SSE tests (test_sse_client_basic_connection_mounted_app, test_request_context_isolation) fail intermittently because wait_for_server() only checks TCP port connectivity. On slow CI machines, the port may accept connections before the ASGI app is fully initialized, causing SSE requests to fail. - Add a two-stage readiness check to wait_for_server(): first TCP connect, then an actual HTTP request to verify the app is handling requests (any HTTP response, even 404, confirms readiness) - Remove unreachable dead code after blocking server.run() calls in run_server() and run_mounted_server() - Remove unused `time` import from test_sse.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2fe56e5 commit ad87cfa

File tree

2 files changed

+31
-18
lines changed

2 files changed

+31
-18
lines changed

tests/shared/test_sse.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import json
22
import multiprocessing
33
import socket
4-
import time
54
from collections.abc import AsyncGenerator, Generator
65
from typing import Any
76
from unittest.mock import AsyncMock, MagicMock, Mock, patch
@@ -134,11 +133,6 @@ def run_server(server_port: int) -> None: # pragma: no cover
134133
print(f"starting server on {server_port}")
135134
server.run()
136135

137-
# Give server time to start
138-
while not server.started:
139-
print("waiting for server to start")
140-
time.sleep(0.5)
141-
142136

143137
@pytest.fixture()
144138
def server(server_port: int) -> Generator[None, None, None]:
@@ -313,11 +307,6 @@ def run_mounted_server(server_port: int) -> None: # pragma: no cover
313307
print(f"starting server on {server_port}")
314308
server.run()
315309

316-
# Give server time to start
317-
while not server.started:
318-
print("waiting for server to start")
319-
time.sleep(0.5)
320-
321310

322311
@pytest.fixture()
323312
def mounted_server(server_port: int) -> Generator[None, None, None]:

tests/test_helpers.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,54 @@
22

33
import socket
44
import time
5+
import urllib.error
6+
import urllib.request
57

68

79
def wait_for_server(port: int, timeout: float = 20.0) -> None:
810
"""Wait for server to be ready to accept connections.
911
10-
Polls the server port until it accepts connections or timeout is reached.
11-
This eliminates race conditions without arbitrary sleeps.
12+
First polls until the TCP port accepts connections, then verifies the
13+
HTTP server is actually ready to handle requests. This two-stage check
14+
prevents race conditions where the port is open but the ASGI app hasn't
15+
finished initializing.
1216
1317
Args:
1418
port: The port number to check
15-
timeout: Maximum time to wait in seconds (default 5.0)
19+
timeout: Maximum time to wait in seconds (default 20.0)
1620
1721
Raises:
1822
TimeoutError: If server doesn't start within the timeout period
1923
"""
2024
start_time = time.time()
25+
26+
# Stage 1: Wait for TCP port to accept connections
2127
while time.time() - start_time < timeout:
2228
try:
2329
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
2430
s.settimeout(0.1)
2531
s.connect(("127.0.0.1", port))
26-
# Server is ready
27-
return
32+
break # Port is open, move to stage 2
2833
except (ConnectionRefusedError, OSError):
29-
# Server not ready yet, retry quickly
3034
time.sleep(0.01)
31-
raise TimeoutError(f"Server on port {port} did not start within {timeout} seconds") # pragma: no cover
35+
else:
36+
raise TimeoutError(f"Server on port {port} did not start within {timeout} seconds") # pragma: no cover
37+
38+
# Stage 2: Verify HTTP server is ready by making a request.
39+
# A non-existent path returns 404/405 if the app is ready, or
40+
# raises an error if the ASGI app hasn't finished initializing.
41+
while time.time() - start_time < timeout:
42+
try:
43+
req = urllib.request.Request(
44+
f"http://127.0.0.1:{port}/healthz",
45+
method="GET",
46+
)
47+
with urllib.request.urlopen(req, timeout=1):
48+
return # Any successful response means server is ready
49+
except urllib.error.HTTPError:
50+
# 404/405/etc means the server IS handling requests
51+
return
52+
except (urllib.error.URLError, ConnectionError, OSError):
53+
# Server not ready for HTTP yet
54+
time.sleep(0.05)
55+
raise TimeoutError(f"Server on port {port} did not become HTTP-ready within {timeout} seconds") # pragma: no cover

0 commit comments

Comments
 (0)