This commit was merged in pull request #842.
This commit is contained in:
105
packages/mosaic/framework/tools/lease-broker/lease_generation.py
Normal file
105
packages/mosaic/framework/tools/lease-broker/lease_generation.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Private monotonic runtime-generation state shared by runtime hook processes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fcntl
|
||||
import os
|
||||
import stat
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
MAX_GENERATION: Final = (1 << 63) - 1
|
||||
MAX_GENERATION_BYTES: Final = 32
|
||||
|
||||
|
||||
def parse_generation(value: object) -> int:
|
||||
if not isinstance(value, str) or not value.isascii() or not value.isdigit():
|
||||
raise ValueError("invalid runtime generation")
|
||||
generation = int(value)
|
||||
if generation < 0 or generation > MAX_GENERATION:
|
||||
raise ValueError("invalid runtime generation")
|
||||
return generation
|
||||
|
||||
|
||||
def _validate_descriptor(descriptor: int) -> None:
|
||||
metadata = os.fstat(descriptor)
|
||||
if not stat.S_ISREG(metadata.st_mode):
|
||||
raise ValueError("runtime generation state is not a regular file")
|
||||
if metadata.st_uid != os.geteuid():
|
||||
raise ValueError("runtime generation state has the wrong owner")
|
||||
if stat.S_IMODE(metadata.st_mode) & 0o077:
|
||||
raise ValueError("runtime generation state permissions are not private")
|
||||
if metadata.st_size > MAX_GENERATION_BYTES:
|
||||
raise ValueError("runtime generation state is oversized")
|
||||
|
||||
|
||||
def _read_descriptor(descriptor: int) -> int:
|
||||
os.lseek(descriptor, 0, os.SEEK_SET)
|
||||
raw = os.read(descriptor, MAX_GENERATION_BYTES + 1)
|
||||
if len(raw) > MAX_GENERATION_BYTES:
|
||||
raise ValueError("runtime generation state is oversized")
|
||||
try:
|
||||
return parse_generation(raw.decode("ascii").strip())
|
||||
except UnicodeDecodeError as exc:
|
||||
raise ValueError("invalid runtime generation state") from exc
|
||||
|
||||
|
||||
def _write_descriptor(descriptor: int, generation: int) -> None:
|
||||
payload = f"{generation}\n".encode("ascii")
|
||||
os.lseek(descriptor, 0, os.SEEK_SET)
|
||||
os.ftruncate(descriptor, 0)
|
||||
remaining = memoryview(payload)
|
||||
while remaining:
|
||||
written = os.write(descriptor, remaining)
|
||||
if written <= 0:
|
||||
raise OSError("runtime generation write made no progress")
|
||||
remaining = remaining[written:]
|
||||
os.fsync(descriptor)
|
||||
|
||||
|
||||
def initialize_runtime_generation(path: Path, generation: int) -> None:
|
||||
if generation < 0 or generation > MAX_GENERATION:
|
||||
raise ValueError("invalid runtime generation")
|
||||
descriptor = os.open(
|
||||
path,
|
||||
os.O_RDWR | os.O_CREAT | os.O_TRUNC | os.O_CLOEXEC | os.O_NOFOLLOW,
|
||||
0o600,
|
||||
)
|
||||
try:
|
||||
os.fchmod(descriptor, 0o600)
|
||||
_validate_descriptor(descriptor)
|
||||
_write_descriptor(descriptor, generation)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
|
||||
|
||||
def read_runtime_generation(environ: Mapping[str, str]) -> int:
|
||||
state_path = environ.get("MOSAIC_LEASE_GENERATION_FILE")
|
||||
if not state_path:
|
||||
return parse_generation(environ["MOSAIC_RUNTIME_GENERATION"])
|
||||
descriptor = os.open(state_path, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW)
|
||||
try:
|
||||
_validate_descriptor(descriptor)
|
||||
return _read_descriptor(descriptor)
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
|
||||
|
||||
def bump_runtime_generation(environ: Mapping[str, str]) -> int:
|
||||
state_path = environ.get("MOSAIC_LEASE_GENERATION_FILE")
|
||||
if not state_path:
|
||||
raise ValueError("runtime generation file is required for same-PID rollover")
|
||||
descriptor = os.open(state_path, os.O_RDWR | os.O_CLOEXEC | os.O_NOFOLLOW)
|
||||
try:
|
||||
fcntl.flock(descriptor, fcntl.LOCK_EX)
|
||||
_validate_descriptor(descriptor)
|
||||
current = _read_descriptor(descriptor)
|
||||
if current >= MAX_GENERATION:
|
||||
raise ValueError("runtime generation exhausted")
|
||||
generation = current + 1
|
||||
_write_descriptor(descriptor, generation)
|
||||
return generation
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
Reference in New Issue
Block a user