"""Exclusive process ownership for the client state database.""" from __future__ import annotations import fcntl import os from pathlib import Path from typing import IO class DatabaseLockedError(RuntimeError): pass class DatabaseLease: def __init__(self, database: Path): self.path = Path(f"{database}.lock") self._file: IO[str] | None = None def acquire(self) -> None: if self._file is not None: return self.path.parent.mkdir(parents=True, exist_ok=True) lock_file = self.path.open("a+", encoding="utf-8") os.chmod(self.path, 0o600) try: fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError as exc: lock_file.close() raise DatabaseLockedError( "client database is owned by a running process" ) from exc self._file = lock_file def release(self) -> None: if self._file is None: return try: fcntl.flock(self._file.fileno(), fcntl.LOCK_UN) finally: self._file.close() self._file = None def __enter__(self) -> "DatabaseLease": self.acquire() return self def __exit__(self, *_: object) -> None: self.release()