fix: requests hang or fail with 503 after pages are closed while loading (#32617)

Closing a page while the server was still sending it data (a file's processing status, a chat export) could interrupt a database read halfway. On SQLite the connection used for it was then never given back, and a later unrelated request could hang forever when that connection was finally cleaned up; once enough were lost, every request needing the database waited 30 seconds and failed with a 503. The connection is now kept and rolled back instead of being thrown away inside the interrupted request, and the session is closed once instead of twice, as either alone still lost connections. Closing 600 chat exports and 300 file status streams mid-load on SQLite used to lose 60 (the whole pool in that test) and 56 connections, now none; Postgres lost none before or after.

Fixes #32609
This commit is contained in:
Classic298 2026-10-20 20:23:05 +02:00 • committed by GitHub
parent 9cbd27011b
commit 2355b8a369
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import asyncio
import logging
import os
import re
@ -476,6 +477,12 @@ if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL:
@event.listens_for(async_engine.sync_engine, 'connect')
def _set_sqlite_pragmas(dbapi_connection, connection_record):
_apply_sqlite_pragmas(dbapi_connection)
@event.listens_for(async_engine.sync_engine, 'handle_error')
def _keep_connection_on_cancel(context):
# invalidating here would close the connection inside the cancelled task, and that close is lost
if isinstance(context.original_exception, asyncio.CancelledError):
context.is_disconnect = False
else:
if isinstance(DATABASE_POOL_SIZE, int):
if DATABASE_POOL_SIZE > 0:
@ -514,20 +521,14 @@ AsyncSessionLocal = async_sessionmaker(
async def get_async_session():
"""Async session generator for FastAPI Depends()."""
async with AsyncSessionLocal() as db:
try:
yield db
finally:
await db.close()
yield db
@asynccontextmanager
async def get_async_db():
"""Async context manager for use outside of FastAPI dependency injection."""
async with AsyncSessionLocal() as db:
try:
yield db
finally:
await db.close()
yield db
@asynccontextmanager