Replies: 4 comments
|
我在当前 环境:
使用 Discussion 中的最小复现命令,结果稳定为退出码 这补充确认了该故障在 Node 26 上同样存在,并且 spill 创建失败仍会逃出 stream 回调、终止宿主进程。仓库首次基线 鉴于当前贡献指南暂不接受外部 PR,我没有创建分支或 PR;如果维护者希望外部协助,我可以按这里的验收条件准备最小 RED/GREEN 修复。 |
|
补充一个真实生产环境触发案例(Windows + dsh web GUI): 环境:Windows 11(2026-08-14 重启过)、Node.js v22.19.0、 实际触发路径:不是磁盘写满,而是 Windows 存储感知(Storage Sense)在登录后自动清理 证据时间线(Windows 事件日志 + 注册表
补充观点:在 Windows 上"临时目录被外部删除"不是罕见场景(存储感知、磁盘清理 SilentCleanup、各类第三方清理工具都会做),且发生在进程完全无感知的情况下;这次崩溃的是 GUI 主进程,连带当前会话和其他并发任务一起中断。按本 Discussion 的 Suggested fix 降级(保留受 maxBytes 限制的内存尾部、停用 spill、不发布不完整的 spillPath)即可避免宿主退出。另外建议修复时顺带考虑: 本地规避办法(治标):设置 → 系统 → 存储 → 存储感知,关闭"删除我的应用未使用的临时文件"。 |
|
第三个 Windows 复现确认 + 完整候选补丁。 复现:Windows + Node v22.23.2 + 补丁要点(按本讨论的 Suggested fix 与验收条件):
按贡献指南不直接提交 PR。完整 patch(可直接 0001-fix-subprocess-local-contain-spill-I-O-failures-and-recover-the-spill-directory.patch |
|
subprocess spill I/O 失败直接终止整个进程——一个子进程的输出溢出把主进程带崩,这确实该修(spill 失败应该降级成丢弃而不是未捕获异常)。 长任务/大输出的稳定性问题第 6 章有相关讨论:https://github.com/Electricitysheep/dsh-handbook/blob/main/docs/06-advanced.md |
Uh oh!
There was an error while loading. Please reload this page.
Summary
本地 subprocess 的收集输出超过内存上限后会写入 spill 文件。spill 文件的创建或写入一旦失败,异常会逃出 stdout/stderr 的 EventEmitter
data回调,成为未捕获异常并终止整个 Harness 进程。Checked commit: 47f9438
Expected behavior
spill 是完整输出的附加保存路径。文件系统不可用时,单次 subprocess 至少应保留受
maxBytes限制的内存尾部并正常结算;spill 失败不应使宿主进程退出,也不应返回指向不完整文件的spillPath。Actual behavior
OutputCollector.spillAll()直接调用openSync()和writeSync(),调用方的 streamdatalistener 没有异常隔离:当临时目录失效、磁盘写满或文件系统返回 EIO 时,
openSync或writeSync的错误会成为宿主级未捕获异常,而不是 subprocess 结果或受控的 spill 降级。Minimal reproduction
在仓库根目录运行:
node --import tsx/esm --input-type=module -e "import { spawnSubprocess } from \"./packages/subprocess/subprocess-local/src/spawn.ts\"; const handle = spawnSubprocess({ argv: [\"/bin/sh\", \"-c\", \"printf 12345\"], cwd: process.cwd(), stdio: { stdin: \"ignore\", stdout: { maxBytes: 1, spill: { maxBytes: 1000 } }, stderr: { maxBytes: 1024 } }, graceMs: 100 }, { spillDir: \"/dev/null\" }); await handle.done; console.log(\"survived\")"stdout.maxBytes = 1强制 5 字节输出进入 spill;/dev/null是文件而不是目录,因此首次创建 spill 文件稳定返回ENOTDIR。在 macOS、Node.js v24.14.0 上,实际退出码为
1,survived没有输出,堆栈为:Impact
任意能触发 spill 的长输出命令,都可能在 ENOSPC、EIO、临时目录被删除或权限变化时终止整个 Harness。当前会话、其他并发任务和正常 teardown 会一起中断,子进程树也可能失去 Harness 的收尾。
Suggested fix
在
OutputCollector内隔离 spill 文件操作。首次 I/O 失败后永久停用该 stream 的 spill,继续维护受maxBytes限制的内存尾部;如果文件已经打开,则 best-effort 关闭文件描述符并删除不完整文件,且不发布该路径。Acceptance criteria
openSync、补写已有 chunks 或后续writeSync失败时,异常不逃出 stdout/stderr 的data回调,也不触发宿主uncaughtException或非零退出。await handle.done正常完成,保留子进程退出码0;stdout 返回尾部5、truncated: true,且没有spillPath。maxBytes限制。All reactions