refactor(shm): 内核独占共享槽池,插件经 RPC 申请/归还(§13.2/§13.3)

推翻前两版跨进程分配器设计,根因是"共享内存里放了只被单一进程
更新的可变游标":

- v1 在 SuperBlock 放 arenaUsed 游标,内核 CAS bump。但插件模板里
  arenaUsed 是**进程本地变量**,两进程各自 bump 必写同一段内存;
  arenaReset 还会重置共享游标覆盖对方数据。
- v2 把位图 CAS 下沉到插件模板,正确但把分配器实现泄漏进插件运行时,
  且插件必须与内核保持位图布局同步。

新设计(用户明确的架构约束):内核全权管理共享内存,插件通过
syscall 风格 RPC 申请/归还,内核返回偏移与大小。分配器只存在于内核
进程内,一把 sync.Mutex 即可。共享内存是内部实现,不对插件开发者
暴露——SDK 公开 API 仍是普通字符串/Map。

主要改动:
- arena.go: 定长槽 + 位图,Alloc/Put/Read/Free/ReclaimOwner,内核独占;
  槽头记录 owner,Free 校验归属;payload 超槽容量退回内联 RPC
- protocol.go: 新增 arena.alloc / arena.free(CapCore)
- capability.go: 登记两个新 method(checkAllMethodsClassified 要求)
- corehandler.go: 实现 arena.alloc/free;Cleaner 改为内核预分配
  请求槽 + 响应槽(插件完全不分配)
- plugin.go: Start 领 ownerID;handleExit 调 ReclaimOwner 回收残留槽
- unified.go: 移除坏的 bump 分配器;arena 基址 8 字节对齐
- 删除 toollane.go: ring 状态机是死代码(从未接线),且把 RPC 已有的
  请求 ID 关联/错误传递/ctx 取消重新实现了一遍。控制面保留 RPC,
  只把 payload 搬进共享槽。
- 测试: TestArena_*(归属/回收/并发唯一/耗尽/超限/非法引用/布局)+
  TestPlugin_ArenaAllocFreeAcrossProcess(真进程申请→写入→随业务
  RPC 回传→归还→内核读回一致且池归零)
This commit is contained in:
JianFeeeee
2026-09-10 17:56:50 +08:00
parent cf3c99233b
commit ca584bda3f
13 changed files with 1157 additions and 654 deletions

View File

@ -57,6 +57,14 @@ const (
MethodStageInvoke = "stage.invoke"
// MethodOutputInvoke 经插件输出通道发送。
MethodOutputInvoke = "output.invoke"
// MethodArenaAlloc 向内核申请一块共享内存(返回偏移与大小)。
// MethodArenaFree 通知内核回收先前申请的共享内存。
//
// 这两个是**内部传输层接口**,不由插件开发者直接使用:共享内存是
// 内核的内部实现,公开 SDK 仍是普通字符串/Map模板运行时按
// payload 大小自动选择内联 JSON 还是共享槽。
MethodArenaAlloc = "arena.alloc"
MethodArenaFree = "arena.free"
// MethodHandshake 建链首帧交换协议版本、SDK 版本、共享段规格。
MethodHandshake = "handshake"
)
@ -202,26 +210,40 @@ type StageInvokeResult struct {
}
// ToolInvokeParams / ToolInvokeResult工具调用原 go_invoke_tool
//
// ArgsRef/ResultRef 为后续 §13.3 预留payload 走 Exchange Arena
// RPC 帧只带 16 字节描述符;当前仍以 Args/Result 内联为主。
type ToolInvokeParams struct {
Name string `json:"name"`
Args map[string]interface{} `json:"args,omitempty"`
Name string `json:"name"`
Args map[string]interface{} `json:"args,omitempty"`
ArgsRef SharedRef `json:"args_ref,omitempty"`
}
type ToolInvokeResult struct {
Result interface{} `json:"result,omitempty"`
Result interface{} `json:"result,omitempty"`
ResultRef SharedRef `json:"result_ref,omitempty"`
}
// CleanerInvokeParams / CleanerInvokeResult跨进程计算层清洗。
// Scope 取 tool / input / outputName 是工具名或通道名。
// TextRef 指向共享内存 arena 中的实际文本数据。
//
// 数据面走 Exchange Arena
// - TextRef 指向内核写入的输入文本payload 超过槽容量时改用内联 Text
// - RespRef 指向内核**预分配**的响应槽,插件把结果写进去后回 TextRef
// 结果放不下时插件改用内联 Text 返回
//
// 插件侧不做任何分配,因此不存在跨进程分配器的竞争。
type CleanerInvokeParams struct {
Scope string `json:"scope"`
Name string `json:"name"`
TextRef SharedRef `json:"text_ref"`
Text string `json:"text,omitempty"`
TextRef SharedRef `json:"text_ref,omitempty"`
RespRef SharedRef `json:"resp_ref,omitempty"`
}
type CleanerInvokeResult struct {
TextRef SharedRef `json:"text_ref"`
Text string `json:"text,omitempty"`
TextRef SharedRef `json:"text_ref,omitempty"`
}
const (
@ -241,6 +263,23 @@ type OutputInvokeParams struct {
Args map[string]interface{} `json:"args,omitempty"`
}
// ArenaAllocParams / ArenaAllocResult插件向内核申请共享内存。
//
// 内核返回的 Ref.Length 是槽容量(可写上限),插件写入后自行把 Length
// 改成实际 payload 长度再随业务 RPC 回传。
type ArenaAllocParams struct {
Size uint32 `json:"size"`
}
type ArenaAllocResult struct {
Ref SharedRef `json:"ref"`
}
// ArenaFreeParams插件通知内核回收共享内存。只带描述符槽号在 Flags 里。
type ArenaFreeParams struct {
Ref SharedRef `json:"ref"`
}
// PluginInitParams插件构造参数原 case init_plugin
type PluginInitParams struct {
Name string `json:"name"`