Skip to content

Commit

Permalink
Stop body_stream in case more_body=False (#2194)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kludex committed Jul 13, 2023
1 parent d6007d7 commit 11a3f12
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 0 deletions.
2 changes: 2 additions & 0 deletions starlette/middleware/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ async def body_stream() -> typing.AsyncGenerator[bytes, None]:
body = message.get("body", b"")
if body:
yield body
if not message.get("more_body", False):
break

if app_exc is not None:
raise app_exc
Expand Down
65 changes: 65 additions & 0 deletions tests/middleware/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,71 @@ async def send(message):
assert background_task_run.is_set()


@pytest.mark.anyio
async def test_do_not_block_on_background_tasks():
request_body_sent = False
response_complete = anyio.Event()
events: List[Union[str, Message]] = []

async def sleep_and_set():
events.append("Background task started")
await anyio.sleep(0.1)
events.append("Background task finished")

async def endpoint_with_background_task(_):
return PlainTextResponse(
content="Hello", background=BackgroundTask(sleep_and_set)
)

async def passthrough(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
return await call_next(request)

app = Starlette(
middleware=[Middleware(BaseHTTPMiddleware, dispatch=passthrough)],
routes=[Route("/", endpoint_with_background_task)],
)

scope = {
"type": "http",
"version": "3",
"method": "GET",
"path": "/",
}

async def receive() -> Message:
nonlocal request_body_sent
if not request_body_sent:
request_body_sent = True
return {"type": "http.request", "body": b"", "more_body": False}
await response_complete.wait()
return {"type": "http.disconnect"}

async def send(message: Message):
if message["type"] == "http.response.body":
events.append(message)
if not message.get("more_body", False):
response_complete.set()

async with anyio.create_task_group() as tg:
tg.start_soon(app, scope, receive, send)
tg.start_soon(app, scope, receive, send)

# Without the fix, the background tasks would start and finish before the
# last http.response.body is sent.
assert events == [
{"body": b"Hello", "more_body": True, "type": "http.response.body"},
{"body": b"", "more_body": False, "type": "http.response.body"},
{"body": b"Hello", "more_body": True, "type": "http.response.body"},
{"body": b"", "more_body": False, "type": "http.response.body"},
"Background task started",
"Background task started",
"Background task finished",
"Background task finished",
]


@pytest.mark.anyio
async def test_run_context_manager_exit_even_if_client_disconnects():
# test for https://github.com/encode/starlette/issues/1678#issuecomment-1172916042
Expand Down

0 comments on commit 11a3f12

Please sign in to comment.