fix(audit): run_in_threadpool для write_audit_row в async middleware (#1202)
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
This commit is contained in:
parent
2a68c2e202
commit
323e399593
1 changed files with 10 additions and 3 deletions
|
|
@ -37,6 +37,7 @@ import re
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
from fastapi.concurrency import run_in_threadpool
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
|
@ -171,10 +172,16 @@ async def audit_log_middleware(
|
||||||
# но если мы тут — нечего атрибутировать, аудит пропускаем.
|
# но если мы тут — нечего атрибутировать, аудит пропускаем.
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Write после готового response — off the critical path. Любой сбой внутри
|
# Write после готового response — off the critical path для САМОГО клиента
|
||||||
# write_audit_row проглатывается там же; здесь дополнительная страховка.
|
# (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:
|
try:
|
||||||
write_audit_row(
|
await run_in_threadpool(
|
||||||
|
write_audit_row,
|
||||||
username=username,
|
username=username,
|
||||||
action=action,
|
action=action,
|
||||||
method=request.method,
|
method=request.method,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue