"""Блокировка файла БД: гарантирует, что с одной файловой БД работает не больше одного процесса (семафор задач, _sync_lock, кэш бакета и счётчики неудачных паролей — состояние процесса, не БД). Берётся в lifespan до init_db, снимается при остановке.""" import fcntl from pathlib import Path _lock_file = None # открытый дескриптор блокировки текущего процесса (None — не бралась) def acquire(database_url: str) -> None: global _lock_file if not (database_url.startswith("sqlite:///") and ":memory:" not in database_url): return # :memory: и не-SQLite URL — блокировка не нужна db_path = Path(database_url.removeprefix("sqlite:///")) db_path.parent.mkdir(parents=True, exist_ok=True) lock_path = db_path.with_name(db_path.name + ".lock") f = open(lock_path, "w") try: fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: f.close() raise RuntimeError( f"БД {db_path} уже используется другим процессом ros_control: " "поддерживается только один процесс (--workers 1)" ) from None _lock_file = f def release() -> None: global _lock_file if _lock_file is not None: fcntl.flock(_lock_file, fcntl.LOCK_UN) _lock_file.close() _lock_file = None