From a766a58d14007f07c0b5782fa78cdc370b892796 Mon Sep 17 00:00:00 2001 From: Dan Lapid Date: Wed, 16 Apr 2025 13:57:27 +0100 Subject: [PATCH] Mark ExceptionMiddleware.http_exception as async to prevent thread creation. (#2922) --- starlette/middleware/exceptions.py | 2 +- tests/test_exceptions.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/starlette/middleware/exceptions.py b/starlette/middleware/exceptions.py index 981d2fca..a99b44de 100644 --- a/starlette/middleware/exceptions.py +++ b/starlette/middleware/exceptions.py @@ -61,7 +61,7 @@ class ExceptionMiddleware: await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send) - def http_exception(self, request: Request, exc: Exception) -> Response: + async def http_exception(self, request: Request, exc: Exception) -> Response: assert isinstance(exc, HTTPException) if exc.status_code in {204, 304}: return Response(status_code=exc.status_code, headers=exc.headers) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index fe5da0ba..86824565 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,6 +1,8 @@ +import typing from collections.abc import Generator import pytest +from pytest import MonkeyPatch from starlette.exceptions import HTTPException, WebSocketException from starlette.middleware.exceptions import ExceptionMiddleware @@ -184,3 +186,22 @@ def test_request_in_app_and_handler_is_the_same_object(client: TestClient) -> No response = client.post("/consume_body_in_endpoint_and_handler", content=b"Hello!") assert response.status_code == 422 assert response.json() == {"body": "Hello!"} + + +def test_http_exception_does_not_use_threadpool(client: TestClient, monkeypatch: MonkeyPatch) -> None: + """ + Verify that handling HTTPException does not invoke run_in_threadpool, + confirming the handler correctly runs in the main async context. + """ + from starlette import _exception_handler + + # Replace run_in_threadpool with a function that raises an error + def mock_run_in_threadpool(*args: typing.Any, **kwargs: typing.Any) -> None: + pytest.fail("run_in_threadpool should not be called for HTTP exceptions") # pragma: no cover + + # Apply the monkeypatch only during this test + monkeypatch.setattr(_exception_handler, "run_in_threadpool", mock_run_in_threadpool) + + # This should succeed because http_exception is async and won't use run_in_threadpool + response = client.get("/not_acceptable") + assert response.status_code == 406 -- 2.47.2