mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
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:
@ -26,6 +26,9 @@ type fakeCoreSDK struct {
|
||||
inputDefs map[string]pubsdk.ChannelDef
|
||||
settings map[string]interface{}
|
||||
autoStart bool
|
||||
|
||||
// injected 记录经 InjectText 注入的文本(验证跨进程共享槽路径)。
|
||||
injected []string
|
||||
}
|
||||
|
||||
func newFakeCore() *fakeCoreSDK {
|
||||
@ -40,17 +43,27 @@ func newFakeCore() *fakeCoreSDK {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeCoreSDK) PluginName() string { return "fake" }
|
||||
func (f *fakeCoreSDK) Settings() pubsdk.SettingsAPI { return nil }
|
||||
func (f *fakeCoreSDK) Memory() pubsdk.MemoryAPI { return nil }
|
||||
func (f *fakeCoreSDK) TextMemory() pubsdk.TextMemoryAPI { return nil }
|
||||
func (f *fakeCoreSDK) DocMemory() pubsdk.DocMemoryAPI { return nil }
|
||||
func (f *fakeCoreSDK) Knowledge() pubsdk.KnowledgeAPI { return nil }
|
||||
func (f *fakeCoreSDK) LLM() pubsdk.LLMAPI { return nil }
|
||||
func (f *fakeCoreSDK) Social() pubsdk.SocialAPI { return nil }
|
||||
func (f *fakeCoreSDK) PluginMgr() pubsdk.PluginMgrAPI { return nil }
|
||||
func (f *fakeCoreSDK) RegisterPluginAPI(name string) error { return nil }
|
||||
func (f *fakeCoreSDK) InjectText(s, c, t string) {}
|
||||
func (f *fakeCoreSDK) PluginName() string { return "fake" }
|
||||
func (f *fakeCoreSDK) Settings() pubsdk.SettingsAPI { return nil }
|
||||
func (f *fakeCoreSDK) Memory() pubsdk.MemoryAPI { return nil }
|
||||
func (f *fakeCoreSDK) TextMemory() pubsdk.TextMemoryAPI { return nil }
|
||||
func (f *fakeCoreSDK) DocMemory() pubsdk.DocMemoryAPI { return nil }
|
||||
func (f *fakeCoreSDK) Knowledge() pubsdk.KnowledgeAPI { return nil }
|
||||
func (f *fakeCoreSDK) LLM() pubsdk.LLMAPI { return nil }
|
||||
func (f *fakeCoreSDK) Social() pubsdk.SocialAPI { return nil }
|
||||
func (f *fakeCoreSDK) PluginMgr() pubsdk.PluginMgrAPI { return nil }
|
||||
func (f *fakeCoreSDK) RegisterPluginAPI(name string) error { return nil }
|
||||
func (f *fakeCoreSDK) InjectText(s, c, t string) {
|
||||
f.mu.Lock()
|
||||
f.injected = append(f.injected, t)
|
||||
f.mu.Unlock()
|
||||
}
|
||||
|
||||
func (f *fakeCoreSDK) injectedTexts() []string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return append([]string(nil), f.injected...)
|
||||
}
|
||||
func (f *fakeCoreSDK) InjectInterruptText(s, c, t string) {}
|
||||
func (f *fakeCoreSDK) InjectTextNoMemory(s, c, t string) {}
|
||||
func (f *fakeCoreSDK) InjectInputSync(s, c, t string) string { return "" }
|
||||
@ -439,3 +452,51 @@ func TestPlugin_FiveProcessesConcurrentAppendNoLostUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 跨进程共享槽池:插件通过 arena.alloc 申请、写入、随业务 RPC 回传、arena.free 归还。
|
||||
//
|
||||
// 验证内核独占管理的所有权模型在真进程 + 真 RPC 下成立:
|
||||
// - 内核能把插件申请的槽内容正确读回来(偏移/长度无误)
|
||||
// - 插件归还后槽确实回到池里(无泄漏)
|
||||
func TestPlugin_ArenaAllocFreeAcrossProcess(t *testing.T) {
|
||||
bin := buildTestPlugin(t, "stageplugin.go")
|
||||
core := newFakeCore()
|
||||
|
||||
host, err := NewHost()
|
||||
if err != nil {
|
||||
t.Fatalf("NewHost: %v", err)
|
||||
}
|
||||
defer host.Close()
|
||||
|
||||
p := New("demo", bin, t.TempDir(), nil, host, nil)
|
||||
if err := p.Start(core); err != nil {
|
||||
t.Fatalf("Start: %v", err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
core.mu.Lock()
|
||||
h, ok := core.tools["demo_inject"]
|
||||
core.mu.Unlock()
|
||||
if !ok {
|
||||
t.Fatal("插件应注册 demo_inject 工具")
|
||||
}
|
||||
|
||||
// 用明显超过内联阈值的 payload,确保真的走共享槽而非内联。
|
||||
payload := strings.Repeat("共享内存", 500) // 约 6KB
|
||||
if _, err := h(map[string]interface{}{"text": payload}); err != nil {
|
||||
t.Fatalf("调用 demo_inject: %v", err)
|
||||
}
|
||||
|
||||
got := core.injectedTexts()
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("应注入 1 条文本,实际 %d 条", len(got))
|
||||
}
|
||||
if got[0] != payload {
|
||||
t.Fatalf("经共享槽读到的内容不一致:len(got)=%d len(want)=%d", len(got[0]), len(payload))
|
||||
}
|
||||
|
||||
// 插件已归还槽:池必须回到全空,否则说明 arena.free 没生效。
|
||||
if used, total := host.Arena().Stats(); used != 0 {
|
||||
t.Fatalf("插件归还后槽池应全空,实际 used=%d/%d", used, total)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user