Производительность и масштабирование, пункты 8–10 ревью (docs/changes/019): - кэш списка бакета и связей файлов с копиями (BACKUPS_CACHE_TTL, 60 с): sync_rows только при реальном чтении бакета; сброс после бэкапа и удаления, счётчик поколений против гонки; refresh=1 и «Обновить список»; - обработчики API/UI без await — обычные функции (пул потоков FastAPI), запуск задач остаётся async; запись статуса, задачи и sync_rows — через asyncio.to_thread; SQLite: WAL, synchronous=NORMAL, busy_timeout; - файловая блокировка <файл БД>.lock: второй процесс на той же БД не стартует; раздел «Ограничения» в README. Тесты: 24 из 24. Стенд (порт 8001): кэш 0,014 с против 0,138 с, второй uvicorn на боевой БД отклонён блокировкой, боевые данные не изменены. Ручная проверка UI пользователем на момент коммита не подтверждена. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
35 lines
1.6 KiB
Python
35 lines
1.6 KiB
Python
"""Блокировка файла БД: гарантирует, что с одной файловой БД работает не больше одного процесса
|
|
(семафор задач, _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
|