From 436a92ba1d1db09c016888389c79f141e78d84c2 Mon Sep 17 00:00:00 2001 From: host Date: Sat, 31 Oct 2026 12:55:13 +0000 Subject: [PATCH] fix: close bare open() in host.py and ResourceWarning in test_host_thread_cli - host.py: 7 one-shot reads switched to with open (run_health_check pidfile, daemon-status pid/log, daemon-stop pid, daemon old pid, interrupt poll state, parallel jobs); read content, encoding args and error semantics unchanged. - tests/test_host_thread_cli.py: test_old_archive_unchanged state read switched to with open; added a warnings-capture assertion scoped to the state read so a regression fails the test. - Intentionally untouched: daemonize log handle (daemon-lifetime dup2 target) and the two _load_config_file / init sites owned by PR #266. --- host.py | 22 ++++++++++++++-------- tests/test_host_thread_cli.py | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/host.py b/host.py index a6c3e08..5d82317 100644 --- a/host.py +++ b/host.py @@ -1120,7 +1120,8 @@ def run_health_check(state_path, cfg, codex_bin, codex_home, approval, checks["daemon_pidfile"] = pidfile if os.path.exists(pidfile): try: - pid = int(open(pidfile).read().strip()) + with open(pidfile) as f: + pid = int(f.read().strip()) os.kill(pid, 0) checks["daemon_alive"] = True checks["daemon_pid"] = pid @@ -2707,7 +2708,8 @@ def main(): pid = None if os.path.exists(pidfile): try: - pid = int(open(pidfile).read().strip()) + with open(pidfile) as f: + pid = int(f.read().strip()) os.kill(pid, 0) alive = True except Exception: @@ -2715,7 +2717,8 @@ def main(): tail = "" if os.path.exists(logfile): try: - tail = "\n".join(open(logfile).read().splitlines()[-5:]) + with open(logfile) as f: + tail = "\n".join(f.read().splitlines()[-5:]) except Exception: pass print(json.dumps({"alive": alive, "pid": pid, "pidfile": pidfile, @@ -2728,7 +2731,8 @@ def main(): print("daemon 未运行(无 pidfile)") return try: - pid = int(open(pidfile).read().strip()) + with open(pidfile) as f: + pid = int(f.read().strip()) except Exception: print("pidfile 损坏,直接删除") os.remove(pidfile) @@ -2758,7 +2762,8 @@ def main(): if not args.foreground: if os.path.exists(pidfile): try: - old_pid = int(open(pidfile).read().strip()) + with open(pidfile) as f: + old_pid = int(f.read().strip()) os.kill(old_pid, 0) print(f"daemon 已在运行:pid {old_pid},先 daemon-stop 再起") return @@ -3272,8 +3277,8 @@ def main(): for _ in range(10): time.sleep(1) try: - cur = json.load(open(args.state, - encoding="utf-8")) + with open(args.state, encoding="utf-8") as f: + cur = json.load(f) if (cur.get("tasks", {}).get(args.task, {}) .get("status") == "interrupted"): print(f"interrupt {args.task}: 属主已响应," @@ -3424,7 +3429,8 @@ def main(): last_turn_status=t.get("goal_last_turn_status"), last_turn_text=last_result.get("text", "") or "") elif args.cmd == "parallel": - jobs = json.load(open(args.jobs)) + with open(args.jobs) as f: + jobs = json.load(f) # 依赖 + 优先级:先拓扑排序,缺依赖/成环直接报错不跑; # 按波次派发 + 跑:依赖都 completed 的任务才进本波, # 依赖 failed/skipped 的任务标 skipped 不跑,避免在坏 diff --git a/tests/test_host_thread_cli.py b/tests/test_host_thread_cli.py index d196a52..7c2b6f6 100644 --- a/tests/test_host_thread_cli.py +++ b/tests/test_host_thread_cli.py @@ -1,9 +1,11 @@ +import gc import io import json import os import sys import tempfile import unittest +import warnings from contextlib import redirect_stdout from unittest import mock import host @@ -246,7 +248,19 @@ class ThreadCliTest(unittest.TestCase): code = e.code if isinstance(e.code, int) else 1 self.assertEqual(code, 0) self.assertIn("archived t1", out.getvalue()) - saved = json.loads(Path(state_path).read_text(encoding="utf-8")) + # 第 4 项回归:此处裸 open() 曾在 GC 时报 ResourceWarning; + # 只捕获本段读 state 的警告断言(_load_config_file 的 + # 警告属 #266 轨,不在此断言范围内)。 + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always", ResourceWarning) + with open(state_path, encoding="utf-8") as f: + saved = json.load(f) + gc.collect() + self.assertEqual( + [w for w in caught + if issubclass(w.category, ResourceWarning) + and "host-state.json" in str(w.message)], + []) self.assertEqual(saved["tasks"]["t1"]["status"], "archived") calls = self._calls_before_close(fake) self.assertEqual(calls, [("archive", "th-1")])