Skip to main content

Cancel FastAPI request handlers when the client disconnects

Project description

fastapi-disconnect

Cancel FastAPI request handlers when the client disconnects.

By default, when a client disconnects (timeout, closed tab, network failure), FastAPI keeps executing the request handler to completion. For long-running handlers — an AI flow making paid LLM calls, a heavy database query — that means paying for work nobody is waiting for. This library cancels the handler the moment the client goes away, by listening for the ASGI http.disconnect event (no polling of request.is_disconnected()).

Installation

pip install fastapi-disconnect

Usage

App-wide, via middleware — protects every route, no handler changes:

from fastapi import FastAPI
from fastapi_disconnect import CancelOnDisconnectMiddleware

app = FastAPI()
app.add_middleware(
    CancelOnDisconnectMiddleware,
    exclude_paths=[r"/uploads/.*"],   # optional; re.fullmatch against the path
    on_disconnect=log_cancellation,   # optional; sync or async, gets the ASGI scope
)

on_disconnect runs only when a premature disconnect cancelled a request (not on normal connection close) — a natural place for a metric counting the work cancellation saved you.

Per-endpoint, via decorator — applied below the route decorator:

from fastapi_disconnect import cancel_on_disconnect

@app.get("/generate")
@cancel_on_disconnect
async def generate() -> str:
    return await expensive_llm_flow()

Cooperative, via dependency — for handlers that want to decide when to stop instead of being cancelled preemptively (e.g. finish the current step, checkpoint, then exit):

from fastapi_disconnect import Disconnected

@app.get("/agent")
async def agent(disconnected: Disconnected) -> Result:
    for step in plan:
        if disconnected.is_set():        # free local check, no polling
            return partial_result
        await run_step(step)

Disconnected is an annotated alias for Annotated[asyncio.Event, Depends(disconnected_event)]; the event is set the moment the client disconnects and can also be awaited (disconnected.wait()). Use it instead of the decorator/middleware on a route — combined with them, preemptive cancellation fires first (though the event is still set, which is handy inside shielded cleanup). All three mechanisms share a single per-request watcher, so any combination is safe.

On disconnect, the handler receives a regular asyncio.CancelledError at its next await, so finally blocks and async context managers run as usual. The decorator produces a 499 Client Closed Request response (which goes nowhere — the client is gone — but keeps logs meaningful); the middleware simply stops.

Cleanup on cancellation

Catching CancelledError for cleanup is supported, with one rule: the cancellation is level-triggered (anyio cancel-scope semantics). Once the client is gone, every subsequent await inside the handler raises CancelledError again — so asynchronous cleanup must run in a shielded scope:

import anyio

@app.get("/generate")
@cancel_on_disconnect
async def generate() -> str:
    try:
        return await expensive_llm_flow()
    except asyncio.CancelledError:
        with anyio.CancelScope(shield=True):
            await release_resources()  # runs to completion
        raise

An unshielded await in an except CancelledError or finally block is re-cancelled at its first checkpoint. (asyncio.shield() is not a substitute: it detaches the inner call, but the awaiting handler is still re-cancelled, so the cleanup continues without you.) Synchronous cleanup needs no shielding. Re-raise after cleaning up — a handler that swallows the cancellation and returns a value is treated as having completed normally.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_disconnect-0.1.0.tar.gz (36.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_disconnect-0.1.0-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_disconnect-0.1.0.tar.gz.

File metadata

  • Download URL: fastapi_disconnect-0.1.0.tar.gz
  • Upload date:
  • Size: 36.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastapi_disconnect-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6302d31ac8fa09d6520ccbd2bf1f8d9d026c4640e3c6146ef3152cd1c969afdb
MD5 dcac2b6961bf2bd39874f9c42ab2a836
BLAKE2b-256 499d0eefc7a11d5e06baa1ae9715d5a47f7aeb9f29b6d10957a6272cd8c76adc

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_disconnect-0.1.0.tar.gz:

Publisher: publish.yml on qweeze/fastapi-disconnect

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastapi_disconnect-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_disconnect-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 119781a35371cbbcada4011c2b971bc82b97caa722713e8a0736f8f8ef469a55
MD5 f7e6170f26a9878f92911d6639c460df
BLAKE2b-256 dc78f86430d63b23dd2ae4e7bf068a61b8bce7a47efb1beff0dc83860ea8e185

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_disconnect-0.1.0-py3-none-any.whl:

Publisher: publish.yml on qweeze/fastapi-disconnect

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page