节点panic未被recover捕获时会向上冒泡,跳过调度器逻辑,导致状态无法标记失败、依赖节点不跳过、拓扑中断及静默退出。

为什么节点 panic 会导致整个 DAG 流程崩溃
Go 工作流引擎里,节点执行函数(比如 Execute 方法)如果没包一层 recover,一旦内部发生 panic(如空指针解引用、map 写入未初始化、第三方库 panic),就会直接向上冒泡,跳过调度器的错误处理逻辑,导致:
– 当前节点状态无法标记为失败
– 后续依赖节点不会被跳过或重试
– 拓扑排序中断,整个流程卡死或静默退出
– 日志里只看到 goroutine panic,没有上下文(节点名、输入数据、执行时间)
在 dag-executor 中插入 recover 的标准位置
不是在每个节点实现里写 defer recover(),而是在统一调度入口做。以 Archon 或 flow 类引擎为例,真正执行节点的代码通常集中在 dag-executor.ts(TypeScript)或 Go 里的 executor.RunNode 函数中。你需要在该函数的 goroutine 封装层加 recover:
- 必须在
go func() { ... }()内部 defer,不能在外部 - recover 后要主动调用
store.FailNode(nodeID, err)或等效状态更新 - 错误信息需拼接节点名和原始 panic 值:
fmt.Sprintf("panic in node %s: %v", node.ID, r) - 不要忽略 panic —— 即使你记录了日志,也得让调度器知道“这个节点不可恢复地失败了”
recover 不能替代错误返回,但能兜住未预期 panic
Execute 接口本应返回 error,这是显式错误路径;recover 是兜底路径,只捕获未被 if err != nil 捕获的运行时 panic。二者不互斥,必须共存:
- 业务逻辑错误(如参数校验失败)走
return nil, errors.New("invalid input") - 底层 panic(如
json.Unmarshal(nil, &v))由recover捕获并转为 error - 如果 recover 后不 re-panic,又不返回 error,调度器会误判节点成功,后续节点照常执行 —— 这是最危险的漏报
- 某些引擎(如 Archon)会在
event-emitter.ts发送node.failed事件,recover 后必须触发它,否则可观测性断链
容易被忽略的并发 recover 场景
当工作流开启并行执行(如 max_concurrent_nodes: 10),每个节点都在独立 goroutine 执行。这时 recover 必须每个 goroutine 自己做 —— 全局 recover 无效:
立即学习“go语言免费学习笔记(深入)”;
- 不要在 main goroutine 或调度主循环里 defer recover —— 它捕不到子 goroutine panic
- 每个
go executor.RunNode(...)内部都得有自己的一套 defer-recover - 如果用了协程池(如
semaphore.Acquire()+go task()),recover 要放在task函数最外层,而非池管理逻辑里 - recover 捕获到的
r可能是string、error或自定义 struct,建议统一转成fmt.Errorf("panic: %v", r)再处理


















