|
|
df872b9e86
|
chore(vendor): 主仓不再跟踪 SDK 示例代码(决策 A)
## 背景
`.gitignore` 第 26-45 行早已写明「外部插件与工具链维护在独立 SDK 仓
(决策 sdk_repo_only),本仓经 go.mod 的 replace 引用」,并忽略了
example/、tools/、docs/、site_build/、package/、scripts/。
但有 29 个文件**早于该规则**被跟踪,靠「已跟踪文件不受 .gitignore
影响」留着,注释还特意写了「这是有意的,不要『修』」。
本轮修 example/bili 时踩到了:那 87 行改动先落在主仓、再手动同步到
SDK 仓 —— 同一份代码两个仓各改一遍,正是这个遗留结构的成本。
## 核实:主仓到底需要什么
- 主仓 Go 代码只 import 两个包:`homeagent-sdk/sdk`(插件入口)、
`homeagent-sdk/meta`(版本号)
- `remotedevice/` 是 C 库,主仓 C 侧明确「不链接任何外部库」,
只有注释里提到它
- `tools/hmapdev/yaegi` 的 import 出现在 **SDK 仓自己的文件之间**
(yaegi/interp.go → yaegi/mocksdk),不是主仓依赖;
且 tools/ 本就在 .gitignore 里,从未被跟踪
所以 29 个文件全部可以删。实际仓库负担也只有 42 个文件 / 0.6MB
(工作树里那 971MB 绝大部分是未跟踪的构建产物,不是仓库体积)。
## 改动
- 删 21 个 example/ 文件(10 个示例的 plg.json + plugin.go + qq/plugin_test.go)
- 删 8 个 remotedevice/ C 文件
- 工作树里一并清掉(不受跟踪的构建产物顺带回收)
构建与全量测试均通过。
## 判据:3 条 + 变异(internal/meta/vendored_sdk_test.go)
- TestVendoredSDKHasNoExampleOrRemotedevice:防止示例代码被重新提交进来
- TestVendoredSDKKeepsRequiredPackages:**反向**保护,防止为省事把
sdk/ 与 meta/ 也删掉(上一条只防"多了",删过头要靠这条)
- TestGoModStillReplacesSDKToVendoredPath:决策 A 依赖 replace 指向 vendored 路径
★ 判据自己踩了两个坑,都靠实跑抓出来:
1. `git ls-files` 的路径参数**相对当前目录**解析,而测试跑在
internal/meta/ 下 ⇒ 就地执行返回空,表现为「必需包全都不在」的假红。
改用 `git -C <仓库根>`。
2. 把 tools/hmapdev/yaegi 当成主仓依赖写进必需清单 ⇒ 又一次假红。
根因是把 SDK 内部的引用误当成主仓依赖(它本就在 .gitignore 里)。
变异验证:塞一个 example 文件进版本控制 → 判红。
|
2026-09-26 17:18:41 +08:00 |
|
|
|
eb14b3a1d2
|
fix(proc): 杀插件进程组 + readerWG 超时兜底 —— 修孙进程拖死关停
## 现象
线上关停必超时:systemd 报 `State 'stop-sigterm' timed out. Killing.`,
进程组里 23 个插件全退完了,最后那条 `[homed] stopped` 仍打不出来。
其中只有 bili 报 `[proc] bili SIGKILL 后 2s 仍未被收割`,之后近 90 秒无日志。
## 根因
bili 用 exec.Command 拉 yt-dlp(源码 example/bili/plugin.go:122/212),
无 CommandContext、无 Setpgid、Stop() 是空的。yt-dlp 再 fork ffmpeg,
**孙进程继承插件的 stdout 管道写端**。
插件被 SIGKILL → 孙进程仍存活、写端不关
→ readLoop 的 scanner.Scan() 永不 EOF
→ p.readerWG.Wait() 永不返回(Kill 的最后一行,**无超时**)
→ StopAll 的 wg.Wait() 永不返回 ⇒ 关停挂死 ⇒ systemd SIGKILL
内核 process.go:340 的注释早已预警过这个场景("插件 fork 的孙子进程继承
同一 stdout 写端时,插件本体死了 EOF 也不会到"),但 Kill 没有对应保护。
## 内核三处修法(缺任一条都不够)
1. **spawn 时 Setpgid**:插件自成进程组,不再与内核同组
2. **Kill 杀整个进程组**(kill(-pgid)):孙进程一起死,管道写端才关。
兜底:负 pid 失败时退回杀本体(老插件/非 Unix 平台)
3. **readerWG.Wait() 加超时兜底**:这是唯一能保证 Kill 一定返回的地方。
超时后主动关读端逼 readLoop 退出,再兜一层仍不退就放弃等待 ——
宁可少等 2 秒,也不能把关停无限期挂住。
## 插件侧(bili)
CommandContext + Setpgid + Stop() 里 cancel 并 wait:
- 只 cancel 不 wait 的话内核会先释放共享段,而 yt-dlp 还在写 stdout
- waitRunGroup 杀整个进程组(ffmpeg 也在内),不留孤儿
## 判据:5 条 + 3 组变异
判据用**真实模板编译的插件**(复用 buildPluginWithRealTemplate,
与 e2e_template_test 同一条路)+ NewHost 启动,不是自造 shim:
裸 Spawn 没有 Host 建共享内存段,插件握手会报 permission denied。
★ 判据自己踩了三次坑,都由变异/合跑抓出来:
1. 给孙进程也加 Setpgid ⇒ 它逃出插件进程组,kill(-pgid) 杀不到,
造出假失败(真实场景 yt-dlp 不会脱离进程组)
2. 各测试数全局孙进程数 ⇒ 前一个泄漏的被后一个数进去,
单跑通过、合跑变红。改为记录基线只关心自己新增的
3. readerWG 超时那条用纯构造 &Process{cmd:nil} ⇒ Kill 第 607 行
早退,根本走不到那段,撤掉超时照样绿。补了「脱组孙进程」
场景(Setsid 逃出进程组)才真正覆盖到
变异:去 Setpgid → 判红;只杀本体不杀组 → 判红。
全量 41 包绿。
|
2026-09-26 16:58:41 +08:00 |
|
|
|
e9119edd9a
|
fix(plugins): bili output_dir 系统目录黑名单 + recoverydiag db_path 沙箱限制
P3 bili: output_dir 配置项此前未校验,yt-dlp 可被配置写到任意系统目录。
加系统目录黑名单(/、/etc、/usr、/var 等),命中直接拒绝执行。
P4 recoverydiag: db_path 参数 LLM 可控,可探测读取任意 sqlite 文件。
现强制限制在 data 目录内(前缀校验),越界返回提示。
两插件重打包升版(bili 1.2.0 / recoverydiag 0.2.0)安装验证
config_kept=true,内核重启加载正常。
|
2026-08-26 16:57:34 +08:00 |
|