fix(gateway): raise RLIMIT_NOFILE soft limit at startup (#30230) - #30234
fix(gateway): raise RLIMIT_NOFILE soft limit at startup (#30230)#30234briandevans wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a Unix-only file descriptor (RLIMIT_NOFILE) soft-limit bump during gateway startup, along with focused tests that validate the behavior and try to prevent drift from the production implementation.
Changes:
- Introduce
_raise_fd_soft_limit()ingateway/run.pyand invoke it during module initialization. - Add a new test suite that validates limit bumping, capping behavior, and error swallowing.
- Add a “drift guard” test that pins key strings in
gateway/run.pyto keep the test replica aligned.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
gateway/run.py |
Adds and calls _raise_fd_soft_limit() to reduce EMFILE risk by bumping RLIMIT_NOFILE on Unix. |
tests/gateway/test_fd_soft_limit.py |
Adds tests for RLIMIT bump logic plus a lightweight production-source pin to reduce drift. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| class TestRaiseFdSoftLimit: | ||
| def test_bumps_from_256_to_4096_when_hard_is_infinity(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
|
|
||
| def test_caps_at_hard_when_hard_below_target(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
|
|
||
| def test_noop_when_soft_already_high(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
|
|
||
| def test_noop_when_soft_equals_hard_below_min(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
|
|
||
| def test_swallows_getrlimit_error(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
|
|
||
| def test_swallows_setrlimit_error(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
|
|
||
| def test_custom_min_soft_threshold(self): | ||
| fn = _load_raise_fd_soft_limit() | ||
| import resource |
| def _load_raise_fd_soft_limit(): | ||
| """Replicate the helper in an isolated module. | ||
|
|
||
| gateway/run.py has heavy imports; tests/gateway/test_ssl_certs.py uses | ||
| the same pattern. The body below must stay in sync with the production | ||
| function in gateway/run.py. | ||
| """ | ||
| code = textwrap.dedent("""\ | ||
| def _raise_fd_soft_limit(min_soft=4096): | ||
| try: | ||
| import resource | ||
| except ImportError: | ||
| return | ||
| try: | ||
| soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) | ||
| except (OSError, ValueError): | ||
| return | ||
| if soft >= min_soft: | ||
| return | ||
| target = min_soft if hard == resource.RLIM_INFINITY else min(min_soft, hard) | ||
| if target <= soft: | ||
| return | ||
| try: | ||
| resource.setrlimit(resource.RLIMIT_NOFILE, (target, hard)) | ||
| except (OSError, ValueError): | ||
| pass | ||
| """) |
| text = src.read_text() | ||
| assert "def _raise_fd_soft_limit(" in text | ||
| assert "RLIMIT_NOFILE" in text | ||
| assert "RLIM_INFINITY" in text | ||
| # Helper is wired into module init right after _ensure_ssl_certs(). | ||
| assert "_raise_fd_soft_limit()" in text |
| @@ -513,6 +513,41 @@ def _ensure_ssl_certs() -> None: | |||
| os.environ["SSL_CERT_FILE"] = candidate | |||
| return | |||
|
|
|||
41b2d6a to
b9e1a51
Compare
CI Flake Found & Fix AvailableCI self-heal detected a flake in the test suite on run 26384541008: FailureRoot CauseThe test internally budgets ~50s (5s subprocess discovery + 15s worker-thread join + 30s process-group-exit poll) but the suite default FixAdd Commit: # To apply:
git fetch https://github.com/talwayh1/hermes-agent.git fix/gateway-raise-fd-soft-limit-30230
git cherry-pick cd06767b9 |
b9e1a51 to
bde0826
Compare
…#30230) macOS ships a default RLIMIT_NOFILE soft limit of 256. Hermes gateways with multiple MCP subprocesses + per-profile instances routinely exceed this and crash session save / kanban dispatch with OSError [Errno 24]. Bump the soft limit toward 4096 (capped at the hard limit) at module init alongside _ensure_ssl_certs. Windows / sandboxed environments gracefully no-op. This is the smallest mitigation that addresses the root cap; it complements the per-shutdown auxiliary-client reap landed in NousResearch#14210, which only delays the symptom. Tests pin the in-test replica against the production source so the helper can't silently drift.
bde0826 to
3c085f1
Compare
|
Closing to focus the queue on security/file-safety work where civilian merges are landing. Happy to reopen if maintainers want this picked up. |
What does this PR do?
macOS ships a default
RLIMIT_NOFILEsoft limit of 256. Hermes gateways with multiple MCP subprocesses + per-profile instances routinely exceed this and crash session save / kanban dispatch withOSError: [Errno 24] Too many open files: '.sessions_*.tmp'.This raises the soft limit toward 4096 (capped at the hard limit) at module init, right next to
_ensure_ssl_certs(). Windows and sandboxed environments gracefully no-op. It is the smallest mitigation that addresses the root cap; the per-shutdown auxiliary-client reap landed in #14210 only delays the symptom, it doesn't fix the cap.Related Issue
Fixes #30230
Type of Change
Changes Made
gateway/run.py— add_raise_fd_soft_limit(min_soft=4096)helper and call it at module init right after_ensure_ssl_certs(). Bumps towardmin(min_soft, hard); no-ops when soft is already above target; swallowsOSError/ValueErrorso sandboxed envs (and Windows, which has noresourcemodule) degrade cleanly.tests/gateway/test_fd_soft_limit.py— 8 cases covering: bump-from-256 with infinite hard, cap-at-hard when hard < target, no-op when soft already high, no-op when soft == hard == below target,getrlimitfailure swallowed,setrlimitfailure swallowed, custommin_softoverride, and a source-anchor test that pins the production keywords so the replica can't silently drift.How to Test
uv run --with pytest --with pytest-xdist --with pytest-asyncio --with pytest-timeout python3 -m pytest tests/gateway/test_fd_soft_limit.py tests/gateway/test_ssl_certs.py tests/gateway/test_runner_startup_failures.py tests/gateway/test_allowlist_startup_check.py -vpython3 -c "import resource; r=resource.getrlimit(resource.RLIMIT_NOFILE); print(r); import gateway.run; print(resource.getrlimit(resource.RLIMIT_NOFILE))"shows the soft limit rising from 256 → 4096 after import.Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) — N/A (docstring on_raise_fd_soft_limitis the relevant surface)cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Aresourcemodule; helper catchesImportErrorand returns early. Sandboxed Linux containers that forbidsetrlimit(seccomp, restrictive cgroups) catchOSError/ValueError.Screenshots / Logs
Before (macOS, fresh gateway process):
After: