Skip to content

Commit ddb7b78

Browse files
authored
Deflake the issue-1363 tests: wait for lifespan startup instead of sleeping (#2879)
1 parent 255650d commit ddb7b78

1 file changed

Lines changed: 15 additions & 6 deletions

File tree

tests/issues/test_1363_race_condition_streamable_http.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from contextlib import asynccontextmanager
2222

2323
import anyio
24+
import anyio.to_thread
2425
import httpx
2526
import pytest
2627
from starlette.applications import Starlette
@@ -68,6 +69,7 @@ def __init__(self, app: Starlette):
6869
super().__init__(daemon=True)
6970
self.app = app
7071
self._stop_event = threading.Event()
72+
self._ready_event = threading.Event()
7173

7274
def run(self) -> None:
7375
"""Run the lifespan in a new event loop."""
@@ -78,12 +80,19 @@ async def run_lifespan():
7880
lifespan_context = getattr(self.app.router, "lifespan_context", None)
7981
assert lifespan_context is not None # Tests always create apps with lifespan
8082
async with lifespan_context(self.app):
83+
# Only signal readiness once lifespan startup has completed, i.e. the
84+
# session manager's task group exists and requests can be handled.
85+
self._ready_event.set()
8186
# Wait until stop is requested
8287
while not self._stop_event.is_set():
8388
await anyio.sleep(0.1)
8489

8590
anyio.run(run_lifespan)
8691

92+
def wait_ready(self, timeout: float = 5.0) -> None:
93+
"""Block until the lifespan has started; call from a worker thread, not the event loop."""
94+
assert self._ready_event.wait(timeout), "server thread did not start its lifespan in time"
95+
8796
def stop(self) -> None:
8897
"""Signal the thread to stop."""
8998
self._stop_event.set()
@@ -132,8 +141,8 @@ async def test_race_condition_invalid_accept_headers(caplog: pytest.LogCaptureFi
132141
server_thread.start()
133142

134143
try:
135-
# Give the server thread a moment to start
136-
await anyio.sleep(0.1)
144+
# Wait for the server thread to enter the lifespan before sending requests
145+
await anyio.to_thread.run_sync(server_thread.wait_ready)
137146

138147
# Suppress WARNING logs (expected validation errors) and capture ERROR logs
139148
with caplog.at_level(logging.ERROR):
@@ -203,8 +212,8 @@ async def test_race_condition_invalid_content_type(caplog: pytest.LogCaptureFixt
203212
server_thread.start()
204213

205214
try:
206-
# Give the server thread a moment to start
207-
await anyio.sleep(0.1)
215+
# Wait for the server thread to enter the lifespan before sending requests
216+
await anyio.to_thread.run_sync(server_thread.wait_ready)
208217

209218
# Suppress WARNING logs (expected validation errors) and capture ERROR logs
210219
with caplog.at_level(logging.ERROR):
@@ -243,8 +252,8 @@ async def test_race_condition_message_router_async_for(caplog: pytest.LogCapture
243252
server_thread.start()
244253

245254
try:
246-
# Give the server thread a moment to start
247-
await anyio.sleep(0.1)
255+
# Wait for the server thread to enter the lifespan before sending requests
256+
await anyio.to_thread.run_sync(server_thread.wait_ready)
248257

249258
# Suppress WARNING logs (expected validation errors) and capture ERROR logs
250259
with caplog.at_level(logging.ERROR):

0 commit comments

Comments
 (0)