fix: create fresh SQLite engine after fork instead of reusing dead fd

The grandchild process closes all inherited fds (3..maxfd) to detach
from uWSGI. session_factory().get_bind() returned a session bound to
the now-dead SQLite fd. Capture the DB URL before forking, create a
fresh engine in the child.
This commit is contained in:
russell@unturf.com 2026-02-19 10:20:38 -05:00
parent b8955c0459
commit 67e34b08ad
2 changed files with 10 additions and 2 deletions

View file

@ -1 +1 @@
d071955
85b1a2d

View file

@ -164,6 +164,9 @@ def backfill_karaoke_async(shop_id, session_factory, app_settings):
shop_id_str = str(shop_id)
lockfile = f"/tmp/karaoke_backfill_{shop_id_str}.lock"
# Capture the DB URL before forking — the engine's fd dies after fork+close
db_url = str(session_factory().get_bind().url)
# Quick check — if lockfile exists and is locked, skip
try:
check_fd = open(lockfile, "w")
@ -207,11 +210,15 @@ def backfill_karaoke_async(shop_id, session_factory, app_settings):
lock_fd.flush()
import boto3
from sqlalchemy import create_engine
from sqlalchemy.orm import Session as SASession
from ..models.product import Product, get_media_type
from ..models.shop import Shop
session = SASession(bind=session_factory().get_bind())
# Create a fresh engine — the parent's SQLite connection fd was closed
# after fork (lines 195-199). Reusing session_factory would hit a dead fd.
engine = create_engine(db_url)
session = SASession(bind=engine)
def _make_s3():
return boto3.session.Session().client(
@ -338,6 +345,7 @@ def backfill_karaoke_async(shop_id, session_factory, app_settings):
log.exception("Background karaoke backfill failed for shop %s", shop_id_str)
finally:
session.close()
engine.dispose()
except (IOError, OSError):
pass # couldn't acquire lock — another backfill is running
except Exception: