-
-
Notifications
You must be signed in to change notification settings - Fork 949
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
Move BackgroundTask execution outside of middleware stack #1700
Conversation
It's better to add some background task tests |
There is already a test which does not pass on |
It didn't actually "pass" the test on master - the background tasks are cancelled during the The expected output is:
|
When I saw this PR about a week ago, I thought that this was a pretty good solution to #1438. However, since then I have been able to think about that issue more deeply (after being prompted by another of your PRs, #1697), and I believe that #1715 also solves #1438, but keeps background tasks as being run in the request/response cycle. I do not mean to say that "putting background tasks in a separate, app-initialized task group" is not a worthwhile change - I only mean to say that it may not be necessary to fix #1438. |
@pytest.fixture( | ||
params=[[], [BackgroundTaskMiddleware]], | ||
ids=["without BackgroundTaskMiddleware", "with BackgroundTaskMiddleware"], | ||
) | ||
def test_client_factory_mw( | ||
test_client_factory: TestClientFactory, request: Any | ||
) -> TestClientFactory: | ||
mw_stack: List[Callable[[ASGIApp], ASGIApp]] = request.param | ||
|
||
def client_factory(app: ASGIApp) -> TestClient: | ||
for mw in mw_stack: | ||
app = mw(app) | ||
return test_client_factory(app) | ||
|
||
return client_factory | ||
|
||
|
||
def response_app_factory(task: BackgroundTask) -> ASGIApp: | ||
async def app(scope: Scope, receive: Receive, send: Send): | ||
response = Response(b"task initiated", media_type="text/plain", background=task) | ||
await response(scope, receive, send) | ||
|
||
return app | ||
|
||
|
||
def file_response_app_factory(task: BackgroundTask) -> ASGIApp: | ||
async def app(scope: Scope, receive: Receive, send: Send): | ||
with NamedTemporaryFile("wb+") as f: | ||
f.write(b"task initiated") | ||
f.seek(0) | ||
response = FileResponse(f.name, media_type="text/plain", background=task) | ||
await response(scope, receive, send) | ||
|
||
return app | ||
|
||
|
||
def streaming_response_app_factory(task: BackgroundTask) -> ASGIApp: | ||
async def app(scope: Scope, receive: Receive, send: Send): | ||
async def stream() -> AsyncIterable[bytes]: | ||
yield b"task initiated" | ||
|
||
response = StreamingResponse(stream(), media_type="text/plain", background=task) | ||
await response(scope, receive, send) | ||
|
||
return app |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is all just to get 100% code coverage with both code paths. If we removed the old code path (i.e. backgroundTask does not work without the middleware) there would be (almost) no changes to these tests and it would look a lot like https://github.com/xpresso-devs/asgi-background
Glad one of my messy PRs actually inspired something instead of just being noise 😅
There is not TaksGroup in this PR. That's the name of the branch and that being the original idea, but I realized it was too ambitious so I toned it down (I still think a
👍 gotcha I do think there's a lot to be said for not changing other parts of the codebase in an effort to fix bugs caused by BaseHTTPMiddleware. That said, I think this is a pretty natural change. I think that if I looked at this in 5 years without any comments it'd still make sense. Doesn't make it a better option per-se, but if this wasn't the case I think it'd be a bad option for sure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Co-authored-by: Jean Hominal <jhominal@gmail.com>
Co-authored-by: Jean Hominal <jhominal@gmail.com>
I ended up just removing the test for #919. I think if we end up merging one of these solutions we should probably comment on that issue saying "hey we think this is fixed and also this issue has become an agglomeration of possibly multiple bugs that have changed over time so we are closing it for now. If you are still suffering from this issue please open a new issue with a self contained example" |
For reference I do believe this is the pattern employed by sanic: Task group outside of the middleware stack. https://sanic.dev/en/guide/basics/tasks.html Seems sound. |
Also used by Quart: Quart uses a TaskGroup/Nuersery created in the lifespan event. That latter implementation was the original goal of this PR, I've thought that was the right move since before knowing Quart does that, which I think indicates it is indeed the right move long term. But for today this will have to do. |
Fixes #1438, fixes #919, closes #1654, closes #1699
This will currently break backwards compatibility if someone tries to use the "new" Response implementation with an "old" Starlette app (or an app that is not Starlette, like FastAPI which copies a lot of Starlette code and will have to manually update 1 LOC). We could easily work around this with an
if "starlette.background" in scope
but it will require a couple more tests. Otherwise this should be backwards compatible.