from __future__ import annotations import os from pathlib import Path from typing import BinaryIO class MiningProcessLockError(RuntimeError): """Another application process owns mining for this data directory.""" class MiningProcessLock: def __init__(self, data_dir: Path) -> None: self._path = Path(data_dir) / ".mining_process.lock" self._stream: BinaryIO | None = None def acquire(self) -> None: if self._stream is not None: return self._path.parent.mkdir(parents=True, exist_ok=True) stream = self._path.open("a+b") try: stream.seek(0, os.SEEK_END) if stream.tell() == 0: stream.write(b"0") stream.flush() os.set_inheritable(stream.fileno(), False) _try_lock_file(stream) except BaseException: stream.close() raise self._stream = stream def release(self) -> None: stream = self._stream if stream is None: return self._stream = None try: _unlock_file(stream) finally: stream.close() def _try_lock_file(stream: BinaryIO) -> None: if os.name == "nt": import msvcrt stream.seek(0) try: msvcrt.locking(stream.fileno(), msvcrt.LK_NBLCK, 1) except OSError as exc: raise MiningProcessLockError( "another application process already owns mining for this data directory" ) from exc return import fcntl try: fcntl.flock(stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError as exc: raise MiningProcessLockError( "another application process already owns mining for this data directory" ) from exc def _unlock_file(stream: BinaryIO) -> None: if os.name == "nt": import msvcrt stream.seek(0) msvcrt.locking(stream.fileno(), msvcrt.LK_UNLCK, 1) return import fcntl fcntl.flock(stream.fileno(), fcntl.LOCK_UN)