mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 01:48:11 +00:00
用户明确语义(我此前对 D1 的解析就是错的——当时回答里的“A”指的是 git 选项, D1 实际要的是方案 B): 中断打断时,上个任务到达以来的所有上下文现场被保护(含 toolcall), 然后中断在「上个任务前的那个完整状态」上开始运行; 中断结束后再把被挂起的任务与其上下文现场加载回中断任务之上,并继续运行。 实现: - 删除 SeedMsgs 与 D1=A 的“只读前缀”机制:中断任务不再继承被打断任务的任何内容, 它就是普通新任务,正常走完整 prepare(system prompt + timeline + 自己的输入) - TaskFrame 新增 PrefixLen(基础前缀长度)与 InputBlocks; stepPrepare 在 buildMessages 之后记录 PrefixLen - 新增 rebaseFramePrefix:恢复时重建基础前缀(中断已提交进 a.context, 重建的 timeline 含中断效果=“加载回中断之上”),再把本任务自己的尾部 (Stage 上下文 + 工具轮产物 + 占位)接回;并补回 IsInterrupt 标记与多模态块 - resumeTask 在 runTaskSteps 之前调用 rebaseFramePrefix - 设计稿 §5.3 改写为「已定:D1=B」并写明实现对应;§6.2 补“重建前缀→接回尾部”; §12 的 D1 行更新 测试: - TestPreempt_HigherPreemptsAndResumes 改为断言「中断不继承、恢复后看得见中断内容」 - 新增 TestPreempt_ResumeRebaseRestoresTailDecorations(前缀重建后尾部装饰补回) - 原 TestPreempt_SeedPathDoesNotLeakInterruptFlag 随之删除(机制已不存在) 验收:agent 全量 + -race;全仓 build/vet 通过
119 lines
4.1 KiB
Go
119 lines
4.1 KiB
Go
package core
|
||
|
||
// M5 验收测试:饥饿防护(抢占计数提升有效级 + 抢占冷却)。
|
||
//
|
||
// 设计依据 docs/zh/input-scheduler-design.md §9、§11.5(G1/G2)。
|
||
//
|
||
// 为什么需要:固定四级 + 「严格大于才抢占」下,一条 L4 流可以反复打断同一个
|
||
// L1 任务,使它永不完结。提升被抢占者的**有效**优先级,让它在竞争排队时
|
||
// 逐步追上;封顶 L4,因此它永远抢不过真正的紧急输入(紧急输入本身不被抢占)。
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func TestStarvation_EffectiveLevelPromotion(t *testing.T) {
|
||
base := &Task{Level: LevelBackground}
|
||
if got := effectiveLevel(base); got != LevelBackground {
|
||
t.Fatalf("未抢占时有效级=%v,期望 L1", got)
|
||
}
|
||
|
||
base.PreemptCount = 1
|
||
if got := effectiveLevel(base); got != LevelMessage {
|
||
t.Fatalf("被抢占 1 次后有效级=%v,期望 L2", got)
|
||
}
|
||
base.PreemptCount = 2
|
||
if got := effectiveLevel(base); got != LevelInteractive {
|
||
t.Fatalf("被抢占 2 次后有效级=%v,期望 L3", got)
|
||
}
|
||
base.PreemptCount = 99
|
||
if got := effectiveLevel(base); got != LevelInteractive {
|
||
t.Fatalf("提升应封顶在 +2 档,实际 %v", got)
|
||
}
|
||
|
||
// 封顶 L4:L3 任务被多次抢占也不会超过紧急级。
|
||
high := &Task{Level: LevelInteractive, PreemptCount: 99}
|
||
if got := effectiveLevel(high); got != LevelCritical {
|
||
t.Fatalf("L3 提升后应封顶为 L4,实际 %v", got)
|
||
}
|
||
}
|
||
|
||
func TestStarvation_CooldownBlocksImmediateRepreempt(t *testing.T) {
|
||
a := newPreemptAgent(t, newPreemptProvider())
|
||
|
||
low := &Task{ID: 1, Level: LevelBackground, EnqueuedAt: time.Now()}
|
||
a.sched.enqueue(low)
|
||
a.sched.nextRef() // running = low
|
||
|
||
e1, _ := textEvent("qq", "第一次打断")
|
||
if !a.sched.requestPreempt(e1, LevelMessage) {
|
||
t.Fatal("L2 应能抢占 L1(首次)")
|
||
}
|
||
a.sched.suspend(low, a.newTaskFrame("x", a.stageCtxFromInput("x", "", "")))
|
||
if low.PreemptCount != 1 {
|
||
t.Fatalf("PreemptCount=%d,期望 1", low.PreemptCount)
|
||
}
|
||
if low.LastPreemptAt.IsZero() {
|
||
t.Fatal("挂起必须记录 LastPreemptAt(冷却起点)")
|
||
}
|
||
|
||
// 冷却期内:即使 L4 也不得再抢占。
|
||
a.sched.mu.Lock()
|
||
a.sched.running = low
|
||
a.sched.mu.Unlock()
|
||
|
||
e2, _ := textEvent("cli", "冷却期内的紧急打断")
|
||
if a.sched.requestPreempt(e2, LevelCritical) {
|
||
t.Fatal("抢占冷却期内不得再抢占")
|
||
}
|
||
if a.sched.preemptGrantedFor() {
|
||
t.Fatal("冷却期内不得 arm 让位信号")
|
||
}
|
||
}
|
||
|
||
func TestStarvation_PromotionBlocksSameLevelPreempt(t *testing.T) {
|
||
a := newPreemptAgent(t, newPreemptProvider())
|
||
|
||
low := &Task{ID: 1, Level: LevelBackground, EnqueuedAt: time.Now()}
|
||
a.sched.enqueue(low)
|
||
a.sched.nextRef()
|
||
// 模拟「已被抢占过一次」:有效级 = L2。
|
||
low.PreemptCount = 1
|
||
low.LastPreemptAt = time.Now().Add(-time.Hour) // 冷却已过
|
||
|
||
e1, _ := textEvent("qq", "同级打断")
|
||
if a.sched.requestPreempt(e1, LevelMessage) {
|
||
t.Fatal("有效级 L2 时,L2 中断不得抢占(严格大于才抢占)")
|
||
}
|
||
|
||
e2, _ := textEvent("cli", "更高级打断")
|
||
if !a.sched.requestPreempt(e2, LevelInteractive) {
|
||
t.Fatal("L3 应能抢占有效级 L2")
|
||
}
|
||
if !a.sched.preemptGrantedFor() {
|
||
t.Fatal("L3 > 有效 L2,应已 arm")
|
||
}
|
||
}
|
||
|
||
// 选择函数必须用有效级:被抢占过的任务在排队时应当优先于同级/更低的任务。
|
||
func TestStarvation_SelectionUsesEffectiveLevel(t *testing.T) {
|
||
base := time.Now()
|
||
promoted := &Task{ID: 1, Level: LevelBackground, PreemptCount: 2, EnqueuedAt: base} // 有效 L3
|
||
normal := &Task{ID: 2, Level: LevelMessage, EnqueuedAt: base.Add(time.Second)} // L2
|
||
|
||
if !taskBefore(promoted, normal) {
|
||
t.Fatal("被抢占 2 次的 L1(有效 L3)应先于 L2 执行")
|
||
}
|
||
if taskBefore(normal, promoted) {
|
||
t.Fatal("选择函数不得只看基础级")
|
||
}
|
||
|
||
// 提升不改变调度器自身的排序稳定性:同为有效级时按入队时刻。
|
||
a := &Task{ID: 3, Level: LevelBackground, PreemptCount: 1, EnqueuedAt: base.Add(2 * time.Second)} // 有效 L2
|
||
b := &Task{ID: 4, Level: LevelMessage, EnqueuedAt: base.Add(time.Second)} // L2,更早
|
||
if !taskBefore(b, a) {
|
||
t.Fatal("同有效级时应先到先服务")
|
||
}
|
||
}
|