// Package kernelboot 是内核模块的**声明式装配调度器**。 // // 设计权威:docs/zh/kernel-modularization-design.md §2.3。 // // ## 要解决的问题(实测,2026-10-16) // // `cmd/homed/main.go` 里 277 行装配代码、11 处「必须早于 / 必须在之前」注释。 // 这些**顺序依赖只写在注释里,没有任何机器校验** —— 错了不报错, // 而是静默降级,症状出现在很远的地方。 // // 现场故障:安全模块装配晚于读 `core.llm.*` 的操作 ⇒ 密文解不开 ⇒ // key 变空 ⇒ 所有 LLM 调用 401,而看起来像"key 配错了"。 // // ## 本包提供的性质(用户指出的核心洞察) // // > 依赖图是一个由依赖关系生成的图,通过 BFS 遍历:当前遍历到的节点初始化后, // > 下一层 BFS 就可以安全地并行初始化 —— 这是依赖图的性质。 // // 形式化:设边 `a → b` 表示 b 依赖 a。BFS 从入度 0 的节点开始分层。 // 第 k 层任意节点的**全部前驱都在 0 { fmt.Fprintf(&b, ";依赖了不存在的设施: %v(已声明可提供的: %v)", e.Missing, e.Declared) } if len(e.Cycle) > 0 { fmt.Fprintf(&b, ";循环依赖或等待环上的模块: %v", e.Cycle) } return b.String() } // Build 解析依赖并分层。 // // 算法(文档 §2.3.2): // // ready := {} // 已就绪的设施名 // for 还有未装配的模块 { // layer := 所有 Requires ⊆ ready 的模块 // if layer 为空 => 无解(缺依赖或环)=> 报错 // ready ∪= 所有 layer 成员的 Provides // } // // 返回的 Plan 可直接按层执行(层内可并行)。 func Build(modules []Module) (*Plan, error) { // ① 先收集所有被声明的设施名,用于区分"缺依赖"与"有环"两类错误。 declared := map[string]bool{} for _, m := range modules { for _, p := range m.Provides() { declared[p] = true } } // ② 检查重名(两个模块 Provides 同名设施 ⇒ 歧义,必须报错) // 为何必须拦:重名会让"依赖了谁"变得不确定, // 而依赖图的价值恰恰在于确定性。 owner := map[string]string{} // 设施名 -> 提供者模块名 var dupes []string for _, m := range modules { for _, p := range m.Provides() { if prev, ok := owner[p]; ok && prev != m.Name() { dupes = append(dupes, fmt.Sprintf("%s(%s 与 %s 都声明提供)", p, prev, m.Name())) continue } owner[p] = m.Name() } } if len(dupes) > 0 { sort.Strings(dupes) return nil, fmt.Errorf("kernelboot: 设施名被重复声明: %v", dupes) } // ③ 模块名不得重复 seen := map[string]bool{} for _, m := range modules { if seen[m.Name()] { return nil, fmt.Errorf("kernelboot: 模块名重复: %s", m.Name()) } seen[m.Name()] = true } // ④ BFS 分层 ready := map[string]bool{} pending := make([]Module, len(modules)) copy(pending, modules) var layers [][]Module for len(pending) > 0 { var layer []Module var rest []Module for _, m := range pending { if satisfied(m.Requires(), ready) { layer = append(layer, m) } else { rest = append(rest, m) } } if len(layer) == 0 { // 无解:区分"缺依赖"与"循环" return nil, diagnoseUnresolvable(rest, declared, owner, ready) } // 同层内按名字排序:让分层结果**稳定可复现**(便于判据断言与日志对照)。 // 并行执行顺序不影响正确性(同层无边),但稳定的输出让"层次变了"这类 // 隐式变更容易被发现。 sort.Slice(layer, func(i, j int) bool { return layer[i].Name() < layer[j].Name() }) layers = append(layers, layer) for _, m := range layer { for _, p := range m.Provides() { ready[p] = true } } pending = rest } return &Plan{Layers: layers}, nil } // satisfied 报告 requires 是否全部已在 ready 里。 func satisfied(requires []string, ready map[string]bool) bool { for _, r := range requires { if !ready[r] { return false } } return true } // diagnoseUnresolvable 区分两类无解原因,并给出可定位的信息。 // // 为何要区分:两者的**修复动作完全不同**—— // // · 缺依赖 ⇒ 去补 Provides(常是拼写错误) // · 有环 ⇒ 去拆环(设计问题) // // 只报"有环"会让人去错误的方向查。 func diagnoseUnresolvable(pending []Module, declared map[string]bool, owner map[string]string, ready map[string]bool) error { var missing, cycle []string for _, m := range pending { cycle = append(cycle, m.Name()) for _, r := range m.Requires() { // 该依赖既没被任何模块声明提供、也不在 ready ⇒ 真正的"不存在" if !declared[r] && !ready[r] { missing = append(missing, fmt.Sprintf("%s(%s 需要)", r, m.Name())) } } } sort.Strings(missing) sort.Strings(cycle) declaredList := make([]string, 0, len(declared)) for d := range declared { declaredList = append(declaredList, d) } sort.Strings(declaredList) return &UnresolvableError{Missing: missing, Cycle: cycle, Declared: declaredList} } // verifyLayerDependencies 核对本层各模块的依赖是否**已由更早的层实际产出**。 // // 返回 nil 表示全部就绪;否则返回 *DependencyNotReadyError(带模块名与缺失设施名)。 // 判据形状与 satisfied 一致(Requires ⊆ produced),但数据源是 produced(事实) // 而非 Build 里的 ready(声明推演)—— 这正是它能咬住"层顺序被改坏"的原因。 func verifyLayerDependencies(li int, layer []Module, produced map[string]bool) error { missing := map[string][]string{} for _, m := range layer { var lack []string for _, r := range m.Requires() { if !produced[r] { lack = append(lack, r) } } if len(lack) > 0 { sort.Strings(lack) missing[m.Name()] = lack } } if len(missing) == 0 { return nil } producedList := make([]string, 0, len(produced)) for f := range produced { producedList = append(producedList, f) } sort.Strings(producedList) return &DependencyNotReadyError{Layer: li, Missing: missing, Produced: producedList} } // RunOptions 控制执行。 type RunOptions struct { // MaxParallel 是**层内**的并发上限(<=0 表示不限制)。 // // ★ 为何必须有界:同层无边保证**安全**,但不保证**无代价**—— // 42 个插件同时 spawn 会造成内存/CPU 尖峰(每个 ~11MB 二进制加载 + 握手)。 // 并发度只影响**速度**,不影响**顺序**(同层节点两两无边, // 无论以什么顺序完成,下一层的就绪条件都相同)。 MaxParallel int // OnLayerDone 在每层完成后回调(层号从 0 起)。可为 nil。 OnLayerDone func(layerIndex int, modules []Module) // State 是内核**唯一的公共数据结构体**(用户设计): // 模块从中读依赖、往它写自己的产物。 // // 为何由调用方传入而不是 Run 内部自建:产物在 Run 返回后**仍然要用** // (如 LuaVM、Memory 要一直活着),所以它必须比 Run 活得久。 State *KernelState // Bus 是状态同步总线。非 nil 时,Run 会在**每个层边界**把该层模块贡献的 // 设施标记为就绪(设计 §2.6.2)。 // // ★ 为何由 Run 自动写而不是各模块自己写: // 若模块自己 MarkReady,顺序取决于**层内并发的完成顺序** ⇒ 不可复现; // 而且模块可能"忘记标记"(静默漏掉一个设施 ⇒ 插件永远等不到它就绪)。 // 由 Run 在层边界统一写,既确定又不可能漏。 Bus *SyncBus } // RunResult 是执行结果。 type RunResult struct { // LayerCount 是实际执行的层数。 LayerCount int // MaxConcurrent 是观测到的**峰值并发**(用于判据证明并行确实发生)。 MaxConcurrent int // Order 是按完成顺序记录的模块名(仅用于诊断/日志,**不作正确性依据**)。 Order []string } // DependencyNotReadyError 是"执行期"依赖校验失败:某模块要开工,但它需要的设施 // **在已经真正跑完的层里**找不到。 // // ★ 与 Build/diagnoseUnresolvable 的分工(**刻意不重复实现**): // // Build(构建期) :看**声明**——Requires 有没有人 Provides、有没有环。 // 本错误(执行期,Run):看**事实**——按这份 plan 真的跑完的层里,产出了什么。 // // 两者不可互相替代:声明全对、图也自洽,plan 的**层顺序**仍可能被改坏 // (手改 Layers / 旧图/新代码混装 / 有人把某层提前)。那时 Build 不会报错, // 模块的 Init 拿到 nil 依赖 → 崩在业务代码里。实测形态:把 lua 排到 llm 之后, // llm.buildProviderManager 收到 0x0 的 LuaVM,ConfigureConcurrency 里 SIGSEGV。 // // ⇒ 这条判据的判据对象是**实际执行进度**,不是声明,所以它咬得住"顺序被破坏"。 type DependencyNotReadyError struct { // Layer 是出问题的层号(0 起)。 Layer int // Missing 是 模块名 → 它缺的设施名(各自升序)。 Missing map[string][]string // Produced 是此前各层**实际产出**的设施名(升序),供对照"哪些还没跑"。 Produced []string } func (e *DependencyNotReadyError) Error() string { mods := make([]string, 0, len(e.Missing)) for m := range e.Missing { mods = append(mods, m) } sort.Strings(mods) var b strings.Builder fmt.Fprintf(&b, "kernelboot: 第 %d 层装配前校验失败 —— 依赖未就绪", e.Layer) b.WriteString("(启动计划的层顺序与模块声明不一致,或图与代码漂移): ") for i, m := range mods { if i > 0 { b.WriteString(";") } fmt.Fprintf(&b, "模块 %s 缺设施 %v", m, e.Missing[m]) } fmt.Fprintf(&b, "。此前各层已实际产出的设施: %v", e.Produced) b.WriteString("。\n" + " 为什么必须在这里拦住:依赖没就绪就调 Init,模块拿到的是 nil," + "崩在业务代码里(实测 lua 排到 llm 之后 ⇒ llm 拿 0x0 LuaVM,SIGSEGV)。\n" + " 修复:让提供者在**更早的层**(检查各模块的 Requires/Provides 是否与图一致," + "并重新生成依赖图)") return b.String() } // Run 按计划执行:逐层 Init,层内有界并行。 // // ## 执行期依赖校验(每层 Init **之前**) // // 逐层维护"这一层之前**真的跑完**的模块产出了哪些设施"(produced), // 每层开工前先核对本层各模块的 Requires ⊆ produced: // // · 缺则**立刻返回** DependencyNotReadyError(指名模块名前、缺哪些设施), // 本层**一个 Init 都不调** —— 宁可什么都不装,也不要装一半再崩 // · 这一层全部成功后才把本层 Provides 记入 produced(与 syncbus 的层边界同语义) // // ★ 为什么不复用 Build 的结论:Build 校验的是**声明**(图能否解开)。 // 声明全对时层顺序仍可能被改坏,那时 Build 绿、Init 崩。见 DependencyNotReadyError。 // // 失败策略(文档 §2.3.3):**任何 Init 失败都令整体失败**。 // 不静默跳过、不"带着半初始化状态继续"——后者正是病 A 的形态。 // // 同层内若**多个**模块失败,返回第一个(按名字序,稳定); // 但**不等同于只跑一个**:同层其余模块仍会被执行完(避免半途中断造成更乱的状态)。 // (依赖未就绪属于"开工前就发现",那时本层还没有任何模块动过,故一个都不跑。) func Run(plan *Plan, opts RunOptions) (*RunResult, error) { if plan == nil { return nil, fmt.Errorf("kernelboot: 计划为 nil") } res := &RunResult{} var orderMu sync.Mutex // produced 是**执行期事实**:只有真的跑完的层才贡献设施。 // 它刻意不从 plan 里预填(预填就等于相信声明 ⇒ 变回构建期校验,抓不到顺序被改坏)。 produced := map[string]bool{} ks := opts.State if ks == nil { ks = NewKernelState() } // ★ 执行前把**全部**模块声明的设施登记为"内核注册过"。 // // 这样插件的 `requires_facilities` 校验(防拼错 ⇒ 静默永不启动) // 在"装配结果之外"也成立:即使某层失败、后面的模块没跑, // "这个设施名是模块声明过的"依然为真(语义与"是否已就绪"解耦)。 if opts.Bus != nil { var all []string for _, layer := range plan.Layers { for _, m := range layer { all = append(all, m.Provides()...) } } opts.Bus.MarkDeclared(all) // ★ 把 bus 放进 KernelState:模块的 Init 只能看到 ks, // 而 plugin-host 需要订阅状态变化(挂"等 agent 就绪"的回调)。 // 不放这里 ⇒ 模块拿不到 bus(实测撞到)。 ks.SyncBus = opts.Bus } for li, layer := range plan.Layers { if len(layer) == 0 { continue } // ★ 装配前校验:本层每个模块要的设施,必须在**此前真正跑完**的层里产出过。 // 放在这里(而不是循环中途逐个模块查)⇒ 本层一个 Init 都不调就拒绝, // 避免"前半层已经装了一半"的脏状态。 if err := verifyLayerDependencies(li, layer, produced); err != nil { return res, err } limit := opts.MaxParallel if limit <= 0 || limit > len(layer) { limit = len(layer) } sem := make(chan struct{}, limit) var wg sync.WaitGroup errs := make([]error, len(layer)) // 峰值并发观测(判据用它证明"并行确实发生了") var active, peak int var peakMu sync.Mutex for i, m := range layer { wg.Add(1) go func(i int, m Module) { defer wg.Done() sem <- struct{}{} defer func() { <-sem }() peakMu.Lock() active++ if active > peak { peak = active } peakMu.Unlock() err := m.Init(ks) peakMu.Lock() active-- peakMu.Unlock() orderMu.Lock() res.Order = append(res.Order, m.Name()) orderMu.Unlock() if err != nil { errs[i] = fmt.Errorf("模块 %s 装配失败: %w", m.Name(), err) } }(i, m) } wg.Wait() peakMu.Lock() if peak > res.MaxConcurrent { res.MaxConcurrent = peak } peakMu.Unlock() // 层内失败:收集后**一次性**报告(不要 fail-fast 丢掉其他失败信息) var failed []string for _, e := range errs { if e != nil { failed = append(failed, e.Error()) } } if len(failed) > 0 { sort.Strings(failed) return res, fmt.Errorf("kernelboot: 第 %d 层装配失败: %s", li, strings.Join(failed, "; ")) } res.LayerCount++ // ★ 执行期事实:本层全部成功 ⇒ 它提供的设施才"真的存在"。 for _, m := range layer { for _, p := range m.Provides() { if p != "" { produced[p] = true } } } // ★ 层边界:把本层贡献的设施标记就绪(设计 §2.6.2)。 // 在层内并发**全部完成之后**统一标记 ⇒ 就绪顺序确定、可复现。 if opts.Bus != nil { var provides []string for _, m := range layer { provides = append(provides, m.Provides()...) } opts.Bus.MarkLayerReady(provides) } if opts.OnLayerDone != nil { opts.OnLayerDone(li, layer) } } // ★ 全部层完成 ⇒ 内核完全初始化。 // // 这是"未声明依赖的插件"的启动信号(默认路径 = 现行为,设计 §2.6.5)。 // 它在**所有层都成功之后**才发 —— 中途失败会提前 return,不发这个信号, // 于是那些插件**不会被拉起**(这是对的:内核没装好就拉插件 = 病 A)。 if opts.Bus != nil { opts.Bus.MarkKernelComplete() } return res, nil }