Files
HomeAgent/internal/plugin/proc/testdata/appendplugin.go
JianFeeeee ad016e4410 feat(shm): 统一共享内存区域(§13.1) — 单 memfd 容纳 SuperBlock+StageContext+EvtRing
- unified.go: SuperBlock 布局 + SharedRef 类型
- NewHost(): 两段 memfd 合并为单一 memfd,fd 3 = 统一区域,fd 4 = eventfd
- 子进程侧: testdata 插件从 SuperBlock 解析 ctxOff/evtOff
- streaming 测试: EvtConsumer 协程在 host.Close 前 Stop+Wait 防 SIGSEGV
2026-09-10 11:42:22 +08:00

236 lines
5.4 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.

//go:build ignore
// appendplugin 在 stage 中把自己的标记追加到 FinalText读-改-写)。
//
// 用于跨进程 lost update 验证:多个此类插件并发处理同一 stage
// 若全部标记都保留 ⇒ 无丢失;若少了 ⇒ 出现 lost update。
//
// 这是实验 85 进程 × 300 轮零丢失)在真实 RPC + 真实内核 RunStage
// 下的复刻——机制单测已过,这里验证集成后同样成立。
package main
import (
"bufio"
"encoding/binary"
"encoding/json"
"fmt"
"os"
"sync"
"syscall"
)
type request struct {
ID uint64 `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
type response struct {
ID uint64 `json:"id"`
Result interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
var (
out = bufio.NewWriter(os.Stdout)
writeMu sync.Mutex
nextID uint64
pendMu sync.Mutex
pending = map[uint64]chan json.RawMessage{}
shm []byte
tag string
)
func send(v interface{}) {
b, _ := json.Marshal(v)
writeMu.Lock()
out.Write(b)
out.WriteByte('\n')
out.Flush()
writeMu.Unlock()
}
func callKernel(method string, params interface{}) (json.RawMessage, bool) {
pendMu.Lock()
nextID++
id := nextID
ch := make(chan json.RawMessage, 1)
pending[id] = ch
pendMu.Unlock()
var raw json.RawMessage
if params != nil {
b, _ := json.Marshal(params)
raw = b
}
send(request{ID: id, Method: method, Params: raw})
r, ok := <-ch
return r, ok
}
const (
headerSize = 64
stageFieldCount = 18
sliceSize = 8
flagCount = 8
offArenaBase = 8
offArenaCap = 12
offArenaUsed = 16
offCtxBase = 20
offSeq = 24
fFinalText = 5 // 与 proc/shmcodec.go 的 stageField 枚举顺序一致
)
func desc(field int) (uint32, uint32) {
cb := binary.LittleEndian.Uint32(shm[offCtxBase:])
o := cb + uint32(field*sliceSize)
return binary.LittleEndian.Uint32(shm[o:]), binary.LittleEndian.Uint32(shm[o+4:])
}
func setDesc(field int, off, ln uint32) {
cb := binary.LittleEndian.Uint32(shm[offCtxBase:])
o := cb + uint32(field*sliceSize)
binary.LittleEndian.PutUint32(shm[o:], off)
binary.LittleEndian.PutUint32(shm[o+4:], ln)
}
func readFinalText() string {
off, ln := desc(fFinalText)
if off == 0 && ln == 0 {
return ""
}
if ln == 0 {
return ""
}
base := binary.LittleEndian.Uint32(shm[offArenaBase:])
return string(shm[base+off : base+off+ln])
}
func writeFinalText(s string) error {
used := binary.LittleEndian.Uint32(shm[offArenaUsed:])
if used == 0 {
used = 1
}
cap_ := binary.LittleEndian.Uint32(shm[offArenaCap:])
if used+uint32(len(s)) > cap_ {
return fmt.Errorf("arena 空间不足")
}
base := binary.LittleEndian.Uint32(shm[offArenaBase:])
copy(shm[base+used:], []byte(s))
binary.LittleEndian.PutUint32(shm[offArenaUsed:], used+uint32(len(s)))
setDesc(fFinalText, used, uint32(len(s)))
// 世代号自增
v := binary.LittleEndian.Uint64(shm[offSeq:])
binary.LittleEndian.PutUint64(shm[offSeq:], v+1)
return nil
}
func main() {
tag = os.Getenv("PLUGIN_TAG")
if tag == "" {
tag = "?"
}
in := bufio.NewScanner(bufio.NewReader(os.Stdin))
in.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for in.Scan() {
line := make([]byte, len(in.Bytes()))
copy(line, in.Bytes())
var probe struct {
ID uint64 `json:"id"`
Method string `json:"method"`
}
if json.Unmarshal(line, &probe) != nil {
continue
}
if probe.Method == "" {
var resp struct {
ID uint64 `json:"id"`
Result json.RawMessage `json:"result"`
}
json.Unmarshal(line, &resp)
pendMu.Lock()
ch, ok := pending[resp.ID]
delete(pending, resp.ID)
pendMu.Unlock()
if ok {
ch <- resp.Result
}
continue
}
var req request
json.Unmarshal(line, &req)
switch req.Method {
case "handshake":
var hp struct {
ShmSize int `json:"shm_size"`
}
json.Unmarshal(req.Params, &hp)
if hp.ShmSize > 0 {
m, err := syscall.Mmap(3, 0, hp.ShmSize,
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
if err != nil {
send(response{ID: req.ID, Error: fmt.Sprintf("mmap: %v", err)})
continue
}
// 统一区域:前 64B 是 SuperBlockStageContext 段在其后
ctxOff := binary.LittleEndian.Uint32(m[20:])
ctxSize := binary.LittleEndian.Uint32(m[24:])
shm = m[ctxOff : ctxOff+ctxSize]
}
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 1, "sdk_version": "test",
"plugin_name": "append-" + tag, "pid": os.Getpid(),
}})
case "plugin.init":
send(response{ID: req.ID})
case "plugin.start":
go func(id uint64) {
callKernel("stage.register", map[string]interface{}{
"stage": "after_toolcall",
"scope": "global",
})
send(response{ID: id})
}(req.ID)
case "plugin.stop":
send(response{ID: req.ID})
out.Flush()
os.Exit(0)
case "stage.invoke":
go func(id uint64) {
if shm == nil {
send(response{ID: id, Error: "共享段未挂载"})
return
}
// 拿锁 → 读 → 追加自己的标记 → 写回 → 放锁
callKernel("stage.lock", nil)
cur := readFinalText()
err := writeFinalText(cur + tag)
callKernel("stage.unlock", nil)
if err != nil {
send(response{ID: id, Error: err.Error()})
return
}
send(response{ID: id, Result: map[string]interface{}{"dirty_fields": 1}})
}(req.ID)
default:
if req.ID != 0 {
send(response{ID: req.ID})
}
}
}
}