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:
1 parent
6ef5d11d9d
commit
5bcf22f9a7
4 files changed
+221
-8
No files matched your search
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user