Files
HomeAgent/internal/plugin/proc/toollane.go
JianFeeeee a8e3633174 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
2026-09-10 12:33:58 +08:00

250 lines
8.0 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.

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 (内核写 inputRefeventfd 通知插件)
// -> 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:]),
}
}