Files
hermes-agent/pm/download_state.py
ethernet b728fdd627 refactor(pm): own the lock/atomic-write primitives in pm.filesystem
pm imported runtime_state's private helpers (_lock, _atomic_bytes, _bytes,
_digest) at a dozen sites while runtime_state imports pm.environments at
module top. The primitives are pm's: move them into the stdlib-only
pm.filesystem as lock_fd, durable_write_bytes, read_bytes_or_none and
file_digest, and repoint every pm caller.

runtime_state keeps the private names only as import aliases: it still
calls them through its own globals, and pre-PM updaters load them by these
names mid-swap (tests/compat/old_updater_surface.json).

Boot-subset test fixtures now copy pm/filesystem.py, since runtime_state
imports it at process boot; worker-injection tests patch the name
pm.publication now reads.
2026-09-24 14:08:03 -04:00

54 lines
1.8 KiB
Python

"""Cross-process ownership of resumable partials and their GC boundary."""
from __future__ import annotations
from contextlib import contextmanager
import os
from pathlib import Path
import time
from pm.filesystem import lock_fd
@contextmanager
def partial_lock(root: Path, key: str, *, cancelled=None, wait: bool = True):
# Lock inodes must survive release: removing one lets a new owner lock a
# different inode while an older waiter still holds the original handle.
locks = root / ".locks"
locks.mkdir(parents=True, exist_ok=True)
fd = os.open(locks / key, os.O_CREAT | os.O_RDWR, 0o600)
try:
while not lock_fd(fd, wait=False):
if not wait:
yield False
return
if cancelled is not None and cancelled():
yield False
return
time.sleep(0.05)
yield True
finally:
os.close(fd)
GC_GRACE_SECONDS = 6 * 60 * 60
def collect_partials(root: Path, *, grace_seconds: float = GC_GRACE_SECONDS) -> None:
"""Do not let a GC snapshot race a downloader acquiring ownership."""
if not root.is_dir():
return
keys = {path.stem for path in root.iterdir() if path.suffix in {".part", ".ranges"}}
now = time.time()
for key in sorted(keys):
with partial_lock(root, key, wait=False) as acquired:
if not acquired:
continue
pair = [root / f"{key}{suffix}" for suffix in (".part", ".ranges")]
try:
if any(path.exists() and grace_seconds > 0 and now - path.stat().st_mtime < grace_seconds for path in pair):
continue
for path in pair:
path.unlink(missing_ok=True)
except OSError:
continue # An inaccessible partial is not evidence it is unused.