From fa77bcccf725e01a25efd3880a09b56c87ec164b Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Tue, 27 Jun 2023 14:29:52 +0200 Subject: [PATCH 1/4] Stop `body_stream` in case `more_body=False` --- starlette/middleware/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/starlette/middleware/base.py b/starlette/middleware/base.py index 2ff0e047b..170a805a7 100644 --- a/starlette/middleware/base.py +++ b/starlette/middleware/base.py @@ -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 From 5bf2b1247c41c2558cb190902c7eba80fdb220c1 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 12 Jul 2023 21:38:47 +0200 Subject: [PATCH 2/4] Add tests --- tests/middleware/test_base.py | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/middleware/test_base.py b/tests/middleware/test_base.py index f7dcf521c..7d1214c32 100644 --- a/tests/middleware/test_base.py +++ b/tests/middleware/test_base.py @@ -265,6 +265,72 @@ 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[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 From b56c2111e42fd71409b0b040f9b38372e7a04bd1 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 12 Jul 2023 21:39:05 +0200 Subject: [PATCH 3/4] Fix linter --- tests/middleware/test_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/middleware/test_base.py b/tests/middleware/test_base.py index 7d1214c32..501426e1b 100644 --- a/tests/middleware/test_base.py +++ b/tests/middleware/test_base.py @@ -276,7 +276,6 @@ async def sleep_and_set(): 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) From 82ddb561d0adbf7f546b7177dd4adbf346a4e149 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Wed, 12 Jul 2023 22:57:43 +0200 Subject: [PATCH 4/4] fix python versions linter --- tests/middleware/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/middleware/test_base.py b/tests/middleware/test_base.py index 501426e1b..cf4780cce 100644 --- a/tests/middleware/test_base.py +++ b/tests/middleware/test_base.py @@ -269,7 +269,7 @@ async def send(message): async def test_do_not_block_on_background_tasks(): request_body_sent = False response_complete = anyio.Event() - events: List[str | Message] = [] + events: List[Union[str, Message]] = [] async def sleep_and_set(): events.append("Background task started")