This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
backendcritical PriorityID: fastapi-asgi-structured-concurrency-drain-boundary

FastAPI ASGI Lifespan Structured TaskGroup Drain Boundary

Enforces deterministic structured concurrency boundaries for asynchronous background work within FastAPI ASGI applications. Guarantees that any spawned background coroutines are tethered to an application-scoped or request-scoped TaskGroup that safely drains within lifespan shutdown before database and network connection pools close during Kubernetes SIGTERM events.

Prohibited Anti-Pattern: Unbounded asyncio.create_task and BackgroundTasks Leaks Causing Database Connection Poisoning During SIGTERM

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: FastAPI ASGI Lifespan Structured TaskGroup Drain Boundary - Enforces deterministic structured concurrency boundaries for asynchronous background work within FastAPI ASGI applications. Guarantees that any spawned background coroutines are tethered to an application-scoped or request-scoped TaskGroup that safely drains within lifespan shutdown before database and network connection pools close during Kubernetes SIGTERM events.
globs: app/**/*.py, src/**/*.py, api/**/*.py
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/fastapi-asgi-structured-concurrency-drain-boundary"
---

# FastAPI ASGI Lifespan Structured TaskGroup Drain Boundary

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Unbounded asyncio.create_task and BackgroundTasks Leaks Causing Database Connection Poisoning During SIGTERM

## System Constraints
You are a Principal Engineer enforcing robust ASGI structured concurrency and deterministic shutdown boundaries in Python FastAPI services. Mandate that raw asyncio.create_task and fire-and-forget Starlette BackgroundTasks are strictly prohibited for work dependent on application resources (such as DB pools, HTTP clients, and message queues). Require explicit TaskGroup or AsyncExitStack management tied directly to the ASGI lifespan context to ensure graceful task draining on Kubernetes SIGTERM termination sequences before infrastructure clients disconnect.

## Strict Guidelines
- Engineers MUST ALWAYS manage non-trivial background tasks using structured concurrency boundaries (e.g., anyio.create_task_group() or Python 3.11+ asyncio.TaskGroup) encapsulated within an ASGI lifespan context manager.
- Engineers MUST NOT invoke asyncio.create_task() or Starlette BackgroundTasks for coroutines that require database connections, Redis clients, or shared HTTP connection pools.
- Services MUST cancel and drain remaining background tasks with explicit timeouts prior to disposing of database engines (e.g., SQLAlchemy create_async_engine.dispose()) or closing connection pools.
- Engineers MUST configure readiness probes in Kubernetes to flip to unhealthy immediately upon receiving SIGTERM, allowing in-flight requests and drained TaskGroups to finalize within terminationGracePeriodSeconds.
- Components NEVER swallow asyncio.CancelledError inside background coroutines; tasks must cleanup resources and re-raise or allow CancelledError to propagate to the enclosing TaskGroup.

## Bad vs Good Implementations
### Unbounded Background Task Spawning vs. Lifespan TaskGroup Drain Boundary

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Spawning raw tasks via asyncio.create_task leaves coroutines unmanaged. When Kubernetes terminates the pod, Uvicorn exits and closes the SQLAlchemy engine immediately, aborting the coroutine mid-execution with connection errors and leaving orphaned state.
```python
import asyncio
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine

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

async def write_audit_log(user_id: str):
    # ANTI-PATTERN: Leaks beyond request and lifespan lifecycle.
    # During SIGTERM, engine is disposed while this task is still running,
    # causing InterfaceError, orphaned connections, and uncommitted data.
    async with AsyncSession(engine) as session:
        await session.execute("INSERT INTO audit_logs (user_id) VALUES (:id)", {"id": user_id})
        await session.commit()

@app.post("/users/{user_id}/action")
async def trigger_action(user_id: str):
    asyncio.create_task(write_audit_log(user_id))
    return {"status": "accepted"}
```

#### Production Standard (Correct)
> **Standard**: The AnyIO TaskGroup binds the lifespan of all background jobs to the ASGI app lifespan. When Uvicorn initiates shutdown on SIGTERM, the lifespan block exits, waits for all active tasks to complete or cancel gracefully, and only then tears down the database engine.
```python
import asyncio
from contextlib import asynccontextmanager
from typing import AsyncGenerator
import anyio
from anyio.abc import TaskGroup
from fastapi import FastAPI, Request
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine

engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db", pool_pre_ping=True)

@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    # SECURE: Explicit task group bound to ASGI lifespan boundary
    async with anyio.create_task_group() as tg:
        app.state.background_tg = tg
        yield
        # Graceful drain initiation: cancel and wait with timeout
        tg.cancel_scope.cancel()
    
    # Infrastructure is ONLY disposed after all tasks have drained
    await engine.dispose()

app = FastAPI(lifespan=lifespan)

async def write_audit_log(user_id: str):
    try:
        async with AsyncSession(engine) as session:
            await session.execute("INSERT INTO audit_logs (user_id) VALUES (:id)", {"id": user_id})
            await session.commit()
    except anyio.get_cancelled_exc_class():
        # Optional: handle graceful rollback if interrupted
        raise

@app.post("/users/{user_id}/action")
async def trigger_action(user_id: str, request: Request):
    tg: TaskGroup = request.app.state.background_tg
    tg.start_soon(write_audit_log, user_id)
    return {"status": "accepted"}
```

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 ASGI Lifespan Structured TaskGroup Drain Boundary

FastAPI ASGI Lifespan Structured TaskGroup Drain Boundary defines an industrial-grade behavioral standard for backend engineering. Enforces deterministic structured concurrency boundaries for asynchronous background work within FastAPI ASGI applications. Guarantees that any spawned background coroutines are tethered to an application-scoped or request-scoped TaskGroup that safely drains within lifespan shutdown before database and network connection pools close during Kubernetes SIGTERM events. 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 asyncio.create_task and BackgroundTasks Leaks Causing Database Connection Poisoning During SIGTERM 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 asyncio.create_task and BackgroundTasks Leaks Causing Database Connection Poisoning During SIGTERMCritical
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 manage non-trivial background tasks using structured concurrency boundaries (e.g., anyio.create_task_group() or Python 3.11+ asyncio.TaskGroup) encapsulated within an ASGI lifespan context manager.

Engineers MUST ALWAYS manage non-trivial background tasks using structured concurrency boundaries (e.g., anyio.create_task_group() or Python 3.11+ asyncio.TaskGroup) encapsulated within an ASGI lifespan context manager.

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 NOT invoke asyncio.create_task() or Starlette BackgroundTasks for coroutines that require database connections, Redis clients, or shared HTTP connection pools.

Engineers MUST NOT invoke asyncio.create_task() or Starlette BackgroundTasks for coroutines that require database connections, Redis clients, or shared HTTP connection pools.

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

Services MUST cancel and drain remaining background tasks with explicit timeouts prior to disposing of database engines (e.g., SQLAlchemy create_async_engine.dispose()) or closing connection pools.

Services MUST cancel and drain remaining background tasks with explicit timeouts prior to disposing of database engines (e.g., SQLAlchemy create_async_engine.dispose()) or closing connection pools.

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

Engineers MUST configure readiness probes in Kubernetes to flip to unhealthy immediately upon receiving SIGTERM, allowing in-flight requests and drained TaskGroups to finalize within terminationGracePeriodSeconds.

Engineers MUST configure readiness probes in Kubernetes to flip to unhealthy immediately upon receiving SIGTERM, allowing in-flight requests and drained TaskGroups to finalize within terminationGracePeriodSeconds.

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

Components NEVER swallow asyncio.CancelledError inside background coroutines

Components NEVER swallow asyncio.CancelledError inside background coroutines; tasks must cleanup resources and re-raise or allow CancelledError to propagate to the enclosing TaskGroup.

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 ASGI Lifespan Structured TaskGroup Drain Boundary critical for AI coding assistants?

Enforces deterministic structured concurrency boundaries for asynchronous background work within FastAPI ASGI applications. Guarantees that any spawned background coroutines are tethered to an application-scoped or request-scoped TaskGroup that safely drains within lifespan shutdown before database and network connection pools close during Kubernetes SIGTERM events. 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 asyncio.create_task and BackgroundTasks Leaks Causing Database Connection Poisoning During SIGTERM) 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