This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
backendcritical PriorityID: fastapi-lifespan-async-teardown-guardrail

FastAPI Lifespan Async Context Teardown and Graceful Task Drain Standard

Enforces strict asynchronous context manager lifecycle contracts in FastAPI services to guarantee deterministic resource disposal. Mandates that all active background tasks, thread pools, and pooled database engine connections cleanly drain before SIGTERM execution terminates the process worker.

Prohibited Anti-Pattern: Unbounded fire-and-forget background tasks and abrupt database connection drops during worker shutdown

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: FastAPI Lifespan Async Context Teardown and Graceful Task Drain Standard - Enforces strict asynchronous context manager lifecycle contracts in FastAPI services to guarantee deterministic resource disposal. Mandates that all active background tasks, thread pools, and pooled database engine connections cleanly drain before SIGTERM execution terminates the process worker.
globs: **/main.py, **/app.py, **/lifespan.py, **/core/lifecycle.py, src/**/*.py
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/fastapi-lifespan-async-teardown-guardrail"
---

# FastAPI Lifespan Async Context Teardown and Graceful Task Drain Standard

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Unbounded fire-and-forget background tasks and abrupt database connection drops during worker shutdown

## System Constraints
You are a Principal Backend Architect enforcing strict FastAPI lifespan protocols. You must reject legacy startup/shutdown events, prohibit untracked background tasks created via naked asyncio.create_task, and require deterministic async context manager cleanup using try...finally to ensure connection pools and running coroutines safely drain prior to process termination.

## Strict Guidelines
- Engineers MUST ALWAYS use the '@asynccontextmanager' lifespan handler pattern on FastAPI initialization and MUST NOT use deprecated '@app.on_event("startup")' or '@app.on_event("shutdown")' hooks.
- Engineers MUST wrap all lifespan yield operations in a 'try...finally' block to guarantee teardown execution even if the application encounters an unhandled runtime error during initialization or execution.
- Engineers MUST register and track all background tasks in a shared state structure (e.g., 'app.state.background_tasks: set[asyncio.Task]') instead of spawning untracked fire-and-forget tasks via naked 'asyncio.create_task'.
- During lifespan teardown, services MUST cancel active background tasks and await them using 'asyncio.wait' or 'asyncio.gather(..., return_exceptions=True)' within an explicit, configurable timeout threshold (e.g., 'asyncio.timeout(drain_timeout)').
- Pooled client resources (SQLAlchemy AsyncEngines, Redis connection pools, HTTPX AsyncClients, and AMQP channels) MUST be explicitly disposed or closed in the lifespan 'finally' block strictly AFTER in-flight background tasks have fully drained.
- Lifespan handlers MUST NEVER catch and swallow 'asyncio.CancelledError' during application drain unless performing explicit termination cleanup before re-raising or exiting.

## Bad vs Good Implementations
### FastAPI Lifespan Context Manager vs. Legacy Untracked Shutdown

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Uses deprecated on_event hooks, launches unreferenced background tasks that get abruptly aborted on SIGTERM, and closes the database connection pool immediately without giving background tasks or active requests time to finish.
```python
import asyncio
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine

app = FastAPI()
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")

@app.on_event("startup")
async def startup():
    # ANTI-PATTERN: Untracked background task creation
    asyncio.create_task(background_metrics_worker())

@app.on_event("shutdown")
async def shutdown():
    # ANTI-PATTERN: Abrupt pool disposal while background workers or in-flight
    # requests may still be querying; tasks are orphaned and abruptly killed
    await engine.dispose()

async def background_metrics_worker():
    while True:
        await asyncio.sleep(5)

```

#### Production Standard (Correct)
> **Standard**: Implements modern @asynccontextmanager lifespan with a guaranteed try...finally structure. Tracks active coroutine tasks, signals cancellation, awaits resolution with a deterministic 10s timeout, and disposes the database pool only after tasks have completed.
```python
import asyncio
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine

logger = logging.getLogger(__name__)

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup Phase
    engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db", pool_pre_ping=True)
    app.state.engine = engine
    app.state.background_tasks = set()

    # Launch tracked background worker
    worker_task = asyncio.create_task(background_metrics_worker())
    app.state.background_tasks.add(worker_task)
    worker_task.add_done_callback(app.state.background_tasks.discard)

    try:
        yield
    finally:
        # Teardown Phase: Gracefully drain background tasks
        tasks = [t for t in app.state.background_tasks if not t.done()]
        if tasks:
            logger.info(f"Draining {len(tasks)} background tasks...")
            for t in tasks:
                t.cancel()
            try:
                async with asyncio.timeout(10.0):
                    await asyncio.gather(*tasks, return_exceptions=True)
            except* TimeoutError:
                logger.warning("Timed out waiting for background tasks to drain")

        # Teardown Phase: Deterministically dispose connection pools AFTER tasks drain
        logger.info("Disposing database connection pool...")
        await engine.dispose()

app = FastAPI(lifespan=lifespan)

async def background_metrics_worker():
    try:
        while True:
            await asyncio.sleep(5)
    except asyncio.CancelledError:
        logger.info("Metrics worker received cancellation; performing final flush.")
        raise

```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: FastAPI Lifespan Async Context Teardown and Graceful Task Drain Standard

FastAPI Lifespan Async Context Teardown and Graceful Task Drain Standard defines an industrial-grade behavioral standard for backend engineering. Enforces strict asynchronous context manager lifecycle contracts in FastAPI services to guarantee deterministic resource disposal. Mandates that all active background tasks, thread pools, and pooled database engine connections cleanly drain before SIGTERM execution terminates the process worker. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Unbounded fire-and-forget background tasks and abrupt database connection drops during worker shutdown Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftUnbounded fire-and-forget background tasks and abrupt database connection drops during worker shutdownCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS use the '@asynccontextmanager' lifespan handler pattern on FastAPI initialization and MUST NOT use deprecated '@app.on_event("startup")' or '@app.on_event("shutdown")' hooks.

Engineers MUST ALWAYS use the '@asynccontextmanager' lifespan handler pattern on FastAPI initialization and MUST NOT use deprecated '@app.on_event("startup")' or '@app.on_event("shutdown")' hooks.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST wrap all lifespan yield operations in a 'try...finally' block to guarantee teardown execution even if the application encounters an unhandled runtime error during initialization or execution.

Engineers MUST wrap all lifespan yield operations in a 'try...finally' block to guarantee teardown execution even if the application encounters an unhandled runtime error during initialization or execution.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Engineers MUST register and track all background tasks in a shared state structure (e.g., 'app.state.background_tasks

Engineers MUST register and track all background tasks in a shared state structure (e.g., 'app.state.background_tasks: set[asyncio.Task]') instead of spawning untracked fire-and-forget tasks via naked 'asyncio.create_task'.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

During lifespan teardown, services MUST cancel active background tasks and await them using 'asyncio.wait' or 'asyncio.gather(..., return_exceptions=True)' within an explicit, configurable timeout threshold (e.g., 'asyncio.timeout(drain_timeout)').

During lifespan teardown, services MUST cancel active background tasks and await them using 'asyncio.wait' or 'asyncio.gather(..., return_exceptions=True)' within an explicit, configurable timeout threshold (e.g., 'asyncio.timeout(drain_timeout)').

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Pooled client resources (SQLAlchemy AsyncEngines, Redis connection pools, HTTPX AsyncClients, and AMQP channels) MUST be explicitly disposed or closed in the lifespan 'finally' block strictly AFTER in-flight background tasks have fully drained.

Pooled client resources (SQLAlchemy AsyncEngines, Redis connection pools, HTTPX AsyncClients, and AMQP channels) MUST be explicitly disposed or closed in the lifespan 'finally' block strictly AFTER in-flight background tasks have fully drained.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
6

Lifespan handlers MUST NEVER catch and swallow 'asyncio.CancelledError' during application drain unless performing explicit termination cleanup before re-raising or exiting.

Lifespan handlers MUST NEVER catch and swallow 'asyncio.CancelledError' during application drain unless performing explicit termination cleanup before re-raising or exiting.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is FastAPI Lifespan Async Context Teardown and Graceful Task Drain Standard critical for AI coding assistants?

Enforces strict asynchronous context manager lifecycle contracts in FastAPI services to guarantee deterministic resource disposal. Mandates that all active background tasks, thread pools, and pooled database engine connections cleanly drain before SIGTERM execution terminates the process worker. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Unbounded fire-and-forget background tasks and abrupt database connection drops during worker shutdown) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

1 Rule in Stack

FastAPI