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

"Task was destroyed but it is pending!" error when middleware dispatch func discards response from call_next #1022

Closed
itayperl opened this issue Aug 8, 2020 · 5 comments
Labels
bug Something isn't working

Comments

@itayperl
Copy link

itayperl commented Aug 8, 2020

Here's a small test case based on FastAPI

from fastapi import FastAPI, Request, Response

app = FastAPI()

@app.middleware('http')
async def foo(request: Request, call_next):
    resp = await call_next(request)
    if resp.status_code == 404:
        return Response('Oh no!')
    return resp

@app.get('/meow')
def meow():
    return 'Meow!'

Calling a nonexistent endpoint on this server will result in this error log at some point:

ERROR:    Task was destroyed but it is pending!
task: <Task pending name='Task-4' coro=<BaseHTTPMiddleware.call_next.<locals>.coro() done, defined at /usr/lib/python3.8/site-packages/starlette/middleware/base.py:36> wait_for=<Future cancelled>>

The problem is that the response returned from call_next is not used, and the coroutine started in BaseHTTPMiddleware.call_next keeps blocking on queue.put until the Task object is GC'd.

This seems to be a new issue in 0.13.7 as in previous versions the queue was not limited and didn't block.

@erewok erewok added the bug Something isn't working label Aug 9, 2020
@erewok
Copy link
Contributor

erewok commented Aug 9, 2020

Thanks for reporting this issue and thanks for putting together a straightforward example. If you had middleware that completely discarded the request on every call, then it looks like it could accumulate pending tasks in a more predictable manner. A simpler example of that would look like this:

@app.middleware('http')
async def foo(request: Request, call_next):
    resp = await call_next(request)
    return Response('Oh no!')

The change to limit the queue size was to benefit those using this middleware class with their own StreamingResponses but in this case, the code is assuming that the response will be fully evaluated. We'll have to figure out how to accommodate both cases.

@erewok
Copy link
Contributor

erewok commented Aug 11, 2020

@itayperl, After discussing this issue with encode maintainers, we've decided we should revert this change and issue a new release, under the suspicion that your issue is likely to be more common and potentially more problematic than the issue we fixed for users who are returning StreamingResponses from their handlers.

On the whole, this particular middleware class has some issues that need to be worked through, and it will need to be reevaluated in the future. I suggest creating Middleware similar to other examples in the Starlette repo and in this particular case (if you are using middleware for particular status codes as in your example), to switch to using something like the exception handlers example from the docs.

@itayperl
Copy link
Author

itayperl commented Aug 11, 2020

@erewok, thanks for looking into this.

I didn't mention this in my report, but I actually upgraded to 0.13.7 because I needed the backpressure fix. I have an event-stream endpoint that streams data for as long as the connection is open. In some cases a client would keep a connection open but stop reading from it. Whenever that happened, the server would gradually leak all of the system's memory, until it got killed by the kernel.

In the meantime I've also changed my middleware to the following, which works for me on 0.13.7:

@app.middleware('http')
async def foo(request: Request, call_next):
    for route in app.router.routes:
        match, _ = route.matches(request.scope)
        if match != starlette.routing.Match.NONE:
            return await call_next(request)

    return Response('...')

@erewok
Copy link
Contributor

erewok commented Aug 11, 2020

I have opened a separate PR on reverting the maxsize fix here for discussion: #1028

It's good to know that the fix in version 0.13.7 resolved your problem.

@erewok
Copy link
Contributor

erewok commented Aug 14, 2020

We have reverted the fix from 0.13.7 that lead to this problem. I am going to close this issue as a result, because the behavior from 0.13.6 should be restored. Thanks again for reporting the issue.

@erewok erewok closed this as completed Aug 14, 2020
uSpike added a commit to uSpike/starlette that referenced this issue Jun 13, 2021
@uSpike uSpike mentioned this issue Jun 13, 2021
3 tasks
JayH5 pushed a commit that referenced this issue Jun 18, 2021
* First whack at anyio integration

* Fix formatting

* Remove debug messages

* mypy fixes

* Update README.md

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>

* Fix install_requires typo

* move_on_after blocks if deadline is too small

* Linter fixes

* Improve WSGI structured concurrency

* Tests use anyio

* Checkin progress on testclient

* Prep for anyio 3

* Remove debug backend option

* Use anyio 3.0.0rc1

* Remove old style executor from GraphQLApp

* Fix extra import

* Don't cancel task scope early

* Wait for wsgi sender to finish before exiting

* Use memory object streams in websocket tests

* Test on asyncio, asyncio+uvloop, and trio

* Formatting fixes

* run_until_first_complete doesn't need a return

* Fix middleware app call

* Simplify middleware exceptions

* Use anyio for websocket test

* Set STARLETTE_TESTCLIENT_ASYNC_BACKEND in tests

* Pass async backend to portal

* Formatting fixes

* Bump anyio

* Cleanup portals and add TestClient.async_backend

* Use anyio.run_async_from_thread to send from worker thread

* Use websocket_connect as context manager

* Document changes in TestClient

* Formatting fix

* Fix websocket raises coverage

* Update to anyio 3.0.0rc3 and replace aiofiles

* Apply suggestions from code review

Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>

* Bump to require anyio 3.0.0 final

* Remove mention of aiofiles in README.md

* Pin jinja2 to releases before 3 due to DeprecationWarnings

* Add task_group as application attribute

* Remove run_until_first_complete

* Undo jinja pin

* Refactor anyio.sleep into an event

* Use one less task in test_websocket_concurrency_pattern

* Apply review suggestions

* Rename argument

* fix start_task_soon type

* fix BaseHTTPMiddleware when used without Starlette

* Testclient receive() is a non-trapping function if the response is already complete

This allows for a zero deadline when waiting for a disconnect message

* Use variable annotation for async_backend

* Update docs regarding dependency on anyio

* Use CancelScope instead of move_on_after in request.is_disconnected

* Cancel task group after returning middleware response

Add test for #1022

* Add link to anyio backend options in testclient docs

* Add types-dataclasses

* Re-implement starlette.concurrency.run_until_first_complete and add a test

* Fix type on handler callable

* Apply review comments to clarify run_until_first_complete scope

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
hidraco added a commit to hidraco/starlette that referenced this issue Apr 21, 2022
* First whack at anyio integration

* Fix formatting

* Remove debug messages

* mypy fixes

* Update README.md

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>

* Fix install_requires typo

* move_on_after blocks if deadline is too small

* Linter fixes

* Improve WSGI structured concurrency

* Tests use anyio

* Checkin progress on testclient

* Prep for anyio 3

* Remove debug backend option

* Use anyio 3.0.0rc1

* Remove old style executor from GraphQLApp

* Fix extra import

* Don't cancel task scope early

* Wait for wsgi sender to finish before exiting

* Use memory object streams in websocket tests

* Test on asyncio, asyncio+uvloop, and trio

* Formatting fixes

* run_until_first_complete doesn't need a return

* Fix middleware app call

* Simplify middleware exceptions

* Use anyio for websocket test

* Set STARLETTE_TESTCLIENT_ASYNC_BACKEND in tests

* Pass async backend to portal

* Formatting fixes

* Bump anyio

* Cleanup portals and add TestClient.async_backend

* Use anyio.run_async_from_thread to send from worker thread

* Use websocket_connect as context manager

* Document changes in TestClient

* Formatting fix

* Fix websocket raises coverage

* Update to anyio 3.0.0rc3 and replace aiofiles

* Apply suggestions from code review

Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>

* Bump to require anyio 3.0.0 final

* Remove mention of aiofiles in README.md

* Pin jinja2 to releases before 3 due to DeprecationWarnings

* Add task_group as application attribute

* Remove run_until_first_complete

* Undo jinja pin

* Refactor anyio.sleep into an event

* Use one less task in test_websocket_concurrency_pattern

* Apply review suggestions

* Rename argument

* fix start_task_soon type

* fix BaseHTTPMiddleware when used without Starlette

* Testclient receive() is a non-trapping function if the response is already complete

This allows for a zero deadline when waiting for a disconnect message

* Use variable annotation for async_backend

* Update docs regarding dependency on anyio

* Use CancelScope instead of move_on_after in request.is_disconnected

* Cancel task group after returning middleware response

Add test for encode/starlette#1022

* Add link to anyio backend options in testclient docs

* Add types-dataclasses

* Re-implement starlette.concurrency.run_until_first_complete and add a test

* Fix type on handler callable

* Apply review comments to clarify run_until_first_complete scope

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
dudleyhunt86 added a commit to dudleyhunt86/starlette-develop-with-python that referenced this issue Oct 7, 2022
* First whack at anyio integration

* Fix formatting

* Remove debug messages

* mypy fixes

* Update README.md

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>

* Fix install_requires typo

* move_on_after blocks if deadline is too small

* Linter fixes

* Improve WSGI structured concurrency

* Tests use anyio

* Checkin progress on testclient

* Prep for anyio 3

* Remove debug backend option

* Use anyio 3.0.0rc1

* Remove old style executor from GraphQLApp

* Fix extra import

* Don't cancel task scope early

* Wait for wsgi sender to finish before exiting

* Use memory object streams in websocket tests

* Test on asyncio, asyncio+uvloop, and trio

* Formatting fixes

* run_until_first_complete doesn't need a return

* Fix middleware app call

* Simplify middleware exceptions

* Use anyio for websocket test

* Set STARLETTE_TESTCLIENT_ASYNC_BACKEND in tests

* Pass async backend to portal

* Formatting fixes

* Bump anyio

* Cleanup portals and add TestClient.async_backend

* Use anyio.run_async_from_thread to send from worker thread

* Use websocket_connect as context manager

* Document changes in TestClient

* Formatting fix

* Fix websocket raises coverage

* Update to anyio 3.0.0rc3 and replace aiofiles

* Apply suggestions from code review

Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>

* Bump to require anyio 3.0.0 final

* Remove mention of aiofiles in README.md

* Pin jinja2 to releases before 3 due to DeprecationWarnings

* Add task_group as application attribute

* Remove run_until_first_complete

* Undo jinja pin

* Refactor anyio.sleep into an event

* Use one less task in test_websocket_concurrency_pattern

* Apply review suggestions

* Rename argument

* fix start_task_soon type

* fix BaseHTTPMiddleware when used without Starlette

* Testclient receive() is a non-trapping function if the response is already complete

This allows for a zero deadline when waiting for a disconnect message

* Use variable annotation for async_backend

* Update docs regarding dependency on anyio

* Use CancelScope instead of move_on_after in request.is_disconnected

* Cancel task group after returning middleware response

Add test for encode/starlette#1022

* Add link to anyio backend options in testclient docs

* Add types-dataclasses

* Re-implement starlette.concurrency.run_until_first_complete and add a test

* Fix type on handler callable

* Apply review comments to clarify run_until_first_complete scope

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants