docs(plan): 第 12 节 —— 子进程化迁移剩余工作与目标效果

迁移主体已完成上生产(内核 v1.0.0,17 插件全部子进程化),
但有若干项未做完或未达成。写进 plan.md 而非只留在对话里,
避免下次接手时靠猜。

八个小节按「阻塞程度」排:

**12.1 合并到 main + 发布分支** —— 卡在四个决策点,非技术阻塞:
merge 方式(--no-ff vs squash)、是否删 feature 分支、release 构建是否
再替换生产二进制、SDK 仓是否同步。附完整执行序列。

记了一个易错点:v1.0.0 tag 当前打在 feature 分支中间点 670efcd,
按规范应在 release 分支上,需删除重打。

**12.2 验收清单两项未达成** —— 这两项在迁移计划里已如实标 ⚠️:
- SetToolBlocks 仍未实现。C ABI 时代也是空实现故不算回归,
  但 §3.8 明确承诺过「二进制写入 arena + Slice 描述符回传」,没兑现。
- 内存 88MB 远超「基线 +29MB」。根因是每插件静态链接整个 Go runtime,
  15 个不同二进制无共同物理页(PSS/RSS 99.9% vs 基线 44%)。
  基线用 2.68MB 最小插件复制 17 份,绝对数字本就不可比。

**12.3 事件环零真实负载检验** —— 机制完成、压测通过(2.29ms 与实验 4
一致),但 grep 确认无任何插件使用 Events().Subscribe。压测是我构造的
负载,生产上这条路径从未被真实插件走过。只写  会掩盖这一点。

**12.4 三套 ABI 只收敛两套** —— C ABI 删了、Windows DLL 收敛了,
Lua 仍走独立解释器路径。§9.2 那句「三套收敛为单一 RPC」本轮兑现 2/3。
不阻塞是因为 Lua 不经 C ABI,不属于要消除的 6 类缺陷。

**12.5 Windows 无真机验证** —— 只做了交叉编译 + 单元测试。
已知语义差异(Event 是二元信号非计数器)推理上不影响正确性,
但没在真机确认过。§9.2 声称 Windows「从受害者变受益方」缺实证。

**12.6 stage 往返省两次 IPC** —— 132µs 里编解码只占 3.7µs,
其余是 3 次进程往返(invoke + 插件侧反向 lock/unlock)。合并后预期
降到 ~30µs。风险是锁持有时机改变,插件若在 handler 里再请求锁会死锁。

**12.7 遗留项** —— homed 主 heap 2.36GB(与插件无关)、鸿蒙端未提交改动。

**12.8 已达成目标留档** —— 6 类缺陷逐条对账,每条附证据
(测试名或生产日志),便于日后确认哪些是真解决了。
This commit is contained in:
JianFeeeee
2026-09-03 08:50:48 +08:00
parent 12259ed4ec
commit 351e99dd70

179
plan.md
View File

@ -910,3 +910,182 @@ homed RSS = 2.34 GB RssAnon = 2.35 GB真实驻留
context 累积导致的内存增长
- [ ] 单独排查 homed heap 2.36GB 驻留来源
---
## 12. 子进程化迁移收尾:剩余工作与目标效果
> 状态锚点2026-09-03迁移主体已完成并上生产。内核 **v1.0.0**
> 生产 17 个外部插件全部经子进程通道运行15 个子进程稳定存活。
> 分支:主仓 `feature/plugin-proc-migration` @ `12259ed`(领先 main 26
> SDK 仓 `feature/plugin-proc-migration` @ `5ed8d65`(领先 main 4
>
> 三项合入门禁**已全部通过**`make test` 零失败、`go vet ./...` 无告警、
> `git diff main -- third_party/homeagent-sdk/sdk/` 为空(接口冻结不变量)。
>
> 详细执行记录见 `docs/zh/plugin-migration-plan.md`Part 0~6 全部标记完成)。
### 12.1 待用户决策后执行:合并到 main + 发布分支
**当前卡在四个决策点**不是技术阻塞
| # | 决策点 | 备选 | 倾向 |
|---|---|---|---|
| 1 | merge 方式 | `--no-ff` 保留 25 commit / squash 压成一条 | `--no-ff`——commit message 记录了为何共享同一块 memfd」「为何 procCore 不能嵌入等踩坑过程 |
| 2 | 合回后是否删 feature 分支 | 规范要求/ 8-9 周大特性 | 听用户 |
| 3 | release 构建是否再替换生产二进制 | 溯源干净/ 不换避免停服 | 听用户 |
| 4 | SDK 仓是否同步 main + release | 同步 / 只合 main / 暂不处理 | 同步——规范说两仓版本对齐是第一优先级 |
**目标效果**
- `main` 含全部迁移工作且**永远可部署**规范 §.1)。
- 存在 `release/v1.0.0` 分支`v1.0.0` tag **打在 release 分支上**而非 feature
当前 tag 指向 `670efcd`feature 分支中间点需删除重打
- 两仓版本对齐主仓 `internal/meta.Version` = SDK `meta.Version` = `1.0.0`
vendored SDK SDK release tag 内容一致
- 现网部署产物可追溯到 release tag 构建规范 §)。
**执行序列**决策落定后
```bash
# 主仓
git checkout main && git merge --no-ff feature/plugin-proc-migration
git checkout -b release/v1.0.0 main
git tag -d v1.0.0 && git tag -a v1.0.0 # 重打在 release 上
make build VERSION=1.0.0 # 发布产物
# SDK 仓(同上流程)
cd third_party/homeagent-sdk
git checkout main && git merge --no-ff feature/plugin-proc-migration
git checkout -b release/v1.0.0 main && git tag -a v1.0.0
```
---
### 12.2 验收清单里两项**未达成**的目标
这两项在 `docs/zh/plugin-migration-plan.md` 的最终验收清单里如实标了
不是遗漏而是明确的未兑现承诺
#### 12.2.1 `SetToolBlocks` 仍是未实现(承诺未兑现)
- **现状**`io.setToolBlocks` 已在 `proc/protocol.go` 定义已划入 `CapCore`
能力组 `corehandler.go` handler 仍返回未实现
- **为何不算回归**C ABI 时代它也是空实现(§1.4 / `case` 无对应逻辑
能力从给不了变成暂未接」,没变差
- ** §3.8 承诺过**迁移评估明确写`SetToolBlocks` 二进制写入 arena
返回 `Slice` 描述符 ✅」。这条没做到
- **目标效果**插件调用 `SetToolBlocks(blocks)` 多模态内容块经共享段
arena 传给内核内核把它并入工具返回值`Slice` 描述符回传避免拷贝
- **当前无用户**17 个外部插件均未调用故不阻塞发布
- [ ] 实现 `io.setToolBlocks` 的内核侧 handlerarena 写入 + Slice 回传
- [ ] 补一个真实使用它的 example 插件否则无法验证
#### 12.2.2 内存开销超出计划目标(结构性问题)
- **计划目标**迁移后常驻 基线 +29MB实验 5 量级)。
- **实测**15 个插件进程 `RSS=88.0MB` / `PSS=87.9MB`均摊 5.87MB
- **根因**每插件静态链接整个 Go runtime15 **不同**二进制之间无共同
物理页可映射`PSS/RSS = 99.9%`基线是 44%——那次用同一个 2.68MB 最小
插件复制 17 页可共享)。
- **绝对数字不可比**基线插件 2.68MB真实插件 3.1~14.8MBbrowser 最大)。
结构性指标均摊线程 5.5 vs 4.9同量级
- **实际开销高于 §4.3 乐观估计**这是每插件独立二进制的固有代价
- **目标效果若要压**共享一个 launcher 二进制 + 各插件只提供业务模块
15 个进程映射同一份 runtime 物理页 PSS 压回 RSS 的一半以下
代价是插件不再是自包含可执行文件分发与版本管理都变复杂
- [ ] 决定是否值得为此改变分发模型当前倾向不改88MB 可接受
---
### 12.3 事件环:机制完成但**零真实负载检验**
- **已完成**内核侧 `proc/evtring.go`写端 + 消费端 + 事件类型位编码)、
`internal/plugin/evtring.go`Bus EvtRing 适配)、模板侧 `evtConsumerLoop`
三平台通知机制Linux eventfd / macOS pipe / Windows Event)。
- **压测通过**5000 Publish + 20µs 慢消费者 = 2.29ms(与实验 4 一致
订阅者 18 耗时不变环溢出仍 O(1)。
- ****`grep` 确认**无任何外部插件使用 `Events().Subscribe`**。
压测是我构造的负载生产上这条路径从未被真实插件走过
- **目标效果**至少一个真实插件订阅内核事件并正确处理
验证独立游标 + 溢出跳过 + dropped 计数在真实时序下的行为
- [ ] 写一个订阅 `stage`/`tool_call` 事件的 example 插件做真实验证
- [ ] 观察长时间运行下 `dropped` 计数是否异常增长
---
### 12.4 三套 ABI 只收敛了两套Lua 仍独立
- **已收敛**C ABI删除+ Windows DLL改走同一 RPC)。
- **未收敛**`internal/plugin/lua_plugin.go` / `dynamic_lua.go` 仍走
gopher-lua 解释器的独立路径
- **为何不阻塞本轮**Lua 经解释器不经 C ABI不属于本轮要消除的 6 类缺陷
热重载失效崩溃隔离缺失stage lost updatecgo 超时泄漏
output_send 假成功能力断层)。§9.2 三套 ABI 收敛为单一 RPC
这句话本轮只兑现了 2/3
- **目标效果**Lua 插件也走 `proc` 通道launcher 进程内嵌解释器
内核侧只有一套加载逻辑与一套权限检查
- **收益**Lua 插件获得崩溃隔离与共享内存 stage 全字段可见
内核侧删掉 `lua_plugin.go` 的平行实现
- [ ] 评估 Lua proc 通道的代价解释器进程启动开销 vs 隔离收益
---
### 12.5 Windows 只做了交叉编译,无真机验证
- **已完成**`shmalloc_windows.go``CreateFileMappingW` + `MapViewOfFile`)、
`evtfd_windows.go``CreateEventW` + `SetEvent`)、`shmpass_windows.go`
名字经环境变量传递)、插件侧 `proc_shm_windows.go.tmpl`
`syscall.NewLazyDLL` 绑定 `OpenFileMappingW`/`OpenEventW`)。
- **验证程度** `GOOS=windows GOARCH=amd64 go build` 通过 + 单元测试
**无 Windows 测试机,从未真机跑过**
- **已知的语义差异**代码注释里记了但未实测
Windows Event 是二元信号而非计数器多次 `SetEvent` 只唤醒一次
推理上不影响正确性消费者按 `readSeq` `writeSeq` 批量 drain
但没在真机确认过
- **目标效果**Windows 真机上完成一次完整的插件加载 工具调用
stage 改写 事件消费闭环确认 16 字段全可见且写回生效
这是 §9.2 声称 Windows从受害者变受益方的实证)。
- [ ] 找一台 Windows 机器跑端到端验证
- [ ] 特别验证命名对象的撞名防护名字带 PID + 递增序号
---
### 12.6 性能优化候选stage 往返省两次 IPC
- **实测**完整 stage 往返 132µs其中共享段编解码只占 3.7µs2.8%)。
- **成本构成**一次 stage 要走 **3 次进程间往返**——`stage.invoke`
加上插件侧反向的 `stage.lock` / `stage.unlock`
- **相对 LLM 往返 2-8 秒可忽略**故非紧急
- **目标效果** lock/unlock 合入 `stage.invoke` 的请求/应答
内核在下发 invoke 前就代插件持锁应答时释放
stage 往返从 3 IPC 降到 1 预期 132µs ~30µs
- **风险**改变锁的持有时机当前是插件主动请求
改后内核代持——插件若在 handler 里再次请求锁会死锁需要额外防护
- [ ] 评估锁语义变化的影响面哪些插件依赖显式 lock 时机
---
### 12.7 无关本次迁移的遗留项
- [ ] 单独排查 homed heap 2.36GB 驻留来源 §11.9与插件无关
- [ ] `cmd/ohos/.../SettingsPage.ets` 80 行未提交的鸿蒙端改动
非本次迁移内容一直未碰
---
### 12.8 本次迁移**已达成**的目标(对照 §11.0 起因)
留档备查——6 C ABI 前提缺陷的消除状态
| 缺陷 | 原状 | 现状 | 证据 |
|---|---|---|---|
| 热重载失效11.6 | `DF_1_NODELETE` `dlclose` no-op | `plugin.bin` 即生效 | 生产实测 `unloaded (config kept)` 重载 |
| 崩溃隔离缺失 | 插件 panic 带崩 homed | 子进程独立崩溃 | `TestRealPlugin_CrashDoesNotKillKernel` |
| stage lost update11.3 | 副本模型互相覆盖 35.8~36.8% | 0% | `TestPlugin_FiveProcessesConcurrentAppendNoLostUpdate` |
| cgo 超时泄漏11.2 | 现网泄漏 26 | 整套新架构零 cgo | `Process.Kill()` 真取消 |
| output_send 假成功11.1 | 永远返回成功 | 真实结果 | 生产实测 `map[status:sent]` |
| 能力断层11.5 + §3.8 | Windows 只见 3 字段无写回 | 18 字段全可见可写回 | 生产实测 sanitizer 跨进程改写 13590 字节 |
额外收益权限梯度从C ABI 表达能力的意外产物变成**显式三道闸**
类型层 `procCore` 命名字段 + manifest 能力声明 + RPC 边界明确拒绝)。