Background
PR #76 fixed one open(...).read() instance in reflect/scheduler.py by
wrapping it in with. Quick grep over the rest of the codebase finds
several similar patterns that rely on garbage collection to close the
handle:
ga.py:23 if os.path.exists(cr_header): tmp_file.write(open(cr_header, encoding='utf-8').read())
ga.py:387 old = open(path, 'r', encoding=\"utf-8\").read() if os.path.exists(path) else \"\"
ga.py:388 open(path, 'w', encoding=\"utf-8\").write(new + old)
agentmain.py:16 open(...).read() # tools_schema load
agentmain.py:24 open(mem_txt, 'w').write(...)
agentmain.py:28 open(...).write(open(...).read()) # nested, two handles
agentmain.py:236 open(...).<append> # log file
frontends/wechatapp.py:183 open(p, 'wb').write(pt)
For the launchable Streamlit/CLI flow these eventually get GC'd and the
process exits anyway, so the existing behavior is mostly fine. But
tgapp.py, wechatapp.py, fsapp.py etc. run as long-lived daemons,
and several of these patterns are inside per-message handlers. On
default Linux ulimit (1024 FDs) a busy bot will eventually trip
OSError: [Errno 24] Too many open files.
This may also be the root cause of #48 ("multiple calls then model
unresponsive, needs restart") — the symptom there matches FD
exhaustion: the agent stops responding mid-conversation and a restart
clears it.
Proposal
Single janitor-style PR that converts all of the above to
with open(...) as f:. Net line count is +0 (extra with line is
balanced by removing the chained call). Same byte intent, no behavior
change for the happy path.
Two questions before I send the PR:
-
Do you prefer a single janitor PR touching ~5 files, or split per
file? CONTRIBUTING.md asks for "small change radius" — I read this
as "don't ripple A → B → C", which the FD-hygiene change does not
do (each file is independent). But happy to split if you prefer.
-
There are also a few open(...).read() calls inside helper closures
that get called once per process startup (e.g. _load_mykeys in
llmcore.py). Should those stay as-is to keep the boot path
compact, or get the same with treatment for consistency?
Reference: PR #76 already established the with-context pattern as
acceptable in this codebase, so this is a follow-up applying the same
pattern uniformly.
Background
PR #76 fixed one
open(...).read()instance inreflect/scheduler.pybywrapping it in
with. Quick grep over the rest of the codebase findsseveral similar patterns that rely on garbage collection to close the
handle:
For the launchable Streamlit/CLI flow these eventually get GC'd and the
process exits anyway, so the existing behavior is mostly fine. But
tgapp.py,wechatapp.py,fsapp.pyetc. run as long-lived daemons,and several of these patterns are inside per-message handlers. On
default Linux ulimit (1024 FDs) a busy bot will eventually trip
OSError: [Errno 24] Too many open files.This may also be the root cause of #48 ("multiple calls then model
unresponsive, needs restart") — the symptom there matches FD
exhaustion: the agent stops responding mid-conversation and a restart
clears it.
Proposal
Single janitor-style PR that converts all of the above to
with open(...) as f:. Net line count is +0 (extrawithline isbalanced by removing the chained call). Same byte intent, no behavior
change for the happy path.
Two questions before I send the PR:
Do you prefer a single janitor PR touching ~5 files, or split per
file? CONTRIBUTING.md asks for "small change radius" — I read this
as "don't ripple A → B → C", which the FD-hygiene change does not
do (each file is independent). But happy to split if you prefer.
There are also a few
open(...).read()calls inside helper closuresthat get called once per process startup (e.g.
_load_mykeysinllmcore.py). Should those stay as-is to keep the boot pathcompact, or get the same
withtreatment for consistency?Reference: PR #76 already established the
with-context pattern asacceptable in this codebase, so this is a follow-up applying the same
pattern uniformly.