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:
JianFeeeee
2026-09-13 07:31:32 +08:00
parent f3232000f4
commit 158395bfdf
3 changed files with 385 additions and 2 deletions

View File

@ -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)
}
}