48 lines
1.5 KiB
Python
48 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import fcntl
|
|
except ImportError: # pragma: no cover - exercised by Windows validation
|
|
fcntl = None
|
|
import msvcrt
|
|
|
|
|
|
class MaintenanceLock:
|
|
"""Process fence used only by fixture and later approved activation executors."""
|
|
|
|
def __init__(self, path: Path) -> None:
|
|
self.path = path
|
|
self._handle = None
|
|
|
|
def __enter__(self) -> "MaintenanceLock":
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
self._handle = self.path.open("a+")
|
|
if fcntl is not None:
|
|
try:
|
|
fcntl.flock(self._handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except BlockingIOError:
|
|
self._handle.close()
|
|
self._handle = None
|
|
raise
|
|
else:
|
|
self._handle.seek(0)
|
|
try:
|
|
msvcrt.locking(self._handle.fileno(), msvcrt.LK_NBLCK, 1)
|
|
except OSError as exc:
|
|
self._handle.close()
|
|
self._handle = None
|
|
raise BlockingIOError("maintenance lock held") from exc
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc, tb) -> None:
|
|
if self._handle is not None:
|
|
if fcntl is not None:
|
|
fcntl.flock(self._handle.fileno(), fcntl.LOCK_UN)
|
|
else:
|
|
self._handle.seek(0)
|
|
msvcrt.locking(self._handle.fileno(), msvcrt.LK_UNLCK, 1)
|
|
self._handle.close()
|
|
self._handle = None
|