From 65c9d145b90eb65b295c8acf07bd41f70fc2b3e8 Mon Sep 17 00:00:00 2001 From: Light1YT Date: Sat, 13 Jun 2026 10:15:04 +0500 Subject: [PATCH] =?UTF-8?q?fix(audit):=20run=5Fin=5Fthreadpool=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20write=5Faudit=5Frow=20=D0=B2=20async=20middlewar?= =?UTF-8?q?e=20(#1202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit audit_log_middleware зарегистрирован через app.middleware("http") (main.py:92) → async dispatch на event loop. После `await call_next(...)` response уже отдан, но БЛОКИРУЕТ event loop пока идёт sync DB round-trip (SessionLocal/ execute/commit). Это останавливает ВСЕ другие запросы на loop'е до завершения INSERT'а. При исчерпании connection pool (default 5+10, sync analyze держит сессию ~15с inline-wait) checkout ждёт pool_timeout=30с НА loop'е → весь API, включая /health, замирает. Каждый аудируемый запрос: analyze, КАЖДЫЙ 2с-poll GET /forecast, export, insight-write. На активной демо-сессии — десятки таких в минуту. Fix: write_audit_row выполняется через fastapi.concurrency.run_in_threadpool — sync IO в worker-потоке, event loop свободен для других запросов. Никаких изменений в самой write_audit_row — она остаётся sync def с собственным try/except + rollback. 16/16 audit-middleware тестов зелёные (поведение для caller'а не изменилось). Closes #1202 --- backend/app/core/audit_middleware.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/app/core/audit_middleware.py b/backend/app/core/audit_middleware.py index fa18f36e..b7c894af 100644 --- a/backend/app/core/audit_middleware.py +++ b/backend/app/core/audit_middleware.py @@ -37,6 +37,7 @@ import re from collections.abc import Awaitable, Callable from fastapi import Request +from fastapi.concurrency import run_in_threadpool from fastapi.responses import Response from sqlalchemy import text @@ -171,10 +172,16 @@ async def audit_log_middleware( # но если мы тут — нечего атрибутировать, аудит пропускаем. return response - # Write после готового response — off the critical path. Любой сбой внутри - # write_audit_row проглатывается там же; здесь дополнительная страховка. + # Write после готового response — off the critical path для САМОГО клиента + # (response уже отдан выше), но БЛОКИРУЕТ event loop пока идёт sync DB + # round-trip (SessionLocal/execute/commit). Это останавливает ВСЕ другие + # запросы на loop'е до завершения insert'а — а при исчерпании connection + # pool checkout ждёт `pool_timeout=30s` НА loop'е, замораживая весь API + # включая /health (#1202). Выполняем write в threadpool через + # `run_in_threadpool` — sync IO в worker-потоке, event loop свободен. try: - write_audit_row( + await run_in_threadpool( + write_audit_row, username=username, action=action, method=request.method,