Files
HomeAgent/internal/plugin/proc/testdata/forkplugin.go
JianFeeeee ceb8a7b165 refactor(proc)!: 版本身份只由版本号本身承载,删除全部自定版本号
内核侧配合 SDK 仓 b2872f3:兼容性不再依赖任何自造版本号。

删除三处自造号:
  - ProtocolVersion(协议号)→ 改为比对主版本段(MajorVersion)
  - shmVersion(共享段 header)→ 改为校验描述符个数(field_count)
  - unifiedVersion(SuperBlock)→ 结构自描述,不再校验

握手契约随之调整:
  HandshakeParams:  protocol(int) → core_version(string)
  HandshakeResult:  protocol(int) → sdk_version 承担判据
  内核比对 MajorVersion(meta.Version) == MajorVersion(res.SDKVersion),
  跨主版本即拒并提示重编(主版本跃迁 = 破坏性变更的定义)。

为什么废除:额外的号需要人去记得升,而它没有任何结构性约束兜底,
必然腐烂。实证:ProtocolVersion 常量在多次布局变更里只升过一次,
对应的守卫测试长期失败无人察觉(期望 18/1,实际 19/2)。

新增判据:
  - TestProcess_MajorVersionMismatchRejected:跨主版本被拒(testdata/badproto)
  - TestProcess_SameMajorDifferentMinorAccepted:同主版本放行(新增 testdata/oldminor)
    后者是 §七「1.x 内纯追加、不调新面不用重编」规约的落地 —— 若误用全等
    比较,未重编的存量插件会被无谓地全部拒绝。
  两条均经变异验证:改成全等比较 → 前者过/后者挂;删掉校验 → 前者挂。
2026-10-13 09:39:34 +08:00

72 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build ignore
// forkplugin 在启动时 fork 一个存活时间比自己长的子进程(继承同一个 stdout),
// 然后在收到 die 工具调用时让自己退出。
//
// 用途:复现「EOF 不等于进程死亡」这一缺陷。
// 插件本体死后,孙子进程仍持有 stdout 写端,父进程(homed)的 readLoop
// 永远读不到 EOF——若内核只靠 EOF 判定死亡,就会完全感知不到插件已死:
// 工具调用一直超时、崩溃回调不触发、自动重启永不发生。
// 生产上 browser 拉 chromium、editdoc 拉 python 都是这个形状。
package main
import (
"bufio"
"encoding/json"
"os"
"os/exec"
)
type request struct {
ID uint64 `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
type response struct {
ID uint64 `json:"id"`
Result interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
func main() {
// 孙子进程**只**继承 stdout(本测试的要点),不给 stderr:
// 插件的 stderr 直通到 go test 的捕获管道,孙子抿着它不放会让
// go test 在测试全部通过后仍等 60s I/O。
//
// sleep 给 3s:只需在插件本体退出的那一瞬间它还持有写端即可(实际 <200ms),
// 不必拖得更久而拖慢测试。
child := exec.Command("sleep", "3")
child.Stdout = os.Stdout
_ = child.Start()
in := bufio.NewScanner(bufio.NewReader(os.Stdin))
out := bufio.NewWriter(os.Stdout)
send := func(v interface{}) {
b, _ := json.Marshal(v)
out.Write(b)
out.WriteByte('\n')
out.Flush()
}
for in.Scan() {
var req request
if err := json.Unmarshal(in.Bytes(), &req); err != nil {
continue
}
switch req.Method {
case "handshake":
send(response{ID: req.ID, Result: map[string]interface{}{
"sdk_version": "1.0.0", "plugin_name": "fork", "pid": os.Getpid(),
}})
case "tool.invoke":
// 不回应答,直接退出:模拟插件突然死亡(崩溃/被 kill)。
os.Exit(7)
default:
if req.ID != 0 {
send(response{ID: req.ID})
}
}
}
}