Skip to content

liveness

liveness #

Which processes are alive: each holds a flock the kernel drops on any kind of death.

Liveness dataclass #

This process's hold on a scratch: a locked file named by its pid and a boot nonce.

Source code in capturegraph-lib/capturegraph/recipes/scratch/store/liveness.py
@dataclass(slots=True)
class Liveness:
    """This process's hold on a scratch: a locked file named by its pid and a boot nonce."""

    pid: int
    boot: int
    path: Path
    _handle: IO[bytes] = field(repr=False)

    @classmethod
    def acquire(cls, locks_dir: Path) -> Liveness:
        """Create and lock a fresh liveness file under ``locks_dir``."""
        global _nonce_salt
        _nonce_salt += 1
        pid = os.getpid()
        boot = (time.time_ns() ^ (pid << 32) ^ (_nonce_salt << 16)) & 0xFFFF_FFFF_FFFF_FFFF
        locks_dir.mkdir(parents=True, exist_ok=True)
        path = locks_dir / owner_name(pid, boot)
        handle = open(path, "wb")
        fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
        return cls(pid, boot, path, handle)

    @property
    def name(self) -> str:
        """This owner's name, as used for its lock file and live directory."""
        return owner_name(self.pid, self.boot)

    def release(self) -> None:
        """Drop the lock and remove the file."""
        self._handle.close()
        self.path.unlink(missing_ok=True)

name property #

This owner's name, as used for its lock file and live directory.

acquire(locks_dir) classmethod #

Create and lock a fresh liveness file under locks_dir.

Source code in capturegraph-lib/capturegraph/recipes/scratch/store/liveness.py
@classmethod
def acquire(cls, locks_dir: Path) -> Liveness:
    """Create and lock a fresh liveness file under ``locks_dir``."""
    global _nonce_salt
    _nonce_salt += 1
    pid = os.getpid()
    boot = (time.time_ns() ^ (pid << 32) ^ (_nonce_salt << 16)) & 0xFFFF_FFFF_FFFF_FFFF
    locks_dir.mkdir(parents=True, exist_ok=True)
    path = locks_dir / owner_name(pid, boot)
    handle = open(path, "wb")
    fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
    return cls(pid, boot, path, handle)

release() #

Drop the lock and remove the file.

Source code in capturegraph-lib/capturegraph/recipes/scratch/store/liveness.py
def release(self) -> None:
    """Drop the lock and remove the file."""
    self._handle.close()
    self.path.unlink(missing_ok=True)

lock_is_free(path) #

Whether nobody holds the lock file at path (an absent file counts as free).

Source code in capturegraph-lib/capturegraph/recipes/scratch/store/liveness.py
def lock_is_free(path: Path) -> bool:
    """Whether nobody holds the lock file at ``path`` (an absent file counts as free)."""
    try:
        handle = open(path, "rb")
    except FileNotFoundError:
        return True
    with handle:
        try:
            fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except OSError:
            return False
        return True

owner_alive(locks_dir, pid, boot) #

Whether the owner (pid, boot) still runs and still holds its liveness lock.

Source code in capturegraph-lib/capturegraph/recipes/scratch/store/liveness.py
def owner_alive(locks_dir: Path, pid: int, boot: int) -> bool:
    """Whether the owner ``(pid, boot)`` still runs and still holds its liveness lock."""
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        pass
    return not lock_is_free(locks_dir / owner_name(pid, boot))

owner_name(pid, boot) #

The lock-file and live-directory name of the owner (pid, boot).

Source code in capturegraph-lib/capturegraph/recipes/scratch/store/liveness.py
def owner_name(pid: int, boot: int) -> str:
    """The lock-file and live-directory name of the owner ``(pid, boot)``."""
    return f"{pid:x}-{boot:x}"