Skip to content

fix: close file handles via context manager in remaining call sites#98

Open
shaun0927 wants to merge 1 commit intolsdefine:mainfrom
shaun0927:fix/fd-leaks-with-block
Open

fix: close file handles via context manager in remaining call sites#98
shaun0927 wants to merge 1 commit intolsdefine:mainfrom
shaun0927:fix/fd-leaks-with-block

Conversation

@shaun0927
Copy link
Copy Markdown
Contributor

Closes #96.

Problem

PR #76 wrapped the scheduler's open(...).read() in a context manager.
Several similar patterns elsewhere still rely on garbage collection to
release the underlying file descriptor. For long-running daemons
(tgapp.py, wechatapp.py, fsapp.py, etc.) the FD eventually leaks
to the per-process limit and the symptom shown in #48 ("multiple
calls then model unresponsive, needs restart") becomes hard to
diagnose because the actual EMFILE error surfaces in some unrelated
later open.

The recent refactor 47f106c also added a new unmanaged read at
agentmain.py:105 (/session.<key> = <file>), which would have grown
the leak surface.

Fix

Convert the remaining unmanaged open(...).<read|write>(...) patterns
to with open(...) as f: blocks. Same modes, encodings, bytes; only the
release timing changes.

File Lines (pre-patch) What
ga.py 23 code_run header copy
ga.py 387, 388 prepend mode in do_file_write
ga.py 424 _check_plan_completion read
agentmain.py 16 tools_schema load
agentmain.py 24 global_mem.txt seed
agentmain.py 27-28 global_mem_insight.txt seed (nested open().write(open().read()) — 2 leaks)
agentmain.py 33 tmwebdriver cdp_bridge config seed
agentmain.py 105 /session.<key>=<file> (added by 47f106c)
agentmain.py 242 reflect log append
frontends/wechatapp.py 183 decrypted media write to temp

Verification

$ python -m py_compile ga.py agentmain.py frontends/wechatapp.py
$ grep -nE 'open\\([^)]*\\)\\s*\\.(read|write)\\(' ga.py agentmain.py frontends/wechatapp.py | grep -v 'with '
(empty — all sites now use a context manager)

Scope choices

  • Single janitor PR rather than splitting per file. The change is
    the same idiomatic conversion at every site, and reviewing them in
    one place makes it easy to confirm no behavior change.
  • subprocess.Popen(stdout=open(...), stderr=open(...)) at
    agentmain.py:187-188 is intentionally not changed — those file
    objects are owned by the subprocess for its lifetime and closing them
    in the parent breaks the redirection.
  • if sys.stdout is None: sys.stdout = open(os.devnull, ...)
    at lines 3 and 5 is also unchanged because sys.stdout keeps the
    reference alive for the entire process.
  • open(_logf, 'a', ...) at the wechatapp __main__ block is
    unchanged for the same reason — the file is bound to sys.stdout
    for the lifetime of the daemon.

Net diff: +21 / -11 lines, no functional change.

Closes lsdefine#96.

PR lsdefine#76 already converted the scheduler.py read site to a context
manager. Apply the same treatment to the remaining unmanaged
open() calls that this audit found across ga.py, agentmain.py,
and frontends/wechatapp.py.

Sites updated:

  ga.py
    - 23  : code_run header copy into the temp script
    - 387 : prepend-mode read of existing file in do_file_write
    - 388 : prepend-mode write of new+old back to disk
    - 424 : _check_plan_completion read of plan.md
  agentmain.py
    - 16  : tools_schema*.json load
    - 24  : global_mem.txt initial seed
    - 27-28: global_mem_insight.txt initial seed (was nested
            open(...).write(open(...).read()), so two leaks)
    - 33  : tmwebdriver cdp_bridge config.js seed
    - 105 : /session.<key> = <file> case (refactor 47f106c
            kept the unmanaged read)
    - 242 : reflect log append
  frontends/wechatapp.py
    - 183 : decrypted media bytes write to temp dir

These match the pattern in lsdefine#48 ("multiple calls then model
unresponsive, needs restart") for long-running adapters where
GC of unreferenced file objects is delayed and FD limits matter.
The launcher / startup-only sites are still affected because
the same handler is reused in reflect-loop code paths.

No behavior change: all reads/writes use the same modes,
encodings, and bytes; only the file handle release timing changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Several open() calls without context manager — possible FD leak in long-running bot daemons

1 participant