From 6e33c4985a0aab6e9740d7ad7ed4436e23322109 Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Wed, 4 Mar 2026 13:48:10 -0800 Subject: [PATCH] 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 --- app/database/db.py | 28 +++++++++++++++------------- main.py | 23 ++++++++++++++++++----- requirements.txt | 1 + 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/app/database/db.py b/app/database/db.py index bd1c2bd60..d397fb8c1 100644 --- a/app/database/db.py +++ b/app/database/db.py @@ -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) diff --git a/main.py b/main.py index 6bb8397c7..36e195986 100644 --- a/main.py +++ b/main.py @@ -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): diff --git a/requirements.txt b/requirements.txt index f5269c195..e16108208 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ tqdm psutil alembic SQLAlchemy +filelock av>=14.2.0 comfy-kitchen>=0.2.7 comfy-aimdo>=0.2.4