mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
批 1,不含 SDK 仓与目录改名。 - go.mod 第 1 行 module 路径 - 366 个 .go 文件的完整前缀 github.com/JianFeeeee/HomeAgent 机械替换 (857 处,仅完整前缀;展示名/注释里的 HomeAgent 字样留给批 5) - 未改:third_party 目录名、SDK 自身 go.mod module、go.mod replace 目标、 homed/waiter 可执行名、.hmap 后缀
169 lines
5.8 KiB
Go
169 lines
5.8 KiB
Go
package proc
|
||
|
||
import (
|
||
"fmt"
|
||
safeclose "github.com/JianFeeeee/HologramAgent/internal/safeclose"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 跨进程锁:锁仲裁回归内核(§3.7 已裁定,实验 3 + 实验 9 支撑)。
|
||
//
|
||
// 为什么不用 robust pthread_mutex:
|
||
// - PTHREAD_PROCESS_SHARED + ROBUST 属性 Go 标准库无等价物,引入它意味着
|
||
// **为了一把锁保留 cgo**——与"C 整体退场"的目标冲突。
|
||
// - 锁仲裁回内核后,持锁进程崩溃由 cmd.Wait()/stdio EOF 检测,内核代为释放;
|
||
// 实验 9 实测无死锁、**无需 EOWNERDEAD 处理**。
|
||
// - 成本:一次 RPC 往返 19.4 µs(实验 3,20000 次测得)。stage handler 的加锁
|
||
// 频率很低(每次 stage 一两次,不是每字段一次),微秒级往返可忽略。
|
||
//
|
||
// 于是整个新架构可做到**完全无 cgo**。
|
||
|
||
// lockWaitTimeout 是插件申请 stage 锁的最长等待时间。
|
||
//
|
||
// 取 30s:stage handler 自身受 60s 工具超时约束(toolcall.go),锁等待
|
||
// 必须显著短于它,否则超时错误会指向错误的原因。超时返回错误而非
|
||
// 静默继续——**持锁失败下改写共享段会破坏并发正确性**。
|
||
const lockWaitTimeout = 30 * time.Second
|
||
|
||
// stageLock 是内核侧为单个 stage 执行持有的互斥体。
|
||
//
|
||
// 一次 RunStage 对应一个 stageLock 实例:同阶段并发扇出的所有插件
|
||
// (含跨进程的)在此排队,语义等价于今日内置插件共享
|
||
// *StageContext 的 sync.RWMutex——这正是"保留并发扇出原始设计"
|
||
// (§0.2 第 1 条:并发扇出是原始设计,不是缺陷)。
|
||
type stageLock struct {
|
||
mu sync.Mutex
|
||
|
||
// ownerMu 保护 owner/held,使 ForceRelease 能安全介入
|
||
ownerMu sync.Mutex
|
||
owner string // 当前持锁的插件名,空表示未持有
|
||
held bool
|
||
// depth 是**重入深度**:同一插件嵌套进入同一临界区的次数。
|
||
//
|
||
// 为什么需要它(2026-10-07 修):见 Acquire 的注释 —— 同一插件
|
||
// 可在同一 stage 注册多个 handler(内核 RegisterStageFor 用 append),
|
||
// 它们并发扇出各发一次 stage.lock,第二个进来时就是同插件重入。
|
||
depth int
|
||
}
|
||
|
||
func newStageLock() *stageLock { return &stageLock{} }
|
||
|
||
// Acquire 为 plugin 申请写锁,带超时。
|
||
//
|
||
// # 同插件重入:直接通过(2026-10-07 改)
|
||
//
|
||
// 三个版本的设计与代价(都实测过):
|
||
//
|
||
// 1. 重入即报错 —— 生产 93 条 qq 报错,第二个 handler 永不执行
|
||
// (对 qq 而言那是权限门失效)。
|
||
// 2. 重入即等待 —— 同进程重入是自己等自己,30s 超时死锁。**更糟**:
|
||
// 我先实现了这版,跑判据立刻挂 30s,教训是「等待」不是重入的正解。
|
||
// 3. 重入即通过(当前)—— 可重入锁语义。调用方是「插件的一个 stage
|
||
// handler」,而内核 RegisterStageFor 用 append:**同一插件可以在同一
|
||
// stage 注册多个 handler**,它们并发扇出、各发一次 stage.lock。
|
||
// 同一进程内第二个进来时已在临界区内(同一进程只能串行跑),
|
||
// 本就不该再取互斥量;Release 到 depth 0 才真正释放。
|
||
//
|
||
// 不同插件的互斥语义**完全不变**:仍需排队等待。
|
||
func (l *stageLock) Acquire(plugin string) error {
|
||
// 可重入:已持有者直接进临界区(不取 mu、不排队)
|
||
l.ownerMu.Lock()
|
||
if l.held && l.owner == plugin {
|
||
l.depth++
|
||
l.ownerMu.Unlock()
|
||
return nil
|
||
}
|
||
l.ownerMu.Unlock()
|
||
|
||
acquired := make(chan struct{})
|
||
var acquiredOnce sync.Once
|
||
go func() {
|
||
l.mu.Lock()
|
||
safeclose.Safe(&acquiredOnce, acquired)
|
||
}()
|
||
|
||
select {
|
||
case <-acquired:
|
||
l.ownerMu.Lock()
|
||
l.owner = plugin
|
||
l.held = true
|
||
l.depth = 1
|
||
l.ownerMu.Unlock()
|
||
return nil
|
||
case <-time.After(lockWaitTimeout):
|
||
// 等待超时:上面的 goroutine 可能随后拿到锁,必须让它能释放,
|
||
// 否则锁永久泄漏。用一个补偿 goroutine 等它拿到后立刻放掉。
|
||
go func() {
|
||
<-acquired
|
||
l.ownerMu.Lock()
|
||
stillFree := !l.held
|
||
l.ownerMu.Unlock()
|
||
if stillFree {
|
||
l.mu.Unlock()
|
||
}
|
||
}()
|
||
// ★ 文案带持锁方:排查时要能直接知道在等谁。
|
||
return fmt.Errorf("proc: 插件 %s 申请 stage 锁超时(%s,持锁方=%q)",
|
||
plugin, lockWaitTimeout, l.Owner())
|
||
}
|
||
}
|
||
|
||
// Release 释放写锁。非持锁者调用返回错误(防止串扰)。
|
||
//
|
||
// 可重入:depth 减到 0 才真正释放(见 stageLock.depth)。
|
||
func (l *stageLock) Release(plugin string) error {
|
||
l.ownerMu.Lock()
|
||
if !l.held {
|
||
l.ownerMu.Unlock()
|
||
return fmt.Errorf("proc: 插件 %s 释放未持有的 stage 锁", plugin)
|
||
}
|
||
if l.owner != plugin {
|
||
owner := l.owner
|
||
l.ownerMu.Unlock()
|
||
return fmt.Errorf("proc: 插件 %s 试图释放 %s 持有的 stage 锁", plugin, owner)
|
||
}
|
||
l.depth--
|
||
if l.depth > 0 {
|
||
// 仍在嵌套层:锁继续归该插件,但**不释放 mu**
|
||
// ——否则外层还在临界区里就被别人抢走。
|
||
l.ownerMu.Unlock()
|
||
return nil
|
||
}
|
||
l.owner = ""
|
||
l.held = false
|
||
l.depth = 0
|
||
l.ownerMu.Unlock()
|
||
l.mu.Unlock()
|
||
return nil
|
||
}
|
||
|
||
// ForceRelease 在插件进程崩溃/退出时由内核代为释放其持有的锁(实验 9 的自愈机制)。
|
||
//
|
||
// 返回是否实际释放了锁。**这是"无需 robust mutex"的核心**:
|
||
// 锁的所有权在内核进程,插件死亡由 cmd.Wait()/stdio EOF 检测到,
|
||
// 内核直接解锁,不存在"持锁者死亡导致全局死锁"。
|
||
func (l *stageLock) ForceRelease(plugin string) bool {
|
||
l.ownerMu.Lock()
|
||
if !l.held || l.owner != plugin {
|
||
l.ownerMu.Unlock()
|
||
return false
|
||
}
|
||
l.owner = ""
|
||
l.held = false
|
||
l.depth = 0
|
||
l.ownerMu.Unlock()
|
||
l.mu.Unlock()
|
||
return true
|
||
}
|
||
|
||
// Owner 返回当前持锁插件名(诊断用)。
|
||
func (l *stageLock) Owner() string {
|
||
l.ownerMu.Lock()
|
||
defer l.ownerMu.Unlock()
|
||
if !l.held {
|
||
return ""
|
||
}
|
||
return l.owner
|
||
}
|