数据截至 (上游 commit e741923f72c3)
调度引擎:就绪队列、边激活与分支剪枝
30 秒导读: 上一章把画布编译成了 DAG,但图不会自己跑。本章讲运行期的三件事——谁先跑(就绪队列)、谁能跑(边激活)、谁不该跑(分支剪枝)。最值得学的一点在第 6 节:一个分支没被选中,引擎不是简单地"跳过它",而是要把这条分支的整条下游链路逐级失活,下游的汇合节点才知道"别等了"。
引用约定: 本章所有源码路径写全路径(相对克隆根),行号锚定 frontmatter 里的
sourceCommit。同一段落内重复引用同一文件时用短名 + 行号(如edge-manager.ts:306)。
1. 这章解决什么问题(零基础也能懂)
一张工作流图长这样:触发器 → 一个"判断"块 → 分两条路 → 两条路最后汇到一个"发 Slack"块。
图画好了,现在要跑它。跑图要回答三个问题:
| 问题 | 白话 | 本章对应机制 |
|---|---|---|
| 谁先跑 | 有五个块都没有未满足的依赖,先跑哪个?能一起跑吗? | 就绪队列 readyQueue + 隐式并发 |
| 谁能跑 | 一个块有三条入边,是三条都到齐才跑,还是到一条就跑? | 边激活 + isNodeReady |
| 谁不该跑 | 判断块选了「是」那条路,「否」那条路上的十个块怎么办? | 分支剪枝(级联失活) |
第三个问题是本章的重头戏,也是最反直觉的。 直觉答案是"不选中就不入队,自然不会跑"——错。因为下游那个汇合块会一直等那条死掉的路:它数着"我还有 2 个上游没来",而其中一个永远不会来。
所以 Sim 的做法是:没被选中的边不是被忽略,而是被明确标记为"失活",并把这条失活沿着下游链路一路传播下去,直到碰到一个"还有别的活路可走"的节点为止。汇合节点因此能算出"我等的那条边已经死了,不用等了,可以跑"。
一句话直觉: 把边想成电线。选中的路通电,没选中的路要逐级拉闸;拉到某个还有别的电源的节点就停手。汇合节点判断自己能不能跑,看的不是"来了几个",而是"还有几条没断电的线还没到"。
2. 顶层全景:装配线与主循环
这节先看"大盘":谁把零件装起来、主循环长什么样。
2.1 五个零件由谁装配
入口是 DAGExecutor.execute(apps/sim/executor/execution/executor.ts:87),它干三件事:建图 → 造上下文 → 装配流水线,然后把控制权交给引擎。
DAGExecutor.execute(workflowId, triggerBlockId)
│
├─ ① 建图 DAGBuilder.build → DAG(节点 + 入边集合 + 出边表)
├─ ② 造上下文 createExecutionContext → ExecutionContext + ExecutionState
├─ ③ 装流水线 buildExecutionPipeline → ExecutionEngine
└─ ④ 跑 engine.run(triggerBlockId)
装配发生在 buildExecutionPipeline(executor.ts:346),一次性 new 出六个对象并串起来:
| 零件 | 一句话职责 | 文件 · 符号 |
|---|---|---|
VariableResolver | 把块参数里的 <blockName.field> 引用换成真值 | apps/sim/executor/variables/resolver.ts · VariableResolver |
BlockExecutor | 找 handler、跑一个块、记日志、兜错 | apps/sim/executor/execution/block-executor.ts:78 · BlockExecutor |
EdgeManager | 判定边激活/失活、算节点是否就绪 | apps/sim/executor/execution/edge-manager.ts:10 · EdgeManager |
LoopOrchestrator / ParallelOrchestrator | 循环与并行的迭代语义(见 03) | apps/sim/executor/orchestrators/loop.ts / parallel.ts |
NodeExecutionOrchestrator | 分流:哨兵节点走子流程逻辑,普通块走 BlockExecutor | apps/sim/executor/orchestrators/node.ts:51 · executeNode |
ExecutionEngine | 主循环:队列、并发、取消、边传播 | apps/sim/executor/execution/engine.ts:31 · ExecutionEngine |
装配顺序里有一处不能挪:edgeManager.restoreDeactivatedEdges(...)(executor.ts:372)必须在引擎创建之前执行——从快照恢复的那次执行,已经失活的边要先灌回 EdgeManager,否则恢复后的引擎会以为所有分支都还活着。快照恢复的完整故事见 05。
createExecutionContext(executor.ts:386)则负责把快照里的一堆序列化结构还原成运行期的 Map/Set:blockStates、executedBlocks、decisions.router、decisions.condition、loopExecutions、parallelExecutions。它同时构造 ExecutionState,这是唯一的块输出读写口(见 §7.4)。