Replace SQLite exclusive lock with cross-platform file lock

- Use filelock (FileLock) instead of PRAGMA locking_mode=EXCLUSIVE to
  prevent multi-process database access. The OS automatically releases
  the lock when the process exits, even on crashes or Ctrl+C.
- Add friendly error messages for database-is-locked and general
  database init failures when --enable-assets is set.
- Exit the process instead of silently disabling assets when the user
  explicitly passed --enable-assets and the database fails.
- Add filelock to requirements.txt.

Amp-Thread-ID: https://ampcode.com/threads/T-019cbab8-50d4-748c-9669-2506575dda44
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Luke Mino-Altherr 2026-03-04 13:48:10 -08:00
parent d5cac66405
commit 6e33c4985a
3 changed files with 34 additions and 18 deletions

View File

@ -3,6 +3,7 @@ import os
import shutil
from app.logger import log_startup_warning
from utils.install_util import get_missing_requirements_message
from filelock import FileLock, Timeout
from comfy.cli_args import args
_DB_AVAILABLE = False
@ -15,7 +16,6 @@ try:
from alembic.runtime.migration import MigrationContext
from alembic.script import ScriptDirectory
from sqlalchemy import create_engine, event
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
_DB_AVAILABLE = True
@ -66,20 +66,22 @@ def get_db_path():
raise ValueError(f"Unsupported database URL '{url}'.")
def _acquire_exclusive_lock(engine, db_path):
"""Acquire an exclusive SQLite lock to prevent multi-process access.
_db_lock = None
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.
def _acquire_file_lock(db_path):
"""Acquire an OS-level file lock to prevent multi-process access.
Uses filelock for cross-platform support (macOS, Linux, Windows).
The OS automatically releases the lock when the process exits, even on crashes.
"""
conn = engine.connect()
global _db_lock
lock_path = db_path + ".lock"
_db_lock = FileLock(lock_path)
try:
conn.execute(sa.text("PRAGMA locking_mode=EXCLUSIVE"))
conn.execute(sa.text("BEGIN EXCLUSIVE"))
conn.execute(sa.text("COMMIT"))
except Exception:
_db_lock.acquire(timeout=0)
except Timeout:
raise RuntimeError(
f"Could not acquire exclusive lock on database '{db_path}'. "
f"Could not acquire lock on database '{db_path}'. "
"Another ComfyUI process may already be using it. "
"Use --database-url to specify a separate database file."
)
@ -132,11 +134,11 @@ def init_db():
logging.exception("Error upgrading database: ")
raise e
# Acquire an exclusive lock after migrations are complete.
# Acquire an OS-level file 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)
_acquire_file_lock(db_path)
global Session
Session = sessionmaker(bind=engine)

23
main.py
View File

@ -7,7 +7,6 @@ import folder_paths
import time
from comfy.cli_args import args, enables_dynamic_vram
from app.logger import setup_logger
from app.assets.api.routes import disable_assets_routes
from app.assets.seeder import asset_seeder
import itertools
import utils.extra_config
@ -16,6 +15,7 @@ import sys
from comfy_execution.progress import get_progress_state
from comfy_execution.utils import get_executing_context
from comfy_api import feature_flags
from app.database.db import init_db, dependencies_available
if __name__ == "__main__":
#NOTE: These do not do anything on core ComfyUI, they are for custom nodes.
@ -357,17 +357,30 @@ def cleanup_temp():
def setup_database():
try:
from app.database.db import init_db, dependencies_available
if dependencies_available():
init_db()
if args.enable_assets:
if asset_seeder.start(roots=("models", "input", "output"), prune_first=True, compute_hashes=True):
logging.info("Background asset scan initiated for models, input, output")
except Exception as e:
logging.error(f"Failed to initialize database. Please ensure you have installed the latest requirements. If the error persists, please report this as in future the database will be required: {e}")
if "database is locked" in str(e):
logging.error(
"Database is locked. Another ComfyUI process is already using this database.\n"
"To resolve this, specify a separate database file for this instance:\n"
" --database-url sqlite:///path/to/another.db"
)
sys.exit(1)
if args.enable_assets:
disable_assets_routes()
asset_seeder.disable()
logging.error(
f"Failed to initialize database: {e}\n"
"The --enable-assets flag requires a working database connection.\n"
"To resolve this, try one of the following:\n"
" 1. Install the latest requirements: pip install -r requirements.txt\n"
" 2. Specify an alternative database URL: --database-url sqlite:///path/to/your.db\n"
" 3. Use an in-memory database: --database-url sqlite:///:memory:"
)
sys.exit(1)
logging.error(f"Failed to initialize database. Please ensure you have installed the latest requirements. If the error persists, please report this as in future the database will be required: {e}")
def start_comfyui(asyncio_loop=None):

View File

@ -20,6 +20,7 @@ tqdm
psutil
alembic
SQLAlchemy
filelock
av>=14.2.0
comfy-kitchen>=0.2.7
comfy-aimdo>=0.2.4