mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
fix(cover): release the DB connection before the FTP thumbnail download (#2572)
GET /printers/{id}/cover took its printer row via Depends(get_db), whose
yield-dependency session stays open for the whole request — including the
3MF cover download (up to 8 remote paths x retries with backoff, minutes
under FTP contention). One pooled connection sat idle-in-transaction the
entire time; on a large farm a wall of dashboards drained the pool. The
route now fetches the printer in a short-lived async_session() and releases
the connection before the download (expire_on_commit=False keeps printer.*
readable). Pinned by a signature-inspection guard that fails if get_db is
ever re-added.
fix(print-start): release the DB connection across plate detection and 3MF download (#2572)
on_print_start held one session from top to bottom of the handler, across
two slow I/O blocks that need no database: the plate-detection camera grab
and, on the new-archive path, the multi-path 3MF FTP download (its own
comments cite worst cases of tens of minutes). The connection sat idle-in-
transaction for both, once per starting print. It now commits at each
boundary — only read SELECTs have run on those paths (every write branch
returns earlier), so the commit persists nothing and simply returns the
connection to the pool for the I/O; the next query re-acquires, and
expire_on_commit=False keeps printer.* readable.
fix(startup): connect to printers concurrently so the API serves within seconds (#2572)
init_printer_connections awaited each printer's connection serially, and
connect_printer ends in a fixed 1s settle wait. The MQTT connect is non-
blocking (connect_async + loop_start), so that 1s x fleet size was pure
serial dead air the FastAPI lifespan blocked on before uvicorn began
serving — ~100s before port 8000 responded on a 93-printer farm. The
connections are now started with asyncio.gather, so the step takes ~1s
regardless of fleet size. return_exceptions=True isolates each result: one
unreachable printer no longer aborts the rest, or startup itself.
This commit is contained in:
@@ -571,6 +571,32 @@ class TestPrintersAPI:
|
||||
# ========================================================================
|
||||
|
||||
|
||||
class TestCoverPoolHygiene:
|
||||
"""Regression guard for the /cover DB-connection leak (issue #2572)."""
|
||||
|
||||
def test_cover_does_not_hold_a_get_db_session(self):
|
||||
"""The cover endpoint must NOT take a ``Depends(get_db)`` session.
|
||||
|
||||
``get_db`` is a ``yield`` dependency, so its session stays open for the
|
||||
whole request — including the 3MF cover download (up to 8 remote paths ×
|
||||
retries with backoff, minutes under FTP contention), pinning one pooled
|
||||
DB connection ``idle in transaction`` the entire time. The endpoint
|
||||
fetches the printer in a short-lived ``async with async_session()`` and
|
||||
releases the connection before the FTP work. If someone re-adds a
|
||||
``Depends(get_db)`` param, this fails.
|
||||
"""
|
||||
import inspect
|
||||
|
||||
from backend.app.api.routes.printers import get_db, get_printer_cover
|
||||
|
||||
for name, param in inspect.signature(get_printer_cover).parameters.items():
|
||||
dependency = getattr(param.default, "dependency", None)
|
||||
assert dependency is not get_db, (
|
||||
f"get_printer_cover re-introduced a get_db-held session via parameter {name!r} — "
|
||||
"it would stay open for the entire FTP cover download (issue #2572)"
|
||||
)
|
||||
|
||||
|
||||
class TestPrinterDataIntegrity:
|
||||
"""Tests for printer data integrity."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user