Files
hermes-agent/tests/posix_lock_probe.py
teknium1 35272ce28b fix(state): /proc/locks readers no longer lose entries to other processes' lock churn
/proc/locks is host-wide and the kernel serves it over several read() calls,
resuming by record index. When other processes add or drop locks between
those reads the table shifts and entries are skipped, so a lock that is
still held can read as gone.

- tests: the live-DB preview and header-probe lock tests read their own
  process's locks from /proc/locks and flaked on busy CI hosts (1 in 12 on
  a quiet box; 9 in 12 with six processes churning locks). A shared
  tests/posix_lock_probe.own_posix_locks() reads /proc/self/fdinfo instead,
  which lists this process's locks per open file in one read.
- hermes_state_lockowners: state_db_write_lock_holders() names the writer
  in the "state.db write lock unavailable" warning. One pass could skip the
  holder and log "no write-class lock held at the deadline". It now unions
  three passes; a skip rarely repeats.
2026-09-28 02:26:21 -07:00

32 lines
1.3 KiB
Python

"""This process's POSIX advisory locks on a file, read from ``/proc/self/fdinfo`` (Linux).
Not ``/proc/locks``: that table is host-wide and the kernel serves it over several ``read()``
calls, so lock churn in other processes (a parallel test run) shifts it mid-read and silently
skips entries — a lock this process still holds reads as cancelled. ``fdinfo`` lists this
process's locks per open file and is read in one call.
"""
import os
from pathlib import Path
def own_posix_locks(*paths) -> list[str]:
"""Sorted ``"POSIX ADVISORY <type> <pid> <dev>:<ino> <start> <end>"`` lines this process holds on *paths*."""
targets = set()
for path in paths:
try:
st = os.stat(path)
except OSError:
continue
targets.add((st.st_dev, st.st_ino))
found = set()
for fd in os.listdir("/proc/self/fd"):
try:
st = os.stat(f"/proc/self/fd/{fd}")
info = Path(f"/proc/self/fdinfo/{fd}").read_text(encoding="utf-8")
except OSError:
continue
if (st.st_dev, st.st_ino) in targets:
found.update(line.split(": ", 1)[1] for line in info.splitlines()
if line.startswith("lock:") and "POSIX ADVISORY" in line)
return sorted(found)