Files
hermes-agent/tests
joaomarcos 0e9b57d310 fix(state): cap read connections per PROCESS and yield when the fd table is tight
Follow-up to the per-file budget in the previous commit, which closed the
scaling axis it measured and left three others open.

* A per-file ceiling still lets the cost grow with the PROFILE count: a
  multiplexed gateway serves N profiles from one process and each has its own
  state.db, so `_READ_POOL_MAX` bounded each file while the process total went
  unbounded — the per-instance bug one level out. `_READ_POOL_PROCESS_MAX`
  (three files' worth) now bounds the process, and a miss reclaims an idle
  connection from ANY path before degrading: a profile quiet for an hour must
  not hold descriptors the profile being served right now needs.

* Hermes's SQLite descriptors are only ever a share of the fd table. In #98573
  the ~20 state.db handles were not the whole 256 — they were the share that
  pushed httpx sockets and terminal subprocess pipes over, and the EMFILE
  surfaced in tools/terminal_tool.py rather than here. New read connections are
  now refused when the process is within `_FD_HEADROOM_RESERVE` of its soft
  RLIMIT_NOFILE, measured from /proc/self/fd or /dev/fd and cached briefly. The
  guard fails OPEN where it cannot measure (Windows has neither the fd
  directory nor RLIMIT_NOFILE, and a CRT limit in the thousands) and CLOSED on
  evidence — including a probe that could not get a descriptor of its own.
  `_read_open_denied_fd_headroom` makes it diagnosable from a running process.

* Writer connections cannot be rationed the way read connections can: a
  SessionDB without one cannot write. Their only real bound is not opening
  redundant handles, so a process that accumulates more than
  `_HANDLES_PER_PATH_WARN` handles on one file now says so once, and the next
  duplicate is visible before it is an incident instead of inferred from an
  lsof after one.

`_READ_POOL_MAX` itself is deliberately unchanged at 8. Retuning that constant
is #98585's subject; with a process ceiling above it and the headroom guard
in front of it, the value is no longer the binding constraint.

Refs #98573

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 11:23:48 -07:00
..