mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 09:28:14 +00:00
Part 1 加载分派骨架(迁移可逐插件推进、随时回退的前提):
- dynamic.go: 新增 binEntry/skillEntry 常量 + entryKind 枚举 + classifyEntry/detectEntryKind
manifest entry 优先级最高(改回 plugin.so 即回退 cabi);无 manifest 时按目录探测,.bin 优先
- registry.go: tryDynamic 按 entry 分派 proc/cabi 双通道;
entry 声明 .bin 但二进制缺失时报明确错误,不静默回退(否则'已迁移插件跑回旧通道'极难排查)
- registry.go: pluginEntryHash 候选顺序与 detectEntryKind 对齐(.bin 优先),
否则增量重载会用错文件算 hash
- dynamic_proc_{unix,windows}.go: tryLoadProc 桩位(权限/类型校验已实现,进程管理属 Part 2)
Part 4 共享内存数据面(迁移评估 §3.3/§3.4/§3.7,最关键一环):
- proc/shm.go: 段布局(Header + ShmStageCtx 描述符数组 + append-only arena)
相对偏移设计——各进程 mmap 到不同虚拟地址仍能正确解引用
arena 用尽显式报错而非静默截断(§4.4 风险登记);Compact() 回收 append-only 垃圾
- proc/shmcodec.go: StageContext 16 字段跨进程编解码
字段级描述符消除 lost update:只改 FinalText 的插件不触碰 ToolResults 描述符
WriteDirty 只写脏字段——只读插件零写入,不可能覆盖他人改写
Snapshot 存序列化字符串(切片共享底层数组的坑,C ABI 侧修 11.3 时已踩过)
Extra 4 键提升为具名字段;Response 用标志位表达 nil vs 空串
- proc/lock.go: 锁仲裁回归内核(§3.7 已裁定,零 cgo)
ForceRelease 实现实验 9 的崩溃自愈——排除 robust pthread_mutex 必要性
重复加锁显式拒绝(否则死锁 30s);等待超时有补偿 goroutine 防锁泄漏
验证:
- proc 包 16 项测试全绿(含 -race):全字段往返/只读零写回/原地改切片识别/
现网 sanitizer+weather 场景/5插件×40轮并发零丢失/arena 耗尽报错/压实不破坏字段/
锁互斥·串扰拒绝·崩溃自愈·临界区串行化
- entry 分派 9 项测试全绿;go build ./... exit 0;接口冻结 git diff sdk/ 为空
185 lines
4.3 KiB
Go
185 lines
4.3 KiB
Go
package proc
|
||
|
||
import (
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// 锁仲裁回归内核(§3.7 已裁定)的行为验证,含实验 9 的崩溃自愈机制。
|
||
|
||
func TestStageLock_MutualExclusion(t *testing.T) {
|
||
l := newStageLock()
|
||
|
||
if err := l.Acquire("A"); err != nil {
|
||
t.Fatalf("A 应能获得锁: %v", err)
|
||
}
|
||
if l.Owner() != "A" {
|
||
t.Errorf("Owner 应为 A,实际 %q", l.Owner())
|
||
}
|
||
|
||
// B 在 A 持锁期间不得进入
|
||
entered := make(chan struct{})
|
||
go func() {
|
||
_ = l.Acquire("B")
|
||
close(entered)
|
||
}()
|
||
select {
|
||
case <-entered:
|
||
t.Fatal("A 持锁期间 B 不应获得锁")
|
||
case <-time.After(50 * time.Millisecond):
|
||
}
|
||
|
||
if err := l.Release("A"); err != nil {
|
||
t.Fatalf("A 释放失败: %v", err)
|
||
}
|
||
select {
|
||
case <-entered:
|
||
case <-time.After(2 * time.Second):
|
||
t.Fatal("A 释放后 B 应获得锁")
|
||
}
|
||
if l.Owner() != "B" {
|
||
t.Errorf("Owner 应为 B,实际 %q", l.Owner())
|
||
}
|
||
_ = l.Release("B")
|
||
}
|
||
|
||
// 非持锁者不得释放他人的锁(防止串扰导致并发正确性被破坏)。
|
||
func TestStageLock_ReleaseByNonOwnerRejected(t *testing.T) {
|
||
l := newStageLock()
|
||
if err := l.Acquire("A"); err != nil {
|
||
t.Fatalf("Acquire: %v", err)
|
||
}
|
||
defer l.Release("A")
|
||
|
||
err := l.Release("B")
|
||
if err == nil {
|
||
t.Fatal("非持锁者释放应被拒绝")
|
||
}
|
||
if !strings.Contains(err.Error(), "试图释放") {
|
||
t.Errorf("错误信息应说明串扰,实际: %v", err)
|
||
}
|
||
if l.Owner() != "A" {
|
||
t.Errorf("A 应仍持锁,实际 owner=%q", l.Owner())
|
||
}
|
||
}
|
||
|
||
func TestStageLock_ReleaseWithoutHoldRejected(t *testing.T) {
|
||
l := newStageLock()
|
||
if err := l.Release("A"); err == nil {
|
||
t.Fatal("未持锁时释放应报错")
|
||
}
|
||
}
|
||
|
||
// handler 内嵌套加锁会死锁,应显式拒绝而不是让插件挂死到超时。
|
||
func TestStageLock_ReentrantAcquireRejected(t *testing.T) {
|
||
l := newStageLock()
|
||
if err := l.Acquire("A"); err != nil {
|
||
t.Fatalf("Acquire: %v", err)
|
||
}
|
||
defer l.Release("A")
|
||
|
||
err := l.Acquire("A")
|
||
if err == nil {
|
||
t.Fatal("同一插件重复加锁应被拒绝(否则死锁 30s)")
|
||
}
|
||
if !strings.Contains(err.Error(), "重复申请") {
|
||
t.Errorf("错误信息应说明重复加锁,实际: %v", err)
|
||
}
|
||
}
|
||
|
||
// 实验 9 的核心:持锁进程崩溃后内核代为释放,后续插件不死锁。
|
||
// 这条彻底排除了 robust pthread_mutex 的必要性 —— 整个架构零 cgo。
|
||
func TestStageLock_ForceReleaseOnPluginCrash(t *testing.T) {
|
||
l := newStageLock()
|
||
|
||
// 插件 X 拿锁后"崩溃"(不调用 Release)
|
||
if err := l.Acquire("X"); err != nil {
|
||
t.Fatalf("X Acquire: %v", err)
|
||
}
|
||
if !l.ForceRelease("X") {
|
||
t.Fatal("内核应能强制释放崩溃插件持有的锁")
|
||
}
|
||
if l.Owner() != "" {
|
||
t.Errorf("强制释放后应无持有者,实际 %q", l.Owner())
|
||
}
|
||
|
||
// 插件 Y 随后必须能正常拿到锁(无死锁)
|
||
done := make(chan error, 1)
|
||
go func() { done <- l.Acquire("Y") }()
|
||
select {
|
||
case err := <-done:
|
||
if err != nil {
|
||
t.Fatalf("Y 应能获得锁: %v", err)
|
||
}
|
||
case <-time.After(2 * time.Second):
|
||
t.Fatal("X 崩溃后 Y 无法获得锁 —— 出现死锁")
|
||
}
|
||
if err := l.Release("Y"); err != nil {
|
||
t.Fatalf("Y 释放失败: %v", err)
|
||
}
|
||
}
|
||
|
||
// ForceRelease 对非持有者/未持锁应为 no-op,不能误放他人的锁。
|
||
func TestStageLock_ForceReleaseIsTargeted(t *testing.T) {
|
||
l := newStageLock()
|
||
if err := l.Acquire("A"); err != nil {
|
||
t.Fatalf("Acquire: %v", err)
|
||
}
|
||
defer l.Release("A")
|
||
|
||
if l.ForceRelease("B") {
|
||
t.Error("强制释放不该动 A 持有的锁")
|
||
}
|
||
if l.Owner() != "A" {
|
||
t.Errorf("A 应仍持锁,实际 %q", l.Owner())
|
||
}
|
||
}
|
||
|
||
// 高并发下锁的串行化保证:临界区不重叠。
|
||
func TestStageLock_SerializesCriticalSection(t *testing.T) {
|
||
l := newStageLock()
|
||
var (
|
||
mu sync.Mutex
|
||
inside int
|
||
maxSeen int
|
||
)
|
||
const workers = 8
|
||
const iters = 50
|
||
|
||
var wg sync.WaitGroup
|
||
for i := 0; i < workers; i++ {
|
||
wg.Add(1)
|
||
go func(id int) {
|
||
defer wg.Done()
|
||
name := string(rune('A' + id))
|
||
for j := 0; j < iters; j++ {
|
||
if err := l.Acquire(name); err != nil {
|
||
t.Errorf("Acquire: %v", err)
|
||
return
|
||
}
|
||
mu.Lock()
|
||
inside++
|
||
if inside > maxSeen {
|
||
maxSeen = inside
|
||
}
|
||
mu.Unlock()
|
||
|
||
mu.Lock()
|
||
inside--
|
||
mu.Unlock()
|
||
if err := l.Release(name); err != nil {
|
||
t.Errorf("Release: %v", err)
|
||
return
|
||
}
|
||
}
|
||
}(i)
|
||
}
|
||
wg.Wait()
|
||
|
||
if maxSeen > 1 {
|
||
t.Fatalf("临界区出现并发:同时 %d 个持有者", maxSeen)
|
||
}
|
||
}
|