1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
7.0 KiB
7.0 KiB
79 · 插件启停的两处平台缺陷:uninstall 缺 await(把整个 orchestrator 带崩)+ pnpm remove 用了无效 flag(2026-09-13 发现)
- 日期:2026-09-13
- 状态:✅ 已修复并部署(2026-09-13 13:4x 逐条核实产物);仅剩可选的「加固 b 半应用态主动自愈」未做 —— 见 §七
- 触发:用户在 guest 的「功能管理」启用
dsh-plugin-mcn-suite→ 安装失败(2026-09-13 07:4x) - 影响:P0 —— 任一次插件启停失败都可能终止整个
dshs进程(全体用户瞬断 + 所有实例被停),并把目标 profile 留在半应用态(该实例此后每次启动即崩)
TL;DR:D1(缺
await)→ 未捕获 Promise 拒绝 → orchestrator 退出;D2(无效 flag)→ 让 D1 必然被触发;D3(内存不够)→ 让流程必然走进触发 D1 的分支。
一、现象链(journalctl 原文,逐条可核)
| 时刻 | 事件 |
|---|---|
| 07:41–07:43 | guest 装上 mcn-suite 后反复 [dsh-plugin-mcn] 榜单查询失败: unable to open database file |
| 07:43:28/46/56 | 实例侧 FATAL ERROR: Reached heap limit(Mark-Compact 155.3(169.1) -> 153.9 MB 等)→ exitCode 134;[crash-restart] 触发 |
| 07:43:58 | Error: Command failed: … setpriv … pnpm remove dsh-plugin-mcn-suite --ignore-workspace-root-check …;stderr 解出 ERROR Unknown option: 'ignore-workspace-root-check';栈 = runPnpmAs(…:406) ← uninstall(…:575) ← business-plugins.js:614 |
| 07:43:58 | dshs.service: Main process exited, code=exited, status=1/FAILURE → systemd 4 秒后自动重启(07:44:02) |
| 07:45–07:46 | 服务重启后 guest 继续被拉起 → 每次都 OOM → attempt 1..5 → crash-loop-circuit-open(熔断);两个实例均 inactive |
二、三个缺陷
| # | 级别 | 位置 | 内容 |
|---|---|---|---|
| D1 | P0 | src/web/routes/business-plugins.ts:682(部署版 business-plugins.js:614) |
隔离定位循环里写的是 try { uninstall(sel.id) } catch { /* 尽力而为 */ } —— 缺 await。uninstall 是 async,其 rejection 逃出同步 try/catch ⇒ unhandled rejection ⇒ 进程退出(Node 默认 --unhandled-rejections=throw) |
| D2 | P1 | 同文件 :644(部署版 :575) |
runPnpmAs(user.id, ['remove', id, '--ignore-workspace-root-check', '--reporter', 'silent'], dir) —— 该 flag 只存在于 add 子命令,remove 不认(pnpm 9.15.9 报 Unknown option,提示 ignore-workspace)⇒ 禁用路径必然失败,进而必然触发 D1 |
| D3 | P1 | 容量 | 实例 V8 堆限 160 MiB 装不下 dsh-univer-office + dsh-plugin-mcn-suite(隔离 cgroup 实测分别 +63.7 / +38.5 MiB rss)⇒ 探活失败 → 平台进「隔离定位」分支 → 必然走到 D1/D2 |
三、连带后果(本身也是缺陷)
- 因进程直接退出,平台兜底的
restoreProfile()(在try/catch里)没跑 ⇒ profile 停在半应用态(mcn-suite 已在dependencies+bundles)⇒ 该实例此后每次启动都 OOM(熔断循环),用户彻底不可用。 - 本次处置:手工回滚 guest profile(从 deps + bundles 移除 mcn-suite,备份
package.json.bak-rollback-20260913T0748)→ 触发一次enter验证实例恢复(dsh web:正常、active running、cgroup 186.5 / 384 MiB、无 crash-restart)。
四、修法(已备好,未落地)
- D1:
try { uninstall(sel.id) } catch {}→try { await uninstall(sel.id) } catch {}(一行)。 - D2:
remove的参数改成['remove', id, '-w', '--reporter', 'silent'](或直接去掉--ignore-workspace-root-check)。 ⚠️add的--ignore-workspace-root-check是合法 flag,不要一起改。 - 加固(建议同批):
- 路由最外层
void (async () => {…})()补.catch()(双保险,避免同类遗漏再崩进程); - 启动/应用前的半应用态自愈:检测「
bundles里有包但node_modules装不全」→ 自动摘除或至少审计告警。
- 路由最外层
- 验收:修复后在任一 profile 上重现「启用一个注定失败的插件」→ 期望 = 任务
failed+ profile 回滚 + 服务不退出。
五、红线与影响
- 改码 +
npm run build+systemctl restart dshs→ 中断在线用户数秒 ⇒ 走 R8 先知会。 - 回滚:单文件
git回退 + 重新 build + 重启;服务本身有 systemd 自动重启兜底。
六、相关
- 档案 65(功能插件启停 ↔ web provider 联动)|档案 16 §2.2(危险操作确认弹窗)|档案 75 维度 B(资源成本,与 D3 同源)|档案 74(V8 堆限 256→160,D3 的背景)。
七、修正(2026-09-13 13:4x)· D1 / D2 / 加固 a 已落地并部署
触发:用户问「还有待处理的事项吗」时复核本条,发现状态已过期 —— 三个缺陷中有两个半已经修完并上线。
逐条实测(部署产物 lib/web/routes/business-plugins.js,只读核对):
| 项 | 期望修法(§四) | 产物实测 | 结论 |
|---|---|---|---|
D1 缺 await |
try { await uninstall(…) } catch {} |
:587 与 :617 均为 await uninstall(sel.id) |
✅ 已修 |
| D2 无效 flag | remove 去掉 --ignore-workspace-root-check |
:575 = ['remove', id, '-w', '--reporter', 'silent'];全仓该 flag 仅剩 add 一处(合法) |
✅ 已修 |
加固 a 外层 .catch() |
路由最外层补 catch | :613 void (async () => { … :718 })().catch((err: unknown) => { |
✅ 已修 |
| 加固 b 半应用态主动自愈 | 检测「bundles 有包但 node_modules 装不全」→ 自动摘除/告警 |
只有失败路径的 restoreProfile()(:567 / :671 / :710)回滚到快照 |
⚠️ 未做(可选加固,非 P0) |
D3 已消解(不再需要窗口):D3 的成因是「V8 堆限写死 160 MiB + MemoryMax 写死 384 MiB 装不下 univer + mcn-suite」。档案 81 · R1-④ 已把两者改成「按插件集合动态计算」(instanceMemMb() / heapMbFor())。2026-09-13 13:3x 实测运行中实例:
guest(uid 100002) NODE_OPTIONS=--max-old-space-size=256 scope MemoryMax=544 MiB
admin(uid 114801) NODE_OPTIONS=--max-old-space-size=256 scope MemoryMax=384 MiB
平台进程 env 仍有 DSH_INSTANCE_NODE_OPTIONS=--max-old-space-size=160 ← 已被代码侧 withHeap() 覆盖,不再是生效值
⇒ §五「需 R8 窗口重启」的前提已不成立:不再需要为修复本条而重启。
仍未闭环的一项:§四.4 的端到端验收(在某个 profile 上重现「启用一个注定失败的插件」→ 期望 任务 failed + profile 回滚 + 服务不退出)尚未跑过。代码层面三个修法都已就位,但「服务真的不再被带崩」目前是代码判据、不是实测判据。建议与 T03 的 guest 启用(天然就是一次"可能失败的启用")合并验收,不必单独造场景。