mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-03 15:53:56 +00:00
feat(shm): ToolCall ring buffer(§13.3) — 栈帧模型 ring,push/pop 帧状态机
- ToolCallRing: 定长帧 ring buffer (64 frames × 112B),状态机 FREE→CALLING→READING→READY→FREE - Reserve: 环形扫描找 FREE 帧,全忙返回 ErrToolCallRingFull 背压 - SharedRef: pack/unpack 辅助函数 - unified.go: arena 分配器(arenaAlloc/arenaWrite/arenaRead) - 5 个单测覆盖 init/reserve/背压/find/reuse
This commit is contained in:
249
internal/plugin/proc/toollane.go
Normal file
249
internal/plugin/proc/toollane.go
Normal file
@ -0,0 +1,249 @@
|
||||
package proc
|
||||
|
||||
// ToolCall lane 实现(§13.3)。
|
||||
//
|
||||
// 采用操作系统函数调用栈帧模型:每个工具调用 = 一个固定帧,帧满背压,完成即回收。
|
||||
// 不是 arena 堆分配,是 ring buffer 上的 push/pop。
|
||||
//
|
||||
// +--------------------------------------------+
|
||||
// | ToolCall Ring Header 32B |
|
||||
// | magic / cap / frameSize / writeIdx |
|
||||
// +--------------------------------------------+
|
||||
// | Frame 0: state(4) + reqID(8) + name(64) + |
|
||||
// | inputRef(16) + outputRef(16) |
|
||||
// | Frame 1: ... |
|
||||
// | Frame N-1: ... |
|
||||
// +--------------------------------------------+
|
||||
// | Dynamic Arena (input/output JSON data) |
|
||||
// +--------------------------------------------+
|
||||
//
|
||||
// 状态机:
|
||||
// FREE -> CALLING (内核写 inputRef,eventfd 通知插件)
|
||||
// -> READING (插件读 input,执行,写 outputRef,通知内核)
|
||||
// -> FREE (内核读 output,帧回收)
|
||||
//
|
||||
// 背压:writeIdx 追上最后的 busy 帧时返回 ErrToolCallRingFull。
|
||||
// 零竞争:插件只操作自己 requestID 对应的帧。
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
toolRingMagic uint32 = 0x54524C47 // "TRLG" — TooL Ring
|
||||
toolRingVersion uint32 = 1
|
||||
toolRingCap uint32 = 64 // 最大并发工具调用
|
||||
toolFrameNameLen uint32 = 64 // 工具名最大长度
|
||||
|
||||
// 帧偏移(相对帧起始)
|
||||
toolFrameOffState = 0 // uint32: 帧状态
|
||||
toolFrameOffReqID = 8 // uint64: 请求 ID
|
||||
toolFrameOffName = 16 // [16, 80): 工具名(64B)
|
||||
toolFrameOffInput = 80 // SharedRef(16): input 描述符
|
||||
toolFrameOffOutput = 96 // SharedRef(16): output 描述符
|
||||
toolFrameSize = 112 // 单帧总大小
|
||||
|
||||
// 帧状态
|
||||
toolFrameFree uint32 = 0
|
||||
toolFrameCalling uint32 = 1 // 内核写 input,插件待读
|
||||
toolFrameReading uint32 = 2 // 插件正在执行
|
||||
toolFrameReady uint32 = 3 // 插件写 output,内核待读
|
||||
)
|
||||
|
||||
// ToolRing 头偏移(相对区域起始)
|
||||
const (
|
||||
trlOffMagic uint32 = 0
|
||||
trlOffVersion uint32 = 4
|
||||
trlOffCap uint32 = 8
|
||||
trlOffFrameSize uint32 = 12
|
||||
trlOffFrameBase uint32 = 16
|
||||
)
|
||||
|
||||
var (
|
||||
ErrToolCallRingFull = errors.New("proc: tool call ring full")
|
||||
ErrToolCallNotFound = errors.New("proc: tool call frame not found")
|
||||
)
|
||||
|
||||
// toolFrame 是单个帧的内存布局。
|
||||
type toolFrame struct {
|
||||
state uint32 // atomic
|
||||
reqID uint64
|
||||
name [toolFrameNameLen]byte
|
||||
input SharedRef
|
||||
output SharedRef
|
||||
}
|
||||
|
||||
// ToolCallRing 内核侧的 ToolCall ring 视图。
|
||||
type ToolCallRing struct {
|
||||
data []byte
|
||||
cap uint32
|
||||
frameSize uint32
|
||||
framesBase uint32
|
||||
writeIdx atomic.Uint64 // 下一个可写帧 index
|
||||
gen *uint32 // 统一区域 generation(变化则 snapshot 失效)
|
||||
}
|
||||
|
||||
// InitToolRing 在统一区域的 ToolCall 子区域上初始化 ring header。
|
||||
func InitToolRing(data []byte) error {
|
||||
if uint32(len(data)) < trlOffFrameBase+toolRingCap*toolFrameSize {
|
||||
return fmt.Errorf("tool ring: 区域过小(%d 字节,至少需要 %d)",
|
||||
len(data), trlOffFrameBase+toolRingCap*toolFrameSize)
|
||||
}
|
||||
|
||||
putU32(data[trlOffMagic:], toolRingMagic)
|
||||
putU32(data[trlOffVersion:], toolRingVersion)
|
||||
putU32(data[trlOffCap:], toolRingCap)
|
||||
putU32(data[trlOffFrameSize:], toolFrameSize)
|
||||
|
||||
// 初始化所有帧为 FREE
|
||||
for i := uint32(0); i < toolRingCap; i++ {
|
||||
frameOff := trlOffFrameBase + i*toolFrameSize
|
||||
putU32(data[frameOff+toolFrameOffState:], toolFrameFree)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachToolRing 挂载已初始化的 ToolCall ring(插件侧调用)。
|
||||
func AttachToolRing(data []byte) (*ToolCallRing, error) {
|
||||
if uint32(len(data)) < trlOffFrameBase {
|
||||
return nil, fmt.Errorf("tool ring: 区域过小")
|
||||
}
|
||||
magic := getU32(data[trlOffMagic:])
|
||||
if magic != toolRingMagic {
|
||||
return nil, fmt.Errorf("tool ring: 魔数不匹配(0x%x)", magic)
|
||||
}
|
||||
return &ToolCallRing{
|
||||
data: data,
|
||||
cap: getU32(data[trlOffCap:]),
|
||||
frameSize: getU32(data[trlOffFrameSize:]),
|
||||
framesBase: trlOffFrameBase,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reserve 为一次工具调用预留帧(内核调用)。
|
||||
//
|
||||
// 环形扫描:从 writeIdx 开始,绕环一圈找 FREE 帧。全部忙时背压。
|
||||
func (r *ToolCallRing) Reserve() (frameIdx uint32, err error) {
|
||||
start := r.writeIdx.Load()
|
||||
for i := uint64(0); i < uint64(r.cap); i++ {
|
||||
idx := start + i
|
||||
fi := uint32(idx % uint64(r.cap))
|
||||
state := atomic.LoadUint32(r.statePtr(fi))
|
||||
if state == toolFrameFree {
|
||||
r.writeIdx.Store(idx + 1)
|
||||
return fi, nil
|
||||
}
|
||||
}
|
||||
return 0, ErrToolCallRingFull
|
||||
}
|
||||
|
||||
// SetCalling 将帧状态设为 CALLING 并写入 requestID 和工具名(内核调用)。
|
||||
func (r *ToolCallRing) SetCalling(frameIdx uint32, reqID uint64, toolName string, inputRef SharedRef) {
|
||||
off := r.frameOff(frameIdx)
|
||||
// 写 requestID
|
||||
putU64(r.data[off+toolFrameOffReqID:], reqID)
|
||||
// 写工具名(截断到 64B)
|
||||
nameBytes := []byte(toolName)
|
||||
if len(nameBytes) > int(toolFrameNameLen) {
|
||||
nameBytes = nameBytes[:toolFrameNameLen]
|
||||
}
|
||||
for i := range r.data[off+toolFrameOffName : off+toolFrameOffName+toolFrameNameLen] {
|
||||
r.data[off+toolFrameOffName+uint32(i)] = 0
|
||||
}
|
||||
copy(r.data[off+toolFrameOffName:], nameBytes)
|
||||
// 写 input SharedRef
|
||||
packSharedRef(r.data[off+toolFrameOffInput:], inputRef)
|
||||
// 原子设置状态为 CALLING
|
||||
atomic.StoreUint32(r.statePtr(frameIdx), toolFrameCalling)
|
||||
}
|
||||
|
||||
// FindFrameByReqID 查找对应 requestID 的帧(插件调用)。
|
||||
// 返回帧索引和帧内偏移数据,或 ErrToolCallNotFound。
|
||||
func (r *ToolCallRing) FindFrameByReqID(reqID uint64) (uint32, error) {
|
||||
for i := uint32(0); i < r.cap; i++ {
|
||||
off := r.frameOff(i)
|
||||
state := atomic.LoadUint32(r.statePtr(i))
|
||||
if state == toolFrameCalling || state == toolFrameReading {
|
||||
fid := getU64(r.data[off+toolFrameOffReqID:])
|
||||
if fid == reqID {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, ErrToolCallNotFound
|
||||
}
|
||||
|
||||
// GetCallingFrame 读取帧的工具名和 input 描述符(插件侧,正在 CALLING 的帧)。
|
||||
func (r *ToolCallRing) GetCallingFrame(frameIdx uint32) (name string, inputRef SharedRef) {
|
||||
off := r.frameOff(frameIdx)
|
||||
nameBytes := r.data[off+toolFrameOffName : off+toolFrameOffName+toolFrameNameLen]
|
||||
// 去尾零
|
||||
n := len(nameBytes)
|
||||
for n > 0 && nameBytes[n-1] == 0 {
|
||||
n--
|
||||
}
|
||||
name = string(nameBytes[:n])
|
||||
inputRef = unpackSharedRef(r.data[off+toolFrameOffInput:])
|
||||
return
|
||||
}
|
||||
|
||||
// SetReading 将帧状态设为 READING(插件开始执行)。
|
||||
func (r *ToolCallRing) SetReading(frameIdx uint32) {
|
||||
atomic.StoreUint32(r.statePtr(frameIdx), toolFrameReading)
|
||||
}
|
||||
|
||||
// SetReady 将帧状态设为 READY 并写入 output 描述符(插件执行完毕)。
|
||||
func (r *ToolCallRing) SetReady(frameIdx uint32, outputRef SharedRef) {
|
||||
off := r.frameOff(frameIdx)
|
||||
packSharedRef(r.data[off+toolFrameOffOutput:], outputRef)
|
||||
atomic.StoreUint32(r.statePtr(frameIdx), toolFrameReady)
|
||||
}
|
||||
|
||||
// GetReadyFrame 读取帧的 requestID 和 output 描述符(内核消费 READY 帧)。
|
||||
func (r *ToolCallRing) GetReadyFrame(frameIdx uint32) (reqID uint64, outputRef SharedRef) {
|
||||
off := r.frameOff(frameIdx)
|
||||
reqID = getU64(r.data[off+toolFrameOffReqID:])
|
||||
outputRef = unpackSharedRef(r.data[off+toolFrameOffOutput:])
|
||||
return
|
||||
}
|
||||
|
||||
// ReleaseFrame 回收帧为 FREE(内核消费完毕后调用)。
|
||||
func (r *ToolCallRing) ReleaseFrame(frameIdx uint32) {
|
||||
off := r.frameOff(frameIdx)
|
||||
// 清零帧内容
|
||||
for i := uint32(0); i < r.frameSize; i++ {
|
||||
r.data[off+i] = 0
|
||||
}
|
||||
atomic.StoreUint32(r.statePtr(frameIdx), toolFrameFree)
|
||||
}
|
||||
|
||||
func (r *ToolCallRing) frameOff(idx uint32) uint32 {
|
||||
return r.framesBase + idx*r.frameSize
|
||||
}
|
||||
|
||||
func (r *ToolCallRing) statePtr(idx uint32) *uint32 {
|
||||
off := r.framesBase + idx*r.frameSize + toolFrameOffState
|
||||
return (*uint32)(unsafe.Pointer(
|
||||
uintptr(unsafe.Pointer(&r.data[0])) + uintptr(off),
|
||||
))
|
||||
}
|
||||
|
||||
func packSharedRef(b []byte, ref SharedRef) {
|
||||
putU32(b[0:], ref.Offset)
|
||||
putU32(b[4:], ref.Length)
|
||||
putU32(b[8:], ref.Generation)
|
||||
putU32(b[12:], ref.Flags)
|
||||
}
|
||||
|
||||
func unpackSharedRef(b []byte) SharedRef {
|
||||
return SharedRef{
|
||||
Offset: getU32(b[0:]),
|
||||
Length: getU32(b[4:]),
|
||||
Generation: getU32(b[8:]),
|
||||
Flags: getU32(b[12:]),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user