fix(aibox): registry timeout cleanup + reader thread finalization (F2/N6+N4)

N6: both streaming-forward timeout paths (deadline hit, join(5)
expiry) previously returned early and skipped the
tool.streaming_output / tool.output_queue cleanup, leaving the daemon
thread writing through the instance attribute — zombie output could
leak into the next session's queue once the attribute got reassigned.
Now unified through a timed_out flag with cleanup at the single exit;
stream_tool_timeout becomes an instance attribute (3600s default,
test-tunable).

N4: reader-thread finalization after timeout/grace break. First
implementation followed the audit suggestion (close stdout + join),
but PoC regression proved close() blocks: CPython stream objects hold
an internal lock, so closing while readline is blocked parks until
readline returns (PoC-1 regressed 3s -> 15.2s, exactly the
Start-Sleep duration). Corrected strategy: when reader already got
EOF, close stdout to release the pipe fd; when reader is still
blocked, only join(timeout=2) then give up (daemon-bounded, thread
ends naturally when the grandchild exits and EOF arrives), keeping
the main flow prompt. Audit report updated with this correction.

Tests: new tests/test_f2_fixes.py (5 cases), PoC regression 5/5
(PoC-1/4 back to ~5s), full suite 272 passed.
This commit is contained in:
qing committed 2026-08-17 22:47:37 +08:00
1 parent 6ef5d11d9d
commit 5bcf22f9a7
4 files changed
+221 -8

No files matched your search

+20
View File
@@ -159,6 +159,26 @@ class SubprocessCommandExecutor(CommandExecutor):
if output_queue:
output_queue.put(("line", line.rstrip('\n') + "\n"))
# N4 修复(审计 2026-08-18,二次修正):reader 线程收尾。
# 超时/截断/宽限到期 break 后,reader 可能仍阻塞在 readline
# (Windows shell=True 下孙进程持管道写端,永远等不到 EOF)。
# 注意:此时不能 close stdout——CPython 流对象内部有锁,
# readline 持锁阻塞时 close() 会一同挂起直到 readline 返回
# (实测命令超时被拖满整个 Start-Sleep 时长)。reader 为 daemon
# 线程,孙进程退出收到 EOF 后自然结束,不阻塞主流程;
# reader 已正常退出的场景才 close 显式释放管道 fd。
if reader_done.is_set():
try:
process.stdout.close()
except Exception:
pass
else:
reader.join(timeout=2)
if reader.is_alive():
self.logger.warning(
"reader 线程仍阻塞在管道读取(孙进程持写端),已放弃等待,"
"线程由 daemon 兜底、随管道 EOF 自然退出")
return ''.join(output_chunks), timeout_occurred, truncated
def execute(self, command: str, timeout: int = 30, work_dir: str = None, env: Dict = None, **kwargs) -> str: