-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Move BackgroundTask execution outside of request/response cycle #2176
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from typing import List, cast | ||
|
||
from starlette.background import BackgroundTask | ||
from starlette.types import ASGIApp, Receive, Scope, Send | ||
|
||
# consider this a private implementation detail subject to change | ||
# do not rely on this key | ||
_SCOPE_KEY = "starlette._background" | ||
|
||
|
||
_BackgroundTaskList = List[BackgroundTask] | ||
|
||
|
||
def is_background_task_middleware_installed(scope: Scope) -> bool: | ||
return _SCOPE_KEY in scope | ||
|
||
|
||
def add_tasks(scope: Scope, task: BackgroundTask, /) -> None: | ||
if _SCOPE_KEY not in scope: # pragma: no cover | ||
raise RuntimeError( | ||
"`add_tasks` can only be used if `BackgroundTaskMIddleware is installed" | ||
) | ||
Comment on lines
+19
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you trigger this, tho? If we always call We have some scenarios:
Ok... As you see, I was figuring stuff as I was writing this to myself. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like that now the logic is different on those scenarios. I'm having a hard time figuring out how to avoid it tho... We are in a similar situation with the exception of middleware. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually... If the What I mean is to not check if the Maybe that's your plan but with a deprecation warning? |
||
cast(_BackgroundTaskList, scope[_SCOPE_KEY]).append(task) | ||
|
||
|
||
class BackgroundTaskMiddleware: | ||
def __init__(self, app: ASGIApp) -> None: | ||
self._app = app | ||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
tasks: _BackgroundTaskList | ||
scope[_SCOPE_KEY] = tasks = [] | ||
try: | ||
await self._app(scope, receive, send) | ||
finally: | ||
for task in tasks: | ||
await task() | ||
Comment on lines
+35
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Background tasks should only run on 2xx. I guess you'll need a |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
from starlette.background import BackgroundTask | ||
from starlette.concurrency import iterate_in_threadpool | ||
from starlette.datastructures import URL, MutableHeaders | ||
from starlette.middleware import background | ||
from starlette.types import Receive, Scope, Send | ||
|
||
|
||
|
@@ -148,6 +149,12 @@ def delete_cookie( | |
) | ||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
if ( | ||
self.background is not None | ||
and background.is_background_task_middleware_installed(scope) | ||
): | ||
background.add_tasks(scope, self.background) | ||
self.background = None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the scenario you are trying to prevent when setting background to response = Response(background=BackgroundTasks(...)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see now. You are doing because you didn't remove the logic below where we run the background task. I think we can remove that logic, and you can avoid this line. (Is my understanding correct?) |
||
prefix = "websocket." if scope["type"] == "websocket" else "" | ||
await send( | ||
{ | ||
|
@@ -255,6 +262,12 @@ async def stream_response(self, send: Send) -> None: | |
await send({"type": "http.response.body", "body": b"", "more_body": False}) | ||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
if ( | ||
self.background is not None | ||
and background.is_background_task_middleware_installed(scope) | ||
): | ||
background.add_tasks(scope, self.background) | ||
self.background = None | ||
async with anyio.create_task_group() as task_group: | ||
|
||
async def wrap(func: typing.Callable[[], typing.Awaitable[None]]) -> None: | ||
|
@@ -322,6 +335,12 @@ def set_stat_headers(self, stat_result: os.stat_result) -> None: | |
self.headers.setdefault("etag", etag) | ||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: | ||
if ( | ||
self.background is not None | ||
and background.is_background_task_middleware_installed(scope) | ||
): | ||
background.add_tasks(scope, self.background) | ||
self.background = None | ||
if self.stat_result is None: | ||
try: | ||
stat_result = await anyio.to_thread.run_sync(os.stat, self.path) | ||
|
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.