Skip to content
AITroveRead. Build. Understand.
Make this comfortable

FastAPI Lifespan, Background Work, and Delivery State

Last updated: 5 Oct 20268 min read
tutorial
IntermediateBy AITrove Editorial

An application lifespan groups resources that should be created before requests and closed during shutdown, such as an outbound HTTP client or database engine. A background task attached to a response runs later in the same application process. That is convenient for small best-effort work, but it is not a durable queue and cannot prove that a notification survived a crash. A permit approval with a customer-visible notice needs a durable delivery intent recorded in the transaction that changed the case. The response can then return a committed operation ID while a separate dispatcher sends the message and records attempts. A local background task may wake the dispatcher, but the stored intent is the recovery source.

Working case

An approval endpoint commits case 62 and attaches send_email to a response background task. The process restarts just after sending the 200 response. The task never runs, yet the audit page claims the reviewer notification was issued. Another deployment creates a new outbound client on every request and leaves idle connections behind. The repaired service writes an outbox event with the approval, returns the operation result, and lets a worker claim the event using bounded leases and retries. A lifespan context owns one configured outbound client per process and closes it on shutdown. A background wake-up is optional; a periodic dispatcher still finds pending work after restart.

Implementation boundary

python
from contextlib import asynccontextmanager
from typing import Annotated
from fastapi import BackgroundTasks, Depends, FastAPI
from .delivery import OutboundClient, wake_dispatcher
from .services import ApprovalCommand, Reviewer, current_reviewer, record_approval_and_outbox


@asynccontextmanager
async def lifespan(app: FastAPI):
    client = OutboundClient(timeout_seconds=5)
    app.state.outbound_client = client
    try:
        yield
    finally:
        await client.aclose()


app = FastAPI(lifespan=lifespan)


@app.post("/permits/{case_id}/approval")
async def approve_permit(
    case_id: int,
    command: ApprovalCommand,
    background_tasks: BackgroundTasks,
    reviewer: Annotated[Reviewer, Depends(current_reviewer)],
):
    outcome = await record_approval_and_outbox(reviewer.id, case_id, command)
    background_tasks.add_task(wake_dispatcher, outcome.event_id)
    return {"operation_id": outcome.operation_id, "status": outcome.status}

Create shared network clients in an async lifespan context and store them in application state or a dependency with clear ownership. Close them in the context's finalization path, including partial startup failure where relevant. Do not start migrations or slow remote probes in every request. For critical delivery, write the outbox row before the approval transaction commits; include an event ID and stable recipient snapshot or lookup policy. A dispatcher claims pending events, sends with a provider idempotency key when supported, and records success or retry state. Give each task its own database session and bound concurrency. Use response background tasks only for a best-effort wake-up or other work whose loss is acceptable.

Cost and boundaries

A shared outbound client avoids repeated connection setup, but each worker process owns its own pool, so total connections scale with process count. Lifespan startup can delay readiness; close failures can slow shutdown. An outbox adds a row and index work per approval and requires a dispatcher, retry policy, dead-letter inspection, and retention. That overhead buys a recoverable delivery state. In-process response tasks are cheap to schedule but compete with request capacity and vanish on abrupt termination. Track oldest pending event, claim age, retry count, provider result, process restart count, client pool saturation, and the gap between approval commit and notification acceptance.

Failure trace

Kill the API process immediately after it returns approval success. Restart the dispatcher and confirm the stored event is still delivered once by event identity. Force a provider timeout after it accepts a message; retry under the same provider key and reconcile ambiguous delivery rather than blindly generating a new event. Crash after claiming an event and confirm its lease expires for recovery. Start the application with an invalid outbound configuration and verify readiness fails without leaked clients. Stop the server under active requests and check that the shared client closes after bounded work. Disable the optional wake-up task and ensure periodic dispatch still empties the outbox.

Verification

  • Shared clients are closed on shutdown.
  • Approval and outbox intent commit before success is returned.
  • Delivery can recover even if the optional wake-up never executes.

Practice drill

Implement lifespan ownership for one outbound client and a transactional approval outbox. Use case 62, reviewer 47, and an event ID distinct from the HTTP request ID. The approval route commits the status and event together, then returns the saved operation result. Write a dispatcher that claims at most 47 pending events per pass with a finite lease and records attempts. Add fault injection at commit, response, claim, provider acceptance, and process shutdown. Report which failures replay automatically and which require operator review. Ensure a worker obtains its own session instead of reusing a request dependency after response completion.

Decision note

Use lifespan for shared process resources and durable storage for any post-response effect the product promises to deliver.

Common Mistakes

  • Treating a response background task as a durable queue.
  • Sharing a request session with a task after response completion.
  • Creating a fresh outbound connection pool for every request.

Related lessons

FastAPI Contract and Resource Boundaries; FastAPI Dependencies and Object Authorization; FastAPI Input, Output, and Error Contracts; FastAPI Async Sessions and Transaction Ownership; Background Workflow Reliability; Outbound Email and Delivery State.

Apply and check

Build Project: FastAPI permit service boundaries and review Web Development: FastAPI contract and resource quiz.

web-tech
web-development
Storage details