fix: acquire exclusive DB lock after migrations to avoid self-deadlock

The previous commit acquired the exclusive lock before Alembic migrations,
but Alembic opens its own connection — which was then blocked by our lock.
Move lock acquisition to after migrations complete in a dedicated connection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luke Mino-Altherr 2026-03-03 18:55:20 -08:00
parent c268507ea7
commit ce17e303fc

View File

@ -66,6 +66,25 @@ def get_db_path():
raise ValueError(f"Unsupported database URL '{url}'.") raise ValueError(f"Unsupported database URL '{url}'.")
def _acquire_exclusive_lock(engine, db_path):
"""Acquire an exclusive SQLite lock to prevent multi-process access.
The lock is held for the lifetime of the connection (and thus the process).
A second process attempting to use the same database will fail fast at startup.
"""
conn = engine.connect()
try:
conn.execute(sa.text("PRAGMA locking_mode=EXCLUSIVE"))
conn.execute(sa.text("BEGIN EXCLUSIVE"))
conn.execute(sa.text("COMMIT"))
except Exception:
raise RuntimeError(
f"Could not acquire exclusive lock on database '{db_path}'. "
"Another ComfyUI process may already be using it. "
"Use --database-url to specify a separate database file."
)
def init_db(): def init_db():
db_url = args.database_url db_url = args.database_url
logging.debug(f"Database URL: {db_url}") logging.debug(f"Database URL: {db_url}")
@ -77,25 +96,14 @@ def init_db():
# Check if we need to upgrade # Check if we need to upgrade
engine = create_engine(db_url) engine = create_engine(db_url)
# Enable foreign key enforcement and exclusive locking for SQLite # Enable foreign key enforcement for SQLite
@event.listens_for(engine, "connect") @event.listens_for(engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record): def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor() cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON") cursor.execute("PRAGMA foreign_keys=ON")
cursor.execute("PRAGMA locking_mode=EXCLUSIVE")
cursor.close() cursor.close()
# Acquire the exclusive lock early by opening a connection and triggering a read.
# This prevents two processes from sharing the same database file.
conn = engine.connect() conn = engine.connect()
try:
conn.execute(sa.text("PRAGMA schema_version"))
except Exception:
raise RuntimeError(
f"Could not acquire exclusive lock on database '{db_path}'. "
"Another ComfyUI process may already be using it. "
"Use --database-url to specify a separate database file."
)
context = MigrationContext.configure(conn) context = MigrationContext.configure(conn)
current_rev = context.get_current_revision() current_rev = context.get_current_revision()
@ -124,6 +132,12 @@ def init_db():
logging.exception("Error upgrading database: ") logging.exception("Error upgrading database: ")
raise e raise e
# Acquire an exclusive lock after migrations are complete.
# Alembic uses its own connection, so we must wait until it's done
# before locking — otherwise our own lock blocks the migration.
conn.close()
_acquire_exclusive_lock(engine, db_path)
global Session global Session
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)