-
Notifications
You must be signed in to change notification settings - Fork 268
Description
Describe the bug
在调用 executeCommand 方法执行简单的 Shell 命令(如 cat)时,如果命令输出的内容稍多(超过系统管道缓冲区),执行过程会卡住直到触发超时,导致即使成功的命令也返回“TimeoutError”。
To Reproduce
Steps to reproduce the behavior:
- agent使用ShellCommonTool cat 一个大文件
- See error “TimeoutError: The command execution exceeded the timeout of ...”
Environment (please complete the following information):
- AgentScope-Java Version: 1.0.7
- Java Version: 17
- OS: 代码问题,平台无关
Additional context
问题描述:
在调用 executeCommand 方法执行简单的 Shell 命令(如 cat)时,如果命令输出的内容稍多(超过系统管道缓冲区),执行过程会卡住直到触发超时,导致即使成功的命令也返回“TimeoutError”。
受影响范围:
所有通过 executeCommand 执行且输出内容 > 系统管道缓冲区(通常为 4KB - 64KB)的命令。
环境信息:
相关类/方法: ShellCommandTool.executeCommand()
复现步骤:
准备一个文本文件 test.txt,内容大小约为 20KB(足以填满默认缓冲区)。
调用工具方法:executeCommand("cat test.txt", 10) (设置超时为 10 秒)。
观察日志或返回结果。
预期结果: 命令在毫秒级完成,返回文件内容。
实际结果: 命令阻塞约 300 秒,最终抛出/返回 TimeoutError,提示“exceeded timeout of 300 seconds”。
根本原因分析:
该问题由 Java Process 缓冲区死锁 引起。当前代码逻辑如下:
processBuilder.start() 启动子进程。
调用 process.waitFor(...) 阻塞等待进程结束。
进程结束后,调用 readStream(...) 读取输出。
死锁机制:
当子进程(如 cat)向标准输出写入数据时,操作系统使用固定大小的管道缓冲区。如果写入的数据填满了该缓冲区,子进程会被挂起,等待父进程读取数据腾出空间。
然而,父进程此时正阻塞在 waitFor() 中等待子进程结束。
子进程:等父进程读数据。
父进程:等子进程结束。
结果:互相等待 -> 死锁 -> 直到超时强制终止。
建议修复方案:
必须在调用 process.waitFor() 之前 或 同时,在独立线程中消耗 InputStream(标准输出)和 ErrorStream(标准错误流),以防止缓冲区填满。
具体代码修改建议:
在 process.start() 之后,立即启动两个后台线程分别读取 getInputStream() 和 getErrorStream()。
将读取到的内容暂存到 StringBuilder 或线程安全的容器中。
主线程调用 process.waitFor(...)。
waitFor 返回后,合并后台线程读取到的内容并返回。
参考伪代码:
java
// 1. 启动进程
process = processBuilder.start();
// 2. 启动线程异步读取流 (防止死锁)
Thread stdOutThread = new Thread(() -> readStream(process.getInputStream(), ...));
Thread stdErrThread = new Thread(() -> readStream(process.getErrorStream(), ...));
stdOutThread.start();
stdErrThread.start();
// 3. 等待进程结束
boolean completed = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
// 4. 等待读取线程完成并获取结果
stdOutThread.join();
stdErrThread.join();
// ...
附件:

Metadata
Metadata
Assignees
Labels
Type
Projects
Status