问题
bounded() 在超时后先发送进程组 SIGTERM,再安排三秒后的 SIGKILL,但是随即对强制终止定时器调用 unref()。如果直接子进程已经退出、同组后代仍在运行,Node 包装进程会提前退出,SIGKILL 回调不再执行。
这不是只存在于旧版本的问题:已核查 main 和当前重写 PR #1,两处仍然使用相同逻辑。
位置:
关键代码:
try { process.kill(-child.pid, 'SIGTERM'); } catch {}
const hard = setTimeout(() => {
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
}, 3000);
hard.unref();
// child exit 时清除 heartbeat 和 timeout,不再有引用维持包装进程。
已执行的离线复现
环境:Linux,Node v22.16.0。仅执行源码中原样提取的 JavaScript 包装函数,不是整套安装器兼容性测试,没有 npm 安装和外部网络请求。
复现步骤:
- 使用原
bounded() 的 JS,命令超时设为 1 秒。
- 它启动一个模拟构建主进程,主进程再启动同进程组的后代;后代捕获并忽略 SIGTERM,写出 PID 后等待。主进程对 SIGTERM 使用默认退出行为。
- 等待包装进程退出,再在启动后 4.5 秒检查后代的
/proc/<pid>/stat,排除僵尸状态。
- 对照组只把
hard.unref() 换成 void hard,其余逻辑不变。
- 每轮 finally 显式 SIGKILL 测试创建的进程组,避免留下模拟进程。
实际输出:
original:
exit=124, wrapper_elapsed=1.171s
worker_running_after_4.5s=True, worker_state=S
referenced-timer-control:
exit=124, wrapper_elapsed=4.154s
worker_running_after_4.5s=False, worker_state=Z
因此原代码虽然报告了超时,却没有完成承诺的进程组强制清理。对照修改仅用于确认根因,不表示已经提供了完整生产修复。
影响
残留构建进程可以继续占用 CPU、持有文件或写入安装/缓存目录;外层却可能已清理 stage、释放安装锁,或开始备用 registry 重试。这里确认的是进程残留,未声称已经观测到真实缓存损坏。
建议修复和验收
- 强制终止定时器必须维持有效引用,或明确等待清理完成后才让包装进程返回。
- 不要仅因直接子进程退出就推断整个进程组结束;正确处理已经退出的进程组,正常成功路径也不能遗留计时器。
- 超时清理结束后才允许外层释放锁/删除 stage/进行后续尝试。
- 回归测试覆盖:主进程退出但后代忽略 SIGTERM、主进程自身忽略 SIGTERM、正常成功、启动失败和取消;检查退出码及后代存活状态。
- 本轮未在 macOS、Termux、Windows 实测,不把 Linux 结果表述为全平台通过。
已检查本仓库 Issues 和开放 PR;PR #1 尚未解决此处问题。上层项目:TokenNotIncluded/api.lmm.best 的 packages/lmm-scripts。
参考:Node.js timeout.unref 官方说明。
问题
bounded()在超时后先发送进程组 SIGTERM,再安排三秒后的 SIGKILL,但是随即对强制终止定时器调用unref()。如果直接子进程已经退出、同组后代仍在运行,Node 包装进程会提前退出,SIGKILL 回调不再执行。这不是只存在于旧版本的问题:已核查 main 和当前重写 PR #1,两处仍然使用相同逻辑。
位置:
16b70988562e57a1b583b4134e59c360844bc15d:templates/install.sh.in 中的 bounded,由 Pi/DSH 安装入口使用。a193f70c0d405100f419572bba33ef3df27f1e88:templates/lib/node.sh,提取公共函数后保留了问题。关键代码:
已执行的离线复现
环境:Linux,Node v22.16.0。仅执行源码中原样提取的 JavaScript 包装函数,不是整套安装器兼容性测试,没有 npm 安装和外部网络请求。
复现步骤:
bounded()的 JS,命令超时设为 1 秒。/proc/<pid>/stat,排除僵尸状态。hard.unref()换成void hard,其余逻辑不变。实际输出:
因此原代码虽然报告了超时,却没有完成承诺的进程组强制清理。对照修改仅用于确认根因,不表示已经提供了完整生产修复。
影响
残留构建进程可以继续占用 CPU、持有文件或写入安装/缓存目录;外层却可能已清理 stage、释放安装锁,或开始备用 registry 重试。这里确认的是进程残留,未声称已经观测到真实缓存损坏。
建议修复和验收
已检查本仓库 Issues 和开放 PR;PR #1 尚未解决此处问题。上层项目:TokenNotIncluded/api.lmm.best 的
packages/lmm-scripts。参考:Node.js timeout.unref 官方说明。