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

FastAPI Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants

Enforces strict scope boundaries and deterministic cleanup for SQLAlchemy 2.0 AsyncSession lifecycles within FastAPI applications. Prevents connection pool starvation, context variable bleeding, and orphaned connection checkouts across concurrent TaskGroups and streaming responses. Mandates explicit session scoping per concurrency unit and forbids leaking AsyncSession instances across async generator boundaries.

Prohibited Anti-Pattern: Orphaned connection pool checkout and deadlock via unclosed async generators and shared AsyncSession instances across TaskGroups

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: FastAPI Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants - Enforces strict scope boundaries and deterministic cleanup for SQLAlchemy 2.0 AsyncSession lifecycles within FastAPI applications. Prevents connection pool starvation, context variable bleeding, and orphaned connection checkouts across concurrent TaskGroups and streaming responses. Mandates explicit session scoping per concurrency unit and forbids leaking AsyncSession instances across async generator boundaries.
globs: src/**/*.py, app/**/*.py, services/**/*.py, api/**/*.py
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/fastapi-async-sqlalchemy-pool-lifecycle"
---

# FastAPI Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Orphaned connection pool checkout and deadlock via unclosed async generators and shared AsyncSession instances across TaskGroups

## System Constraints
You are a Principal Backend Architect enforcing FastAPI Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants. You must ensure that every database connection checkout corresponds to a strictly scoped, deterministic lifecycle managed via async context managers. Under no circumstances should an AsyncSession be shared across concurrent coroutines (such as asyncio.gather or asyncio.TaskGroup), passed into long-lived unmonitored background tasks, or left unfinalized across streaming response generators.

## Strict Guidelines
- Engineers MUST ALWAYS yield the database session inside an `async with async_session_maker() as session:` block with a nested `try...finally` or explicit transaction boundary (`async with session.begin():`) to guarantee connection checkin back to `AsyncEngine` pool.
- Engineers MUST NOT share a single `AsyncSession` across multiple concurrent coroutines running inside `asyncio.TaskGroup`, `anyio.create_task_group`, or `asyncio.gather`, as `AsyncSession` is strictly non-thread-safe and non-reentrant across concurrent tasks.
- Code SHOULD spawn independent session contexts via a session factory inside each concurrent task branch when executing parallel subtasks.
- Services MUST explicitly call `await engine.dispose()` on the SQLAlchemy `AsyncEngine` within the FastAPI lifespan context manager shutdown phase to gracefully drain and close all pooled connections.
- Components NEVER hand off a request-scoped `AsyncSession` dependency directly to `BackgroundTasks` or detached fire-and-forget tasks; background execution MUST construct its own isolated session boundary.
- Streaming endpoints utilizing `StreamingResponse` MUST NOT rely on dependency-injected generator sessions whose teardown occurs outside the response streaming iteration loop.

## Bad vs Good Implementations
### TaskGroup Concurrent Queries: Shared Session vs. Per-Task Session Factory

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: AsyncSession in SQLAlchemy 2.0 relies on an internal asyncio lock and greenlet execution context that does not support concurrent dispatch. Dispatching multiple execute calls on the same session concurrently will raise an InvalidRequestError ('concurrent query execution') or deadlock the connection pool by leaking open transactions.
```python
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
import asyncio
from app.db import get_session, Item, User

router = APIRouter()

@router.get("/aggregate/{item_id}")
async def get_aggregated_data(item_id: int, session: AsyncSession = Depends(get_session)):
    # CRITICAL DEFECT: Concurrently accessing the same AsyncSession across TaskGroup tasks
    # Causes greenlet race conditions, connection corruption, and pool checkout starvation
    async with asyncio.TaskGroup() as tg:
        item_task = tg.create_task(session.execute(select(Item).where(Item.id == item_id)))
        user_task = tg.create_task(session.execute(select(User).where(User.item_id == item_id)))
    
    return {
        "item": item_task.result().scalar_one_or_none(),
        "user": user_task.result().scalar_one_or_none(),
    }
```

#### Production Standard (Correct)
> **Standard**: Each concurrent coroutine instantiated inside the TaskGroup receives its own session context via the session factory. Connections are checked out and returned to the pool independently and deterministically without cross-task state interference.
```python
from fastapi import APIRouter
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
from sqlalchemy import select
import asyncio
from app.db import async_session_factory, Item, User

router = APIRouter()

async def fetch_item(session_factory: async_sessionmaker[AsyncSession], item_id: int) -> Item | None:
    async with session_factory() as session:
        result = await session.execute(select(Item).where(Item.id == item_id))
        return result.scalar_one_or_none()

async def fetch_user(session_factory: async_sessionmaker[AsyncSession], item_id: int) -> User | None:
    async with session_factory() as session:
        result = await session.execute(select(User).where(User.item_id == item_id))
        return result.scalar_one_or_none()

@router.get("/aggregate/{item_id}")
async def get_aggregated_data(item_id: int):
    # INVARIANT PRESERVED: Each subtask receives an isolated AsyncSession checkout
    async with asyncio.TaskGroup() as tg:
        item_task = tg.create_task(fetch_item(async_session_factory, item_id))
        user_task = tg.create_task(fetch_user(async_session_factory, item_id))
    
    return {
        "item": item_task.result(),
        "user": user_task.result(),
    }
```

### FastAPI Streaming Response: Premature Dependency Teardown vs. Consumer-Controlled Context

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: When using FastAPI's standard dependency injection for streaming responses, the generator dependency scope might exit or get closed unexpectedly during network backpressure, or keep the pool checked out indefinitely on client disconnect, starving the pool under high load.
```python
from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.db import get_session, LogEntry

router = APIRouter()

@router.get("/logs/export")
async def export_logs(session: AsyncSession = Depends(get_session)):
    async def generate_csv():
        # CRITICAL DEFECT: get_session teardown closes the connection before streaming ends,
        # or connection stays checked out indefinitely if the client disconnects without finalizer execution
        result = await session.stream(select(LogEntry))
        async for row in result.scalars():
            yield f"{row.id},{row.message}\n"
            
    return StreamingResponse(generate_csv(), media_type="text/csv")
```

#### Production Standard (Correct)
> **Standard**: The session context manager wraps the generator body directly. If the client disconnects halfway through the stream, the generator raises GeneratorExit, triggering the finally block and context manager __aexit__, safely returning the connection to the AsyncEngine pool.
```python
from collections.abc import AsyncIterator
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
from sqlalchemy import select
from app.db import async_session_factory, LogEntry

router = APIRouter()

async def stream_log_records() -> AsyncIterator[str]:
    # INVARIANT PRESERVED: Session lifecycle is fully owned and encapsulated within generator execution
    async with async_session_factory() as session:
        async with session.stream(select(LogEntry)) as result:
            try:
                async for row in result.scalars():
                    yield f"{row.id},{row.message}\n"
            finally:
                # Guarantees cursor closure and rollback on client disconnects
                await session.rollback()

@router.get("/logs/export")
async def export_logs():
    return StreamingResponse(stream_log_records(), media_type="text/csv")
```

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 Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants

FastAPI Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants defines an industrial-grade behavioral standard for backend engineering. Enforces strict scope boundaries and deterministic cleanup for SQLAlchemy 2.0 AsyncSession lifecycles within FastAPI applications. Prevents connection pool starvation, context variable bleeding, and orphaned connection checkouts across concurrent TaskGroups and streaming responses. Mandates explicit session scoping per concurrency unit and forbids leaking AsyncSession instances across async generator boundaries. 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: Orphaned connection pool checkout and deadlock via unclosed async generators and shared AsyncSession instances across TaskGroups 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 DriftOrphaned connection pool checkout and deadlock via unclosed async generators and shared AsyncSession instances across TaskGroupsCritical
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 yield the database session inside an `async with async_session_maker() as session

Engineers MUST ALWAYS yield the database session inside an `async with async_session_maker() as session:` block with a nested `try...finally` or explicit transaction boundary (`async with session.begin():`) to guarantee connection checkin back to `AsyncEngine` pool.

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 share a single `AsyncSession` across multiple concurrent coroutines running inside `asyncio.TaskGroup`, `anyio.create_task_group`, or `asyncio.gather`, as `AsyncSession` is strictly non-thread-safe and non-reentrant across concurrent tasks.

Engineers MUST NOT share a single `AsyncSession` across multiple concurrent coroutines running inside `asyncio.TaskGroup`, `anyio.create_task_group`, or `asyncio.gather`, as `AsyncSession` is strictly non-thread-safe and non-reentrant across concurrent tasks.

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

Code SHOULD spawn independent session contexts via a session factory inside each concurrent task branch when executing parallel subtasks.

Code SHOULD spawn independent session contexts via a session factory inside each concurrent task branch when executing parallel subtasks.

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

Services MUST explicitly call `await engine.dispose()` on the SQLAlchemy `AsyncEngine` within the FastAPI lifespan context manager shutdown phase to gracefully drain and close all pooled connections.

Services MUST explicitly call `await engine.dispose()` on the SQLAlchemy `AsyncEngine` within the FastAPI lifespan context manager shutdown phase to gracefully drain and close all pooled connections.

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 hand off a request-scoped `AsyncSession` dependency directly to `BackgroundTasks` or detached fire-and-forget tasks

Components NEVER hand off a request-scoped `AsyncSession` dependency directly to `BackgroundTasks` or detached fire-and-forget tasks; background execution MUST construct its own isolated session boundary.

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

Streaming endpoints utilizing `StreamingResponse` MUST NOT rely on dependency-injected generator sessions whose teardown occurs outside the response streaming iteration loop.

Streaming endpoints utilizing `StreamingResponse` MUST NOT rely on dependency-injected generator sessions whose teardown occurs outside the response streaming iteration loop.

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 Async SQLAlchemy 2.0 Connection Pool Lifecycle Invariants critical for AI coding assistants?

Enforces strict scope boundaries and deterministic cleanup for SQLAlchemy 2.0 AsyncSession lifecycles within FastAPI applications. Prevents connection pool starvation, context variable bleeding, and orphaned connection checkouts across concurrent TaskGroups and streaming responses. Mandates explicit session scoping per concurrency unit and forbids leaking AsyncSession instances across async generator boundaries. 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 (Orphaned connection pool checkout and deadlock via unclosed async generators and shared AsyncSession instances across TaskGroups) 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