Skip to content

Commit bd9cfc0

Browse files
fastapi-sqlalchemy-pg-catalog/app: wrap create_all in the lifespan try/finally
Copilot noted that the previous try/finally only covered the yield, so a failure in create_all (the *exact* failure mode this repro demonstrates: pre-fix keploy makes create_all raise psycopg2.DatabaseError) wouldn't reach the finally and the engine pool would leak. Moved the startup logging + create_all call inside the try block; finally now runs regardless of where the failure occurs. Signed-off-by: Akash Kumar <meakash7902@gmail.com>
1 parent 6e97dd3 commit bd9cfc0

1 file changed

Lines changed: 16 additions & 9 deletions

File tree

  • fastapi-sqlalchemy-pg-catalog/app

fastapi-sqlalchemy-pg-catalog/app/main.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,23 @@ class Project(Base):
7373

7474
@asynccontextmanager
7575
async def lifespan(_: FastAPI):
76-
log.info("startup: running Base.metadata.create_all (pg_class probe expected)")
77-
# create_all does synchronous psycopg2 I/O. Offload to a thread so
78-
# uvicorn's event loop stays responsive (otherwise any other
79-
# async work scheduled on startup would block until the pg_class
80-
# probe + any CREATE TABLE round-trips complete). For this minimal
81-
# repro the difference is small, but the pattern is the right
82-
# FastAPI shape for any startup that touches a sync DB driver.
83-
await asyncio.to_thread(Base.metadata.create_all, engine)
84-
log.info("startup: create_all complete")
76+
# Wrap the startup work AND the yield in try/finally so
77+
# engine.dispose() runs even when create_all raises — which is
78+
# the exact failure mode this repro is built around (pre-fix
79+
# keploy makes create_all issue an unrecorded CREATE TABLE that
80+
# raises psycopg2.DatabaseError mid-startup; without the wrap,
81+
# the connection pool would leak on every replay attempt).
8582
try:
83+
log.info("startup: running Base.metadata.create_all (pg_class probe expected)")
84+
# create_all does synchronous psycopg2 I/O. Offload to a thread
85+
# so uvicorn's event loop stays responsive (otherwise any other
86+
# async work scheduled on startup would block until the pg_class
87+
# probe + any CREATE TABLE round-trips complete). For this
88+
# minimal repro the difference is small, but the pattern is the
89+
# right FastAPI shape for any startup that touches a sync DB
90+
# driver.
91+
await asyncio.to_thread(Base.metadata.create_all, engine)
92+
log.info("startup: create_all complete")
8693
yield
8794
finally:
8895
# Release pooled connections on shutdown so repeated

0 commit comments

Comments
 (0)