feat(scheduler)!: 中断/排队两类别模型 + 插件声明 L1-L3、L4 内核独占

用户澄清推翻了早期设计的三处前提,本提交按新模型重做调度核心(行为有意变化):

1) 类别由注入 API 决定,与通道名无关
   - InjectInterrupt*                      -> TaskInterrupt(带级别,可被严格更高级中断打断)
   - InjectText*/InjectInputSync*/内核自循环 -> TaskQueued(无级别,可被任何中断打断)
   - 删除按通道名推断的 taskLevel():qq 走 InjectInterruptTextOpts,本就是中断

2) 级别只属于中断
   - 插件在 InjectOptions.Priority 声明 L1-L3(空/非法降级 L1,声明 L4 夹到 L3)
   - L4 内核独占:新增 raiseKernelInterrupt(panic / selfip);requestKernelPreempt 不夹取
   - panic 现在产生一条带 kernel 标记的 L4 中断;L4 自身 panic 不再产生新 L4(防自我放大)

3) 选择结构:四容器固定次序,删除统一比较器
   - immediate(抢占者立即运行)-> 中断队列 L4..L1 -> 栈顶(与队头比级别) -> 排队 FIFO
   - 删除 pickTaskIndex/taskBefore 与“同级 pending 优先”补丁(根因是抢占者进了队列)
   - 中断栈上界改为结构推论 = 4(= 中断级数);删除“超限转 pendingInterrupts”降级

公开 SDK(feature 分支有意新增,纯追加):InjectOptions.Priority + PriorityL1/2/3;
内核 io / proc 桥 / 插件模板同步透传。

设计稿 §2/§3/§4.1/§6.3/§9/§11/§12/§13/§15 按新模型重写。

验收:go build/vet 干净;go test ./... 37 包 ok 0 FAIL;-race 全绿;
e2e(抢占-挂起-恢复)+ 压力(200 排队 + 50 中断,L1/L2/L3 轮转)通过。
This commit is contained in:
JianFeeeee
2026-09-13 06:57:23 +08:00
parent 86a702b3b0
commit cb32032f76
16 changed files with 1021 additions and 443 deletions

View File

@ -74,7 +74,7 @@ func TestStack_NestedPreemptionResumesLIFO(t *testing.T) {
a := newPreemptAgent(t, sp)
// AL1开始运行
if _, _ = enqueueTask(t, a, LevelBackground, "qq", "任务A"); true {
if _, _ = enqueueQueued(t, a, "qq", "任务A"); true {
}
at, _, _ := a.sched.nextRef()
doneA := make(chan struct{})
@ -94,7 +94,7 @@ func TestStack_NestedPreemptionResumesLIFO(t *testing.T) {
// B 开始运行
bt, _, k := a.sched.nextRef()
if k != nextPending || bt.Level != LevelMessage {
if k != nextImmediate || bt.Level != LevelMessage {
t.Fatalf("应取到 Bpendingkind=%v level=%v", k, bt.Level)
}
doneB := make(chan struct{})
@ -113,8 +113,8 @@ func TestStack_NestedPreemptionResumesLIFO(t *testing.T) {
if len(snap.SuspendStack) != 2 {
t.Fatalf("嵌套后栈深=%d期望 2", len(snap.SuspendStack))
}
if snap.SuspendStack[0].Task.Level != LevelBackground {
t.Fatalf("栈底应为 A(L1),实际 %v", snap.SuspendStack[0].Task.Level)
if snap.SuspendStack[0].Task.Class != TaskQueued {
t.Fatalf("栈底应为排队任务 A无级别,实际 %v", snap.SuspendStack[0].Task.Class)
}
if snap.SuspendStack[1].Task.Level != LevelMessage {
t.Fatalf("栈顶应为 B(L2),实际 %v", snap.SuspendStack[1].Task.Level)
@ -122,7 +122,7 @@ func TestStack_NestedPreemptionResumesLIFO(t *testing.T) {
// C 运行完毕(第三次调用,不阻塞)
ct, _, k := a.sched.nextRef()
if k != nextPending || ct.Level != LevelInteractive {
if k != nextImmediate || ct.Level != LevelInteractive {
t.Fatalf("应取到 Ckind=%v level=%v", k, ct.Level)
}
a.executeNewTask(ct)
@ -141,8 +141,8 @@ func TestStack_NestedPreemptionResumesLIFO(t *testing.T) {
if k2 != nextSuspended {
t.Fatalf("应继续恢复 Akind=%v", k2)
}
if rt2.Level != LevelBackground {
t.Fatalf("最后应恢复 A(L1),实际 %v", rt2.Level)
if rt2.Class != TaskQueued {
t.Fatalf("最后应恢复排队的 A无级别,实际 %v", rt2.Class)
}
a.resumeTask(rt2, rf2)
@ -159,9 +159,9 @@ func TestStack_TopOnlyWinsOverHigherPrioritySuspended(t *testing.T) {
// (栈自底向上基础级递增),这里专门用来区分两种实现:
// · 只比栈顶 → 取 B
// · 全栈扫最优 → 取 AL3 > L2
a.sched.suspend(&Task{ID: 1, Level: LevelInteractive, EnqueuedAt: time.Now()},
a.sched.suspend(&Task{ID: 1, Class: TaskInterrupt, Level: LevelInteractive, EnqueuedAt: time.Now()},
a.newTaskFrame("A", a.stageCtxFromInput("A", "", "")))
a.sched.suspend(&Task{ID: 2, Level: LevelMessage, EnqueuedAt: time.Now()},
a.sched.suspend(&Task{ID: 2, Class: TaskInterrupt, Level: LevelMessage, EnqueuedAt: time.Now()},
a.newTaskFrame("B", a.stageCtxFromInput("B", "", "")))
rt, _, k := a.sched.nextRef()
@ -180,13 +180,13 @@ func TestStack_TopOnlyWinsOverHigherPrioritySuspended(t *testing.T) {
func TestStack_DepthCapDuringNesting(t *testing.T) {
a := newPreemptAgent(t, &scriptProvider{})
frame := func() *TaskFrame { return a.newTaskFrame("x", a.stageCtxFromInput("x", "", "")) }
for i := 0; i < a.sched.maxSuspendDepth; i++ {
a.sched.suspend(&Task{ID: uint64(i + 1), Level: Level(i + 1)}, frame())
for i := 0; i < a.sched.maxInterruptFrames; i++ {
a.sched.suspend(&Task{ID: uint64(i + 1), Class: TaskInterrupt, Level: Level(i + 1)}, frame())
}
if a.sched.canSuspend() {
t.Fatal("栈已满canSuspend 应为 false")
}
if n := len(a.DumpScheduler().SuspendStack); n != a.sched.maxSuspendDepth {
t.Fatalf("栈深=%d期望上 %d", n, a.sched.maxSuspendDepth)
if n := len(a.DumpScheduler().SuspendStack); n != a.sched.maxInterruptFrames {
t.Fatalf("栈深=%d期望上 %d", n, a.sched.maxInterruptFrames)
}
}