Skip to main content

Introduction to AsyncExitStackMiddleware

When you handle file uploads in a web application, those files are often stored in temporary locations or held in memory. If these resources aren't explicitly closed after the request is finished, your application can leak file descriptors or memory. FastAPI uses AsyncExitStackMiddleware to manage these resources automatically, ensuring that every uploaded file is properly closed once the response is sent.

The Role of AsyncExitStackMiddleware

The AsyncExitStackMiddleware is a specialized ASGI middleware that provides a centralized cleanup mechanism for the duration of a request. It creates a contextlib.AsyncExitStack and attaches it to the ASGI scope. This stack acts as a container for cleanup callbacks that should run only after the entire request-response cycle is complete.

In fastapi/middleware/asyncexitstack.py, the middleware is implemented as follows:

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)

When a request enters the middleware:

  1. It initializes an AsyncExitStack.
  2. It stores this stack in the scope dictionary under the key fastapi_middleware_astack.
  3. It calls the next layer of the application.
  4. When the application finishes (after the response is sent), the async with block exits, and the stack automatically executes all registered cleanup callbacks.

Automatic Resource Cleanup

FastAPI uses this middleware primarily to handle the cleanup of form data and uploaded files. When a path operation expects form data (e.g., using UploadFile), FastAPI's routing logic retrieves the stack from the scope and registers the close method of the form data.

In fastapi/routing.py, the request handler uses the stack like this:

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:
# Read the form data (including files)
body = await request.form()
# Register the close method to be called by the middleware later
file_stack.push_async_callback(body.close)

By pushing body.close onto the file_stack, FastAPI ensures that even if the request fails or the connection is closed prematurely, the temporary files created during the form parsing are cleaned up.

Middleware Placement and Rationale

FastAPI automatically adds AsyncExitStackMiddleware to your application's middleware stack. Its position is strategic: it is placed inside user-defined middlewares but outside the main application router.

In fastapi/applications.py, the build_middleware_stack method organizes the stack:

# 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.
# ...
# By placing the middleware and the AsyncExitStack here, inside all
# user middlewares, the same context is used.
Middleware(AsyncExitStackMiddleware),

This placement ensures that:

  1. Context Consistency: The stack operates within the same contextvars context as the inner application. This was historically important when the middleware also handled dependencies with yield.
  2. Reliable Cleanup: Because it wraps the router, it is guaranteed to run its cleanup logic after the router has finished processing the request and generating a response.

Comparison with Dependency Stacks

It is important to distinguish fastapi_middleware_astack from fastapi_inner_astack.

  • fastapi_middleware_astack: Managed by AsyncExitStackMiddleware. Its primary purpose in modern FastAPI is closing uploaded files and form data. It lives for the entire duration of the ASGI request.
  • fastapi_inner_astack: Managed internally by the routing logic (specifically within solve_dependencies). It is used to handle the lifecycle of dependencies that use yield.

FastAPI separates these to better support streaming responses. Dependencies are often closed earlier or handled differently to ensure that background tasks and streams have access to the resources they need without keeping the entire middleware-level stack open longer than necessary.