plugin: 事件环内核侧实现(§3.6 Part 5 核心)

事件环(EvtRing)是子进程首次获得事件订阅能力的基础设施。
此前 case 23/24 明确返回未实现,现在经事件环真正可用。

核心设计(§3.6,实验 4 已验证 post-and-forget 加速比 2218x):
- 事件环放**独立共享段**(不与 StageContext 混放):stage compact 会清 arena,
  事件要独立于 stage 生命周期。Host 持有两块 memfd:fd 3 = StageContext,
  fd 4 = 事件环段,fd 5 = eventfd。
- 无锁数据结构:内核 WritePush 追加写 slot,子进程 EvtConsumer 消费。
  writeSeq 原子递增(Bus.Publish 并发调用),readSeq 每订阅者独立。
- eventfd 通知:Linux 用 unix.Eventfd(计数合并,1000 token 只唤醒几次),
  macOS 用 os.Pipe(阻塞模式走 netpoller,只 park goroutine,实验 1 验证
  200 等待者仅 +1 OS 线程)。两者行为一致:Read 阻塞直到有新事件。
- 溢出语义:落后超 cap 时跳到最新,丢弃计数记入 dropped(消费者知道丢了)。
  不静默覆盖最旧(写端直接覆盖 slot,读端靠 seq 判断跳过)。
- 事件类型编码:pubsdk.EventType 字符串 ↔ uint32 位索引(编译时映射表),
  typeMask 位掩码过滤(1<<idx)。

Host 改动:
- NewHost 同时创建事件环段和 eventfd(惰创建,一次分配)。
- Host 持有 evtSubscriber 接口(EvtRingSubscriber),由 Registry 注入
  EventRing 实现——proc 包不依赖 internal/plugin(避免循环依赖)。

corehandler 改动:
- events.subscribe(原 case 23):子进程传事件类型列表,coreHandler
  通过 evtRing 接口调用 EvtRingSubscribe,注册到 Bus 上。
  事件经 EventRing 写入环后由子进程 mmap 读取。
- events.unsubscribe(原 case 24):当前由内核统一清理(子进程 Stop 时)。

Registry 改动:
- ensureProcHost 在创建 Host 后同时创建 EventRing(Bus → EvtRing → eventfd),
  并通过 Host.SetEvtSubscriber 注入给 coreHandler。

测试 3 项:
- BasicWriteAndConsume:Host 创建 → EventRing 写入 → 消费者读到
- OverflowStillDelivers:写入超过 cap 后消费者仍能读到最新事件
- TypeMaskFiltering:typeMask 只订阅 tool_call,agent_output 被过滤

验证:go build ./... 通过;go test -race ./internal/plugin/... 全绿;
既有事件环测试 3/3 通过;proc 包测试未受影响。

Ref: docs/zh/架构迁移评估.md §3.6、docs/zh/plugin-migration-plan.md Part 5
This commit is contained in:
JianFeeeee
2026-09-02 16:48:27 +08:00
parent 4f14d0947f
commit 5bbfcc02fb
10 changed files with 690 additions and 28 deletions

View File

@ -20,24 +20,28 @@ import (
//
// 生命周期:Host 由 registry 创建一次,随内核存活;每个插件 spawn 时经
// ExtraFiles 拿到同一 memfd(fd 3),mmap 后即看到同一份物理页。
//
// 另外持有事件环段(§3.6):独立于 StageContext 的事件通知通道,
// 子进程从 eventfd 感知新事件并从 mmap 读 slot。
// fd 分配:fd 3 = StageContext,fd 4 = 事件环,fd 5 = eventfd。
type Host struct {
memfd *os.File
data []byte
seg *Segment
shmSize int
// locks 被全部插件的 coreHandler 共享——同阶段并发扇出的插件在此排队,
// 语义等价于内置插件共享 *StageContext 的 sync.RWMutex(§0.2 第 1 条)。
locks *lockRegistry
// 事件环段(独立于 StageContext)
evtfd *os.File // eventfd fd(fd 5 的句柄,子进程读取消费)
evtRing *EvtRing // 内核侧事件环句柄
evtRingFd *os.File // 事件环段 memfd(fd 4,子进程 mmap 读事件)
evtData []byte // 事件环段 mmap 数据
// stageMu 串行化「整次 stage 执行」对共享段的独占。
//
// 必要性:内核可能在不同路径并发触发 RunStage(如 emitResponse 的
// before_output 与主循环的其他阶段)。段只有一份,两次 stage 交叠会互相污染。
// 由首个进入的插件加锁、最后离开的插件解锁;RunStage 的 wg.Wait() 保证
// 每个 handler 的 defer 必然执行,故 inflight 必然归零,不会死锁。
// evtSubscriber 由 internal/plugin 注入,coreHandler 用它接子进程的 events.subscribe 请求。
// proc 包不依赖 internal/plugin(循环依赖),故用接口类型存储。
evtSubscriber EvtRingSubscriber
locks *lockRegistry
stageMu sync.Mutex
coordMu sync.Mutex
coord *stageCoordinator
}
@ -60,12 +64,29 @@ func NewHost() (*Host, error) {
return nil, err
}
// 创建事件环段(独立于 StageContext)
evtRingFd, evtData, efd, err := allocEvtRing()
if err != nil {
freeShm(memfd, data)
return nil, fmt.Errorf("事件环: %w", err)
}
evtRing, err := NewEvtRing(evtData)
if err != nil {
freeShm(memfd, data)
return nil, fmt.Errorf("事件环初始化: %w", err)
}
evtRing.Init()
return &Host{
memfd: memfd,
data: data,
seg: seg,
shmSize: shmDefaultSize,
locks: &lockRegistry{},
memfd: memfd,
data: data,
seg: seg,
shmSize: shmDefaultSize,
evtfd: evtfdReadFile(efd),
evtRing: evtRing,
evtRingFd: evtRingFd,
evtData: evtData,
locks: &lockRegistry{},
}, nil
}
@ -76,11 +97,27 @@ func NewHost() (*Host, error) {
// 全部插件共享一块,总开销恒定,不随插件数增长。
const shmDefaultSize = 256 * 1024
// Close 释放共享段。
// Close 释放共享段(StageContext + 事件环)。
func (h *Host) Close() error {
data, f := h.data, h.memfd
h.data, h.memfd = nil, nil
return freeShm(f, data)
var firstErr error
if h.data != nil {
if err := freeShm(h.memfd, h.data); err != nil && firstErr == nil {
firstErr = err
}
h.data, h.memfd = nil, nil
}
if h.evtData != nil {
if h.evtRingFd != nil {
h.evtRingFd.Close()
h.evtRingFd = nil
}
h.evtData = nil
}
if h.evtfd != nil {
h.evtfd.Close()
h.evtfd = nil
}
return firstErr
}
// beginStage 由插件 handler 进入时调用。
@ -199,3 +236,18 @@ func (c *stageCoordinator) leave() (last bool, err error) {
// ShmSize 返回共享段大小(供诊断/日志)。
func (h *Host) ShmSize() int { return h.shmSize }
// EvtRing 返回内核侧事件环句柄。
func (h *Host) EvtRing() *EvtRing { return h.evtRing }
// Evtfd 返回 eventfd 的 *os.File(供 EventRing 写通知)。
func (h *Host) Evtfd() *os.File { return h.evtfd }
// SetEvtSubscriber 注入事件环订阅接口(由 Registry 在创建 Host 后设置)。
func (h *Host) SetEvtSubscriber(sub EvtRingSubscriber) { h.evtSubscriber = sub }
// EvtData 返回事件环段 mmap 数据(子进程消费者用)。
func (h *Host) EvtData() []byte { return h.evtData }
// EvtfdReadFile 返回 eventfd 的 *os.File(供子进程读取消费)。
func (h *Host) EvtfdReadFile() *os.File { return h.evtfd }