Conversation
… stop fd leak (NousResearch#81547) Two-part fix for the dashboard fd exhaustion reported in NousResearch#81547: 1. Raise RLIMIT_NOFILE soft limit on startup (before uvicorn binds). macOS defaults to 256 for LaunchAgent processes — too tight for the dashboard which opens 3 fds (db+wal+shm) per SessionDB per request across all profiles. After days of polling the soft limit exhausts and every os.listdir/open raises OSError [Errno 24]. The helper raises to the hard limit (or minimum 4096), matching the reporter's ulimit workaround. No-op on Windows (no resource module). 2. Replace bare Path.iterdir() with context-managed os.scandir() in four dashboard hot paths: _fallback_profile_dicts, file manager list, checkpoint listing, and plugin discovery. iterdir() returns a generator that holds an open directory fd until fully consumed; if an exception interrupts iteration the fd leaks. os.scandir() is an explicit context manager that guarantees close on exit, following the same idiom already used in /api/fs/list. Tests: 6 passed, 3 skipped (resource-module tests skip on Windows).
Collaborator
|
Merged via PR #83542 — your commit(s) were cherry-picked onto current main with your authorship preserved in git log (rebase merge). Thank you for the contribution! This follow-up PR completed the EMFILE hardening cluster after #83406: restart-path gateway orphan reap, Desktop-managed gateway termination on serve shutdown, SSH-spawn ulimit raise, and the dashboard iterdir→scandir fd-leak fixes. Everything was live-tested end-to-end on a real serve backend before merge, including a hostile unreadable-profile-dir fixture that surfaced (and fixed) a pre-existing /api/profiles 500 along the way. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Long-running
hermes dashboardexhausts its file descriptor soft limit and everyos.listdir/openstarts raisingOSError [Errno 24] Too many open files(#81547). The dashboard process stays alive (uvicorn swallows the exception) but every API endpoint that touchesPath.iterdir()oros.listdirsilently fails — remote clients see connection timeouts.Two root causes, two fixes:
1. Raise RLIMIT_NOFILE soft limit on startup
macOS defaults to 256 open files for LaunchAgent-managed processes. The dashboard opens 3 fds (db + wal + shm) per
SessionDBper request across all profiles, and the sidebar polls every few seconds. After a few days the soft limit exhausts._raise_fd_soft_limit()runs before uvicorn binds and raises the soft limit to the hard limit (or minimum 4096). This matches the reporter's workaround (ulimit -nin the plist) but runs in-process so it works for all launch methods. No-op on Windows (noresourcemodule) and when the limit is already adequate.2. Replace bare
Path.iterdir()with context-managedos.scandir()Path.iterdir()returns a generator that holds an open directory fd until fully consumed. If an exception interrupts iteration, the fd leaks. On the sidebar poll path (every few seconds), this accumulates over days.Fixed four hot paths:
_fallback_profile_dicts— profile listing (polled on every sidebar refresh)/api/fs/managed— directory listingos.scandir()is an explicit context manager (with os.scandir(...) as scan:) that guarantees the directory fd is closed on exit, following the same idiom already used in/api/fs/list.Closes #81547.
Changes
hermes_cli/web_server.py_raise_fd_soft_limit()helper (47 lines) — raisesRLIMIT_NOFILEsoft to hard/min 4096 before uvicorn binds. Called at top ofstart_server().hermes_cli/web_server.pyPath.iterdir()→os.scandir()context manager in_fallback_profile_dicts, file manager list, checkpoint listing, plugin discovery.DirEntry→Pathconversion at each call site.tests/hermes_cli/test_dashboard_fd_leak_81547.pyDesign Notes
_raise_fd_soft_limitsilently no-ops on Windows (noresourcemodule).os.scandirworks on all platforms.Test Plan
tests/hermes_cli/test_dashboard_fd_leak_81547.py— 6 passed, 3 skipped (resource-module tests skip on Windows)Path.iterdir()in the four hot paths (source-level assertion)_raise_fd_soft_limitcalled before uvicorn binds instart_server()os.scandirpattern in/api/fs/listunchanged — our changes follow the same idiomAdversarial 6-Check — PASS
web_server.py+ new test file ✅_raise_fd_soft_limit()runs beforeimport uvicorninstart_server()✅DirEntry→Pathconversions present at each call site ✅/api/fs/listscandir pattern unchanged; all converted paths preserve sort order and filtering ✅Path.iterdir()calls in the four fixed hot paths ✅