Resource Management with AsyncExitStack
FastAPI uses AsyncExitStackMiddleware to manage the lifecycle of asynchronous resources that must persist throughout the duration of an HTTP request and be reliably cleaned up after the response is sent. This is primarily used to ensure that temporary files created during the parsing of multipart form data are closed and deleted.
The Request-Level Exit Stack
The AsyncExitStackMiddleware, defined in fastapi/middleware/asyncexitstack.py, is a lightweight ASGI middleware that initializes a contextlib.AsyncExitStack and attaches it to the ASGI scope.
class AsyncExitStackMiddleware:
def __init__(
self, app: ASGIApp, context_name: str = "fastapi_middleware_astack"
) -> None:
self.app = app
self.context_name = context_name
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
async with AsyncExitStack() as stack:
scope[self.context_name] = stack
await self.app(scope, receive, send)
By wrapping the entire request execution in an async with block, FastAPI ensures that any cleanup callbacks registered to this stack will execute even if an exception occurs during the request processing or after the response has started streaming.
Automatic Resource Cleanup
The most common use case for this middleware is the management of UploadFile objects. When a path operation receives form data, FastAPI parses the request body. If the body contains files, they are stored in temporary files on disk or in memory.
In fastapi/routing.py, the request handler retrieves the stack from the scope and registers the body.close method as a cleanup callback:
# From fastapi/routing.py
async def app(request: Request) -> Response:
# ...
file_stack = request.scope.get("fastapi_middleware_astack")
assert isinstance(file_stack, AsyncExitStack), (
"fastapi_middleware_astack not found in request scope"
)
# ...
if body_field:
if is_body_form:
body = await request.form()
file_stack.push_async_callback(body.close)
When the AsyncExitStackMiddleware finishes its execution, it triggers body.close(), which in turn closes and removes all temporary files associated with that request's form data.
Middleware Ordering and Context Preservation
FastAPI automatically adds AsyncExitStackMiddleware to the middleware stack during the initialization of the FastAPI app. The placement of this middleware is a deliberate design choice implemented in fastapi/applications.py within the build_middleware_stack method:
# From fastapi/applications.py
def build_middleware_stack(self) -> ASGIApp:
# ...
middleware = (
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
+ self.user_middleware
+ [
Middleware(
ExceptionMiddleware,
handlers=exception_handlers,
debug=debug,
),
# Add FastAPI-specific AsyncExitStackMiddleware for closing files.
# ...
# This needs to happen after user middlewares because those create a
# new contextvars context copy by using a new AnyIO task group.
# This AsyncExitStack preserves the context for contextvars...
Middleware(AsyncExitStackMiddleware),
]
)
# ...
The middleware is positioned after user-defined middlewares. This ensures that if a user middleware creates a new contextvars context (for example, by using an AnyIO task group), the AsyncExitStack is created within that specific context. This allows cleanup tasks to access the same context variables that were available during the request execution.
Separation from Dependency Management
While AsyncExitStackMiddleware manages request-level resources like files, it is distinct from the stack used for dependencies that use yield.
FastAPI uses a separate stack, identified in the scope as fastapi_inner_astack, to manage the lifecycle of dependencies. This separation was introduced to better support StreamingResponse. Because a StreamingResponse may continue to run after the main request handler has finished, dependencies must remain active until the stream is fully consumed.
The fastapi_middleware_astack managed by AsyncExitStackMiddleware serves as the outermost safety net, ensuring that system-level resources like file handles are released regardless of how the dependency resolution or response streaming concludes.