Repository navigation
[Bug][Windows] 工具调用永久卡死:runner 已 fork、IPC 已连上,但 start 请求丢失 —— WindowsJobRunner 无启动超时、pendingCalls 无看门狗(0.1.7-rc.2 → 0.2.1-alpha.2 均复现) #9515
Replies: 2 comments
|
Your forensic evidence is solid, and there is an in-tree half to it that explains why the wedge is survivable at all. Two independent reasons, both source-anchored. 1. A
using d = deadline(exec.signal, timeoutMs, TOOL_TIMEOUT)
exec.signal = d.signal
try {
const result = await next() // ← awaits the body UNCONDITIONALLY
if (timeoutOf(d.signal, TOOL_TIMEOUT) !== undefined) return toolTimeoutResult(timeoutMs)
return result
}It arms a deadline but never races it. Its own module doc states the design: "maps its own expiry to 2. It only arms when the tool declares a numeric budget — and none does. The same wrapper starts with So for a wedged call the guard is inert twice over: it was never armed, and even if it were, it does not race. 3. That is why your two proposed fixes are complementary, and the second one is plugin-reachable.
That bound does not need a core change — the extension point is documented. From the same note:
4. Your item 3 is observable from outside too. 5. Honest limit. Ending the hang does not make the child go away: a body that ignores its signal keeps running after its result has been synthesized, so it still needs reaping — your 3-minute zombie script is exactly the right companion. If instead the signal is honoured, aborting it lets the body win the race on its own terms ( Also: your "not sandbox-related, reproduces with no sandbox at all" point is worth keeping prominent — it separates this from #9463, whose hang does route through the sandbox path. I have this non-cooperative wrapper drafted as an out-of-tree plugin; I will post the package link here once it is published, since the failure mode as reported has no way to end itself today. |
|
A plugin now covers this seam — Following the analysis above, I packaged the fix as a mount rather than a patch, because the wedging phase is exactly what the public The gap, once more: What the mount does on that seam:
The code is deliberately not the body arm's Two limits worth naming, both asserted rather than described:
The mount also supplies the original half (the body budget for tools that declare no npm install @argszero/cordis-plugin-tool-deadline-guard- insert:
- id: tool-deadline-guard
name: '@argszero/cordis-plugin-tool-deadline-guard'Verified against the versions in this report rather than only its own build: the suite runs on One caveat for anyone who already tried 0.1.0 of this package: its peer range admitted 9 releases under npm's prerelease reading and 15 under pnpm's, the divergence being exactly Source and full notes: https://github.com/argszero/cordis-plugin-tool-deadline-guard |
Uh oh!
There was an error while loading. Please reload this page.
环境
@deepseek-ai/dsh:0.1.7-rc.2(9/28 起)与 0.2.1-alpha.2(当前,npm dist-tag alpha;latest=0.2.0-rc.2)dsh web模式(127.0.0.1:3080),文件策略 danger-full-access(不涉及沙箱 ACL 路径)症状
偶发某个工具调用永远不返回,GUI 一直显示执行中(最长一次 2 小时+)。4 天共 12 例,三个通道都中招:pwsh 命令、git 自动检查点(
git status --porcelain/git add --all)、@vscode/ripgrep 代码搜索。服务器主进程全程健康(其他会话正常推进),杀掉问题进程后卡住的会话 2 秒内自动恢复、零数据丢失(12/12 验证)。12 例台账:9/28 ×7(mvn构建、git add ×2、git status、pwsh、ripgrep ×2)、9/29 ×2、10/10 ×2、10/12 ×1;另有 2 例更严重的「派发丢失」变体(连 runner 都没 fork 出来,只能靠点「停止」触发 turn/end 强制清队列)。
诊断证据
runner.js子进程已 fork、IPC 已连接,但目标命令从未启动:0 个子进程、CPU 累计 0.02~0.08s(一例挂了 27 小时仍如此);某案例中命令重定向的日志文件根本没被创建。@deepseek-ai/dsh-subprocess-local/lib/runner.js(在 0.2.1-alpha.2 上逐行验证,L151/L177/L188startSeen)等待 IPC start 请求没有任何超时——全文件setTimeout出现 0 次,出路只有断连→finish(127)和目标退出两条。start 消息一丢 = 双方永久互等。sessionStats.pendingCalls(@deepseek-ai/dsh-session-stats/lib/index.js:129-152)只有tool/result和turn/end两条出路,全链路无任何看门狗/超时。Stop-Process杀掉僵尸 runner → 断连使 harness 合成 result → 该会话投影 mtime 立刻前进、pendingCalls 清空、会话继续,无需任何修复操作。dsh-subprocess-local/lib/runner.js;start 握手是 server↔runner 私有 IPC,插件无代码路径可达;git 自动检查点是纯核心行为;跨版本(0.1.7→0.2.1)、多次重启服务器均复现。期望行为
工具调用不应存在「永久挂起」状态。两处各补一个超时即可根治:
runner.js:IPC 通道建立后即计时,N 秒(如 10~15s)内未收到 start → 自杀并带结构化错误退出;dsh-session-stats:pendingCalls 加看门狗(可配置,如 5min),把丢失的子进程调用合成为错误tool/result,而不是让整个 turn 无限挂起。(可选第三条:harness↔runner IPC 加应用层心跳,检测半开连接。)
僵尸检测脚本(我用来回收 12 例的判据:runner 存活≥3min 且 0 子进程 且 CPU<1s)
相近帖(症状同族、机制不同,建议交叉链接)
All reactions