mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 02:18:06 +00:00
refactor(shm): 变长块分配器 + 工具调用 funccall 调用帧(§13.2/§13.3)
按架构约束推进两步:
1) 分配器收回内核后,定长槽失去了唯一存在理由
(定长槽只是为了绕开"跨进程无法安全变长分配"),
于是 arena 换成**变长块分配器**:first-fit + 邻块合并,
块头 16B(size/state/owner/prevSize)。内核可以按需标定每块大小,
payload 不再受固定槽容量限制(旧上限 16KB)。
- Alloc/Put/Read/Free/ReclaimOwner 全在内核进程内,一把 sync.Mutex
- Free 走块链校验 offset 是已分配块的数据起点 + owner 匹配,
伪造引用不能改动分配器状态
- Read 允许块内偏移(调用帧的结果区就在帧块中间),但不许跨块边界
- arena 4MB,底层 memfd 惰性分配:未触碰的页不占物理内存
2) 工具调用 payload **始终**走共享内存,取消按大小切内联的分支。
按 funccall 模型,内核(caller)标定调用帧交给插件(callee):
[0, ArgsLen) 参数 JSON
[ArgsLen, Frame.Length) 结果区(内核预留的预算)
结果超出预算时插件才 `arena.alloc` 扩容块,引用上打
sharedRefFlagExpand 让内核单独归还。Cleaner 复用同一帧模型。
ToolInvokeParams.Args / ToolInvokeResult.Result 仅剩给直连 RPC 的
测试(process/bench 不建 Host);生产路径永远走帧。
测试:
- TestArena_*(分配/归属/回收/并发唯一/耗尽/超限/伪造 offset/
相邻合并/对齐/布局校验)
- TestPlugin_ToolInvokeArgsResultViaArena:大**小** payload 都经帧往返,
结果一致且 arena 归零("小 payload 同样走调用帧"是本轮行为变更)
- TestPlugin_ArenaAllocFreeAcrossProcess、TestE2E_* 保持通过
- 顺手修 process_test.go 一处预存 doc-comment 分段
This commit is contained in:
@ -221,11 +221,46 @@ func (p *Plugin) handleExit(name string, err error) {
|
||||
|
||||
// ---- 内核 → 插件的反向调用 ----
|
||||
|
||||
// invokeTool 在插件进程内执行工具。
|
||||
//
|
||||
// 按 **funccall 模型**:内核是 caller,为每次调用**标定一块内存帧**
|
||||
// (参数段 + 结果预算段)交给插件(callee)。参数永远写在帧里,不再有
|
||||
// “小 payload 走内联”的按大小分支。
|
||||
//
|
||||
// 结果超出预算时插件才向内核申请扩容块,并在引用上打
|
||||
// sharedRefFlagExpand,内核据此单独归还。
|
||||
//
|
||||
// 控制面仍是 RPC(请求 ID 关联、ctx 取消、崩溃唤醒都由 Process 承载)。
|
||||
func (p *Plugin) invokeTool(name string, args map[string]interface{}) (interface{}, error) {
|
||||
if p.proc == nil {
|
||||
return nil, ErrProcessExited
|
||||
}
|
||||
raw, err := p.proc.Call(MethodToolInvoke, ToolInvokeParams{Name: name, Args: args})
|
||||
arena := p.host.Arena()
|
||||
gen := p.host.Generation()
|
||||
|
||||
argJSON, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 参数序列化失败: %w", p.name, name, err)
|
||||
}
|
||||
|
||||
// 内核标定调用帧:参数段 + 结果预算段。
|
||||
frame, err := arena.Alloc(OwnerHost, len(argJSON)+toolResultBudget, gen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 分配调用帧失败: %w", p.name, name, err)
|
||||
}
|
||||
defer func() { _ = arena.Free(OwnerHost, frame) }()
|
||||
|
||||
area, err := arena.Read(frame, gen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 读取调用帧失败: %w", p.name, name, err)
|
||||
}
|
||||
copy(area[:len(argJSON)], argJSON)
|
||||
|
||||
raw, err := p.proc.Call(MethodToolInvoke, ToolInvokeParams{
|
||||
Name: name,
|
||||
Frame: frame,
|
||||
ArgsLen: uint32(len(argJSON)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -233,7 +268,22 @@ func (p *Plugin) invokeTool(name string, args map[string]interface{}) (interface
|
||||
if err := json.Unmarshal(raw, &res); err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 应答解析失败: %w", p.name, name, err)
|
||||
}
|
||||
return res.Result, nil
|
||||
if res.ResultRef.IsZero() {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 未返回结果引用", p.name, name)
|
||||
}
|
||||
// 插件申请了扩容块:内核负责归还。
|
||||
if res.ResultRef.Flags&sharedRefFlagExpand != 0 {
|
||||
defer func() { _ = arena.Free(OwnerHost, res.ResultRef) }()
|
||||
}
|
||||
data, err := arena.Read(res.ResultRef, gen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 读取共享结果失败: %w", p.name, name, err)
|
||||
}
|
||||
var out interface{}
|
||||
if err := json.Unmarshal(data, &out); err != nil {
|
||||
return nil, fmt.Errorf("proc: %s 工具 %s 解析共享结果失败: %w", p.name, name, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// invokeCleaner 在插件进程内执行工具或通道注册时提供的 Cleaner 函数。
|
||||
|
||||
Reference in New Issue
Block a user