mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
test(scheduler): 优先级压力测试(100 排队 + 100 中断各级混合、嵌套到 4 帧上限)
按需造一个固定内容的 fakeprovider,做两件事: E4:混合载荷(用户指定的形状) 100 条排队输入 + 100 条中断(L1/L2/L3/L4 各 25)混合打入。每条中断都等到 “该被它打断的受害者正在跑”时才注入——调度器是单线程的,闭着眼睛猛灌只会 让绝大多数中断退化成排队,压力就压不到抢占/挂起路径上。 判据:200 个任务全部到达终态、Rejected=0、各级登记数=25、**各级抢占数都>0**、 排空后 Suspended==Resumed、每次“取消流式段”都换来一次挂起。 E5:嵌套到结构上限 排队任务运行中依次注入 L1→L2→L3→L4(每级都等上一级在跑)。判据:栈深峰值 恰好 4(= 结构上限,此时 canSuspend()=false),恢复顺序严格 LIFO [L3,L2,L1,排队],Suspended==Resumed==4。 顺带修掉两处: - **Resumed 双计**:nextRef 弹栈与 resumeTask 各计一次,使“排空后 Suspended==Resumed”这条不变量失真。现在只在 resumeTask 计(nextRef 只负责选出)。 - 新增分级可观测:SchedulerStats.InterruptsByLevel[1..4] / PreemptsByLevel[1..4]。 只看总数会掩盖“总数一样但级别分布完全不同”,按级别验收才是这套调度器的判据。 注意 PreemptsByLevel 是“判定可抢占并进入 immediate”的次数,与 Suspended 不等价: 受害者可能在让位生效前就自行结束,此时抢占者只是“下一个运行”,没有挂起发生。 教训:provider 必须感知 ctx 取消。第一版没做,结果 LLM完成==任务数、挂起≈0—— 抢占全落在“步骤之间”,流式段(真正需要保存/恢复现场的地方)一次都没压到。 验收:go test ./... 37 包 ok 0 FAIL;-race 全绿;压力连跑 3 次稳定; 新内核 go build 通过并真实启动冒烟 OK。
This commit is contained in:
@ -220,14 +220,34 @@ func canPreempt(incoming, running *Task) bool {
|
||||
}
|
||||
|
||||
// SchedulerStats 是调度器的累计计数(可观测性,设计文档 §11 O2)。
|
||||
//
|
||||
// InterruptsByLevel / PreemptsByLevel 按**中断级别**分桶(下标 1..4):
|
||||
// “各级中断各登记了多少、各真正抢断了多少次”。按级别验收(而不是只看总数)
|
||||
// 是这套调度器的核心判据——总数相同、级别分布不同,行为完全不同。
|
||||
type SchedulerStats struct {
|
||||
Enqueued uint64
|
||||
Executed uint64
|
||||
// Rejected 是因队列满(或深度超限)而未被接纳的次数。
|
||||
Rejected uint64
|
||||
// Suspended / Resumed 是挂起与恢复的次数。
|
||||
// 不变量:系统排空后 Suspended == Resumed(挂起必然被恢复),
|
||||
// 因此两者各自只在**一处**计数(suspend / resumeTask)。
|
||||
Suspended uint64
|
||||
Resumed uint64
|
||||
// InterruptsByLevel[1..4]:各级中断被**登记**的次数(含未抢占成功的)。
|
||||
InterruptsByLevel [5]uint64
|
||||
// PreemptsByLevel[1..4]:各级中断**判定为可抢占并进入 immediate**的次数。
|
||||
// 注意它与 Suspended 不等价:受害者可能在让位信号生效前就自行结束,
|
||||
// 此时抢占者仍然"下一个运行",但没有挂起发生。
|
||||
PreemptsByLevel [5]uint64
|
||||
}
|
||||
|
||||
// bumpInterruptLevel 按级别累加(级别必须落在 1..4,否则忽略——
|
||||
// 排队任务没有级别,不该出现在中断计数里)。
|
||||
func (st *SchedulerStats) bumpInterruptLevel(dst *[5]uint64, lv Level) {
|
||||
if lv >= LevelBackground && lv <= LevelCritical {
|
||||
dst[lv]++
|
||||
}
|
||||
}
|
||||
|
||||
// SchedulerSnapshot 是调度器的原子快照。
|
||||
@ -417,8 +437,9 @@ func (s *scheduler) nextRef() (*Task, *TaskFrame, nextSelection) {
|
||||
top := s.suspendStack[n-1]
|
||||
// 栈顶 vs 最高级待处理中断:取高者(持平归栈顶,维持 LIFO 与公平)。
|
||||
if qTask == nil || effectiveLevel(top.Task) >= qLevel {
|
||||
// 这里只负责“选出”;Resumed 由 resumeTask 计一次(否则会双计,
|
||||
// 使“排空后 Suspended == Resumed”这条不变量失真)。
|
||||
s.suspendStack = s.suspendStack[:n-1]
|
||||
s.stats.Resumed++
|
||||
s.running = top.Task
|
||||
return top.Task, top.Frame, nextSuspended
|
||||
}
|
||||
@ -546,12 +567,14 @@ func (s *scheduler) registerInterrupt(t *Task) bool {
|
||||
s.mu.Lock()
|
||||
running := s.running
|
||||
critical := s.critical.Load()
|
||||
s.stats.bumpInterruptLevel(&s.stats.InterruptsByLevel, t.Level)
|
||||
arm := false
|
||||
if !critical && canPreempt(t, running) {
|
||||
if running.LastPreemptAt.IsZero() || time.Since(running.LastPreemptAt) >= preemptCooldown {
|
||||
arm = true
|
||||
s.preemptArmed = true
|
||||
s.preemptLevel = t.Level
|
||||
s.stats.bumpInterruptLevel(&s.stats.PreemptsByLevel, t.Level)
|
||||
s.setImmediateLocked(t)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user