mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 06:47:29 +00:00
refactor(agent/core): 中断子系统独立成 interrupt.go + 修两处真实数据竞争
## 中断子系统拆分(用户按 OS 中断语义三次裁定的最终边界) 中断类比 OS:只提供中断优先级、中断队列、现场保护、中断屏蔽。 调度器建立在其之上——它问"中断侧该出谁",然后执行那个任务。 新增 internal/agent/core/interrupt.go(303 行),承载: - 中断队列(interruptQueues[5] + immediate 单槽) - 中断栈(suspendStack)—— **唯一操作者** - 中断屏蔽(critical,对应关中断) - 让位信号原语(preemptArmed/preemptLevel) 留在 scheduler.go 的是**策略**: - 任务优先级语义(Level / effectiveLevel / canPreempt / clampPluginLevel) - 恢复决策(nextInterruptSelection:栈顶 vs 中断队列比较) - 就绪队列 / 停止配额 / 背压 ### 关键设计:中断栈只有中断管理器能操作 调度器通过 stackTopLocked() **只读查询**栈顶做比较, 真正的弹栈由 restoreTopLocked() 在中断管理器内完成。 这条不变量在类型层面无法绕过。 ### 解耦机制:stackNotify 钩子 中断栈变化时通告调度器,调度器据此唤醒调度循环重新求值。 中断管理器不知道调度器会怎么选,调度器也不碰栈内部 ⇒ 将来给中断子系统加向量表或换多栈策略,不必改调度器。 ### 测试 新增 interrupt_invariant_test.go:I1–I7 共 11 条**不变量**判据 (区别于既有的行为测试)。两条经变异验证确认会变红: - canPreempt 改成恒 true → L4 不抢 L4 判据红 - maxInterruptFrames 改成 99 → 上界判据红 - 去掉 notifyStackLocked → 钩子判据红(且判据本身带超时,缺判据是"红"不是"挂") ## 修两处真实数据竞争(阻塞 CI,非本次引入) -race 实测:internal/agent/core 原有 30 个测试失败,整包被判红。 根因(ab27d1a 上已存在,与拆分无关): - core/stages.go:withExecuteCtx 往调用方共享的 tc.Arguments 原地写 ctx - io/channel.go:bindExecCtx 同样原地写设备 args 原注释称"args 每次调用独立、天然一 goroutine 一份",但代码改的是调用方那份。 工具在自己的 goroutine 里跑,超时/取消路径下主 goroutine 仍在读同一份 args。 修法:复制 map 而非原地写。变异验证:退回原地写后 TestPreempt_DeferredDuringToolExec 立刻报 DATA RACE。 ## 验证 - go build ./... 通过 - go test -p 1 ./... 全仓无失败 - -race 三关键包(core/io/proc)全绿,core 从 30 红归零 - CI 门禁自检 / 启动图判据 / 版本一致性 本地通过 ## 遗留(不在本次) - 中断向量表(计划 §7 已设计,按 §7.6 应在拆分后单独做) - maxQueue 仍约束中断队列:OS 语义上说不通,但改它是行为变更,单独立项
This commit is contained in:
@ -1150,13 +1150,17 @@ const execCtxArgKey = "\x00kernel.execute_ctx\x00"
|
||||
// 但**必须**在真正下发给插件前剥掉,否则严格校验参数的插件会报未知参数。
|
||||
// 剥除由各插件自己的入口决定:core 侧统一下发前已剥(见 StageHost),
|
||||
// io 侧保持原样,因为设备插件直接读 args、不会做严格白名单校验。
|
||||
// ★ 必须**复制**而不是原地写:设备调用在自己的 goroutine 里跑,超时/取消
|
||||
// 路径下调用方会在 goroutine 仍存活时继续读同一份 args —— 原地写就是数据竞争
|
||||
// (-race 实测复现,见 StageHost 侧 withExecuteCtx 的同类说明)。
|
||||
func bindExecCtx(ctx context.Context, args map[string]interface{}) map[string]interface{} {
|
||||
if ctx == nil {
|
||||
return args
|
||||
}
|
||||
if args == nil {
|
||||
args = make(map[string]interface{}, 1)
|
||||
out := make(map[string]interface{}, len(args)+1)
|
||||
for k, v := range args {
|
||||
out[k] = v
|
||||
}
|
||||
args[execCtxArgKey] = ctx
|
||||
return args
|
||||
out[execCtxArgKey] = ctx
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user