mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
e14a084f0d
Installing a second LoRA Manager instance (standalone or a second ComfyUI install) that shares the settings directory puts two processes on the same cache databases. Three things made that unsafe. - The updater preserved cache/ and model_cache/ but not a legacy recipe_cache/ directory, so a portable install predating the cache/ move lost its recipe database on a git-based update. Add it to _PRESERVE_DIRS and to .gitignore. - Cache connections used the sqlite3 default 5s timeout, which a scanning instance can exceed, turning a concurrent write into "database is locked". Route every shared cache connection through connect_cache_db(), which raises the timeout to 30s and sets busy_timeout + synchronous=NORMAL to match the existing WAL mode. App-private databases (download queue, update history) are unchanged. - A full-table cache replace is a read-modify-write that SQLite cannot make atomic across processes, so two instances could interleave and one snapshot could overwrite the other. Guard the recipe and model save_cache paths with a cross-process advisory lock (flock on POSIX, msvcrt on Windows). Locking is best-effort: if it is unavailable the call proceeds and the SQLite busy timeout is the fallback. The lock file is a hidden sibling of the database and is deliberately never unlinked, so a second process cannot lock a fresh inode.
82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""Shared SQLite connection setup for LoRA Manager cache databases.
|
|
|
|
Cache databases live under the settings directory (``cache/model/<library>.sqlite``,
|
|
``cache/recipe/<library>.sqlite``, ``cache/fts/*.sqlite``). With portable mode or a
|
|
pinned ``LORA_MANAGER_SETTINGS_DIR`` off, that directory is shared by every ComfyUI
|
|
instance on the machine, so two processes can open the same cache file at once.
|
|
|
|
SQLite serializes writers, but the default ``timeout`` is 5 seconds: a second
|
|
instance that writes while the first is mid-transaction fails with "database is
|
|
locked". These settings make concurrent access wait instead of failing, and keep
|
|
the write path in WAL so readers are never blocked by a writer.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
from typing import Any
|
|
|
|
# How long a connection waits for a competing writer before raising.
|
|
CONCURRENT_TIMEOUT_SECONDS = 30.0
|
|
|
|
# PRAGMAs applied to every cache connection.
|
|
#
|
|
# ``busy_timeout`` mirrors the connection timeout so a busy database is retried
|
|
# inside SQLite rather than surfacing as an immediate error. ``synchronous=NORMAL``
|
|
# is the documented companion of WAL: still crash-safe, far fewer fsyncs.
|
|
_TUNING_PRAGMAS = (
|
|
"PRAGMA busy_timeout = 30000",
|
|
"PRAGMA synchronous = NORMAL",
|
|
)
|
|
|
|
|
|
def connect_cache_db(
|
|
path: str,
|
|
*,
|
|
readonly: bool = False,
|
|
uri: bool = False,
|
|
detect_types: int = 0,
|
|
row_factory: Any = None,
|
|
) -> sqlite3.Connection:
|
|
"""Open a cache database with multi-instance-friendly settings.
|
|
|
|
Args:
|
|
path: Database path, or a ``file:`` URI when *uri* is True.
|
|
readonly: Open through a read-only URI. Callers still pass the
|
|
plain path; the ``mode=ro`` suffix is added here. The
|
|
write-oriented tuning pragmas are skipped in that case so a
|
|
read-only connection never attempts to change the file.
|
|
uri: Treat *path* as a SQLite URI.
|
|
detect_types: Forwarded to :func:`sqlite3.connect`.
|
|
row_factory: Optional ``row_factory`` for the connection.
|
|
|
|
Returns:
|
|
A configured :class:`sqlite3.Connection`.
|
|
"""
|
|
if readonly:
|
|
if not uri and not path.startswith("file:"):
|
|
path = f"file:{path}?mode=ro"
|
|
uri = True
|
|
|
|
conn = sqlite3.connect(
|
|
path,
|
|
check_same_thread=False,
|
|
uri=uri,
|
|
detect_types=detect_types,
|
|
timeout=CONCURRENT_TIMEOUT_SECONDS,
|
|
)
|
|
if row_factory is not None:
|
|
conn.row_factory = row_factory
|
|
|
|
try:
|
|
for pragma in _TUNING_PRAGMAS:
|
|
# A read-only connection may reject write PRAGMAs; they are not
|
|
# needed there anyway.
|
|
conn.execute(pragma)
|
|
except sqlite3.Error:
|
|
# Tuning is best-effort: a connection that cannot set pragmas still
|
|
# works, just without the concurrency headroom.
|
|
pass
|
|
|
|
return conn
|