Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise exception from background task on BaseHTTPMiddleware introduced a bug? #2893

Open
ramannanda9 opened this issue Feb 28, 2025 · 10 comments

Comments

@ramannanda9
Copy link

ramannanda9 commented Feb 28, 2025

Looking at this commit, it pushed the logic out to the end, but if there is an error raised and handled, it will reraise it again

Steps to reproduce.

   @app.get("/error")
    async def foo():
        exc = Exception("foobar")
        exc.status_code = 501
        raise exc

Middleware handler.

@app.middleware("http")
    async def http_middleware(request: Request, call_next):
          try:
                response = await call_next(request)
          except Exception as exc:
                logger.exception("Failure in user code!")
                # stuff in between
                response = JSONResponse(
                {"error": str(exc), "stacktrace": traceback.format_exc()},
                status_code=status_code,
            )
          return response      

trace

During handling of the above exception, another exception occurred:

request = <starlette.middleware.base._CachedRequest object at 0x13a837040>

    async def call_next(request: Request) -> Response:
        async def receive_or_disconnect() -> Message:
            if response_sent.is_set():
                return {"type": "http.disconnect"}
    
            async with anyio.create_task_group() as task_group:
    
                async def wrap(func: typing.Callable[[], typing.Awaitable[T]]) -> T:
                    result = await func()
                    task_group.cancel_scope.cancel()
                    return result
    
                task_group.start_soon(wrap, response_sent.wait)
                message = await wrap(wrapped_receive)
    
            if response_sent.is_set():
                return {"type": "http.disconnect"}
    
            return message
    
        async def send_no_error(message: Message) -> None:
            try:
                await send_stream.send(message)
            except anyio.BrokenResourceError:
                # recv_stream has been closed, i.e. response_sent has been set.
                return
    
        async def coro() -> None:
            nonlocal app_exc
    
            with send_stream:
                try:
                    await self.app(scope, receive_or_disconnect, send_no_error)
                except Exception as exc:
                    app_exc = exc
    
        task_group.start_soon(coro)
    
        try:
>           message = await recv_stream.receive()


_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = MemoryObjectReceiveStream(_state=MemoryObjectStreamState(max_buffer_size=0, buffer=deque([]), open_send_channels=0, open_receive_channels=0, waiting_receivers=OrderedDict(), waiting_senders=OrderedDict()), _closed=True)

    async def receive(self) -> T_co:
        await checkpoint()
        try:
            return self.receive_nowait()
        except WouldBlock:
            # Add ourselves in the queue
            receive_event = Event()
            receiver = MemoryObjectItemReceiver[T_co]()
            self._state.waiting_receivers[receive_event] = receiver
    
            try:
                await receive_event.wait()
            finally:
                self._state.waiting_receivers.pop(receive_event, None)
    
            try:
                return receiver.item
            except AttributeError:
>               raise EndOfStream
E               anyio.EndOfStream
@febus982
Copy link

febus982 commented Mar 1, 2025

I was literally debugging the same!

@sfriedowitz
Copy link

Yep same issue, I've been debugging this same thing. My LoggingMiddleware that try/catches exceptions from call_next was no longer working as expected, and turns out it stems from this.

Will have to pin to a previous version of starlette until this is resolved.

@mrfroggg
Copy link

mrfroggg commented Mar 3, 2025

Same kind of issue with 0.46.0 on my end. I have a Timeout Middleware setup in my FastAPI app when I raise an asyncio..exceptions.IncompleteReadError to timeout the client with a HTTP 408. Now The code can't catch the exception anymore.
Fixing starlette to 0.45.3 fixes the issue for now

@Sanchoyzer
Copy link

Same problem with raising exception

main.py

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.routing import Route


class NotFoundError(Exception):
    pass


async def handle_error(request):
    raise NotFoundError()


class HandleValueErrorMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        try:
            response = await call_next(request)
        except NotFoundError:
            return JSONResponse({"detail": "Not Found"}, status_code=404)
        return response


app = Starlette(
    middleware=[Middleware(HandleValueErrorMiddleware)],
    routes=[Route("/error", handle_error)],
)

test_1.py

from starlette.testclient import TestClient

from main import app


async def test_404():
    result = TestClient(app=app).get("/error")
    assert result.status_code == 404

This test works on 0.45.3, but fails on 0.46.0

@ahryniv
Copy link
Contributor

ahryniv commented Mar 5, 2025

Same issue on my end, temporary fix with pinning to 0.45.3 helped

@aurelwildfellner
Copy link

aurelwildfellner commented Mar 5, 2025

One issue which is introduced by applying collapse_excgroups is that the Exceptions then self-reference via Exception.__context__. exception_a.__context__ -> ExceptionGroup -> exception_a to be precise. If logging frameworks walk this, they end up in an infinite loop.

vprivat-ads added a commit to RS-PYTHON/rs-server that referenced this issue Mar 10, 2025
vprivat-ads added a commit to RS-PYTHON/rs-server that referenced this issue Mar 10, 2025
* build(deps): bump fastapi from 0.115.9 to 0.115.11 in /services/frontend

Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.9 to 0.115.11.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](fastapi/fastapi@0.115.9...0.115.11)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5 in /services/frontend

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5 in /services/cadip

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5 in /services/catalog

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump moto from 5.1.0 to 5.1.1 in /services/catalog

Bumps [moto](https://github.com/getmoto/moto) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](getmoto/moto@5.1.0...5.1.1)

---
updated-dependencies:
- dependency-name: moto
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump sphinx from 8.2.1 to 8.2.3

Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 8.2.1 to 8.2.3.
- [Release notes](https://github.com/sphinx-doc/sphinx/releases)
- [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES.rst)
- [Commits](sphinx-doc/sphinx@v8.2.1...v8.2.3)

---
updated-dependencies:
- dependency-name: sphinx
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump moto from 5.1.0 to 5.1.1

Bumps [moto](https://github.com/getmoto/moto) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/getmoto/moto/releases)
- [Changelog](https://github.com/getmoto/moto/blob/master/CHANGELOG.md)
- [Commits](getmoto/moto@5.1.0...5.1.1)

---
updated-dependencies:
- dependency-name: moto
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5 in /services/adgs

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump fastapi from 0.115.9 to 0.115.11 in /services/common

Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.9 to 0.115.11.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](fastapi/fastapi@0.115.9...0.115.11)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5 in /services/common

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump authlib from 1.5.0 to 1.5.1 in /services/common

Bumps [authlib](https://github.com/lepture/authlib) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/lepture/authlib/releases)
- [Changelog](https://github.com/lepture/authlib/blob/main/docs/changelog.rst)
- [Commits](lepture/authlib@v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: authlib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump fastapi from 0.115.9 to 0.115.11 in /services/staging

Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.9 to 0.115.11.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](fastapi/fastapi@0.115.9...0.115.11)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump authlib from 1.5.0 to 1.5.1 in /services/staging

Bumps [authlib](https://github.com/lepture/authlib) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/lepture/authlib/releases)
- [Changelog](https://github.com/lepture/authlib/blob/main/docs/changelog.rst)
- [Commits](lepture/authlib@v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: authlib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump pytest from 8.3.4 to 8.3.5 in /services/staging

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](pytest-dev/pytest@8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump types-requests

Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20241016 to 2.32.0.20250306.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump botocore from 1.37.3 to 1.37.9 in /services/common

Bumps [botocore](https://github.com/boto/botocore) from 1.37.3 to 1.37.9.
- [Commits](boto/botocore@1.37.3...1.37.9)

---
updated-dependencies:
- dependency-name: botocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump botocore from 1.37.3 to 1.37.9 in /services/staging

Bumps [botocore](https://github.com/boto/botocore) from 1.37.3 to 1.37.9.
- [Commits](boto/botocore@1.37.3...1.37.9)

---
updated-dependencies:
- dependency-name: botocore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* merge: poetry.lock files from origin/dependabot/pip/moto-5.1.1

* merge: poetry.lock files from origin/dependabot/pip/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/services/adgs/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/services/cadip/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/services/catalog/moto-5.1.1

* merge: poetry.lock files from origin/dependabot/pip/services/catalog/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/services/common/authlib-1.5.1

* merge: poetry.lock files from origin/dependabot/pip/services/common/botocore-1.37.9

* merge: poetry.lock files from origin/dependabot/pip/services/common/fastapi-0.115.11

* merge: poetry.lock files from origin/dependabot/pip/services/common/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/services/frontend/fastapi-0.115.11

* merge: poetry.lock files from origin/dependabot/pip/services/frontend/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/services/staging/authlib-1.5.1

* merge: poetry.lock files from origin/dependabot/pip/services/staging/botocore-1.37.9

* merge: poetry.lock files from origin/dependabot/pip/services/staging/fastapi-0.115.11

* merge: poetry.lock files from origin/dependabot/pip/services/staging/pytest-8.3.5

* merge: poetry.lock files from origin/dependabot/pip/sphinx-8.2.3

* merge: poetry.lock files from origin/dependabot/pip/types-requests-2.32.0.20250306

* merge: rebuild the poetry.lock files

* Manual stac-fastapi updates

* Update pytest against latest stac-fastapi behaviour

* Revert to Starlette 0.45.3 because of encode/starlette#2893

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@condar-mpb
Copy link

Adding another voice to the chorus, we've had to pin to starlette==0.45.3 because it has broken our general purpose exception handler middleware - it'd be good to get a fix so we can get back onto the most up to date version.

ramannanda9 added a commit to ramannanda9/starlette that referenced this issue Mar 19, 2025
ramannanda9 added a commit to ramannanda9/starlette that referenced this issue Mar 19, 2025
ramannanda9 added a commit to ramannanda9/starlette that referenced this issue Mar 19, 2025
Fix for encode#2893
In current state exception is reraised and this adds a flag, which prevent the background exception handling code to rethrow a raised exception
ramannanda9 added a commit to ramannanda9/starlette that referenced this issue Mar 19, 2025
Fix for encode#2893
In current state exception is reraised and this adds a flag, which prevent the background exception handling code to rethrow a raised exception
ramannanda9 added a commit to ramannanda9/starlette that referenced this issue Mar 19, 2025
Fix for encode#2893
In current state exception is reraised and this adds a flag, which prevent the background exception handling code to rethrow a raised exception
@Sanchoyzer
Copy link

@Kludex , have you had a chance to look at this bug?

@iwanbolzern
Copy link

Same here. We also have a general purpose error middleware, which is no longer working.

@shaug
Copy link

shaug commented Apr 5, 2025

Same. We have now pinned 0.45.3 because the changes in 0.46 break our middleware exception handling in fastapi.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants