//! DAG 执行器 — 按拓扑层级调度执行 use std::collections::HashMap; use df_types::events::WorkflowEvent; use df_types::types::NodeId; use crate::conditions::ConditionEngine; use crate::dag::Dag; use crate::eventbus::EventBus; use crate::node::{NodeContext, NodeOutput}; use crate::state::StateMachine; /// DAG 执行器 pub struct DagExecutor { /// 事件总线 event_bus: EventBus, /// 节点状态机 state_machine: StateMachine, /// 工作流执行 ID(由调用方传入,下沉到每个 NodeContext) execution_id: String, } impl DagExecutor { /// 创建执行器 /// /// `execution_id` 为本次工作流执行的唯一标识,会下沉到每个节点的 NodeContext, /// 用于节点内的事件关联、审计追踪等。 pub fn new(event_bus: EventBus, execution_id: String) -> Self { Self { event_bus, state_machine: StateMachine::new(), execution_id, } } /// 返回状态机的共享引用(Arc clone,与 NodeContext.node_status 共享底层 HashMap) /// /// run_workflow 创建执行器后将其注册到 AppState 全局表, /// cancel_workflow_node IPC 经 execution_id 取出此引用调 set_cancelled, /// 写入直达运行中阻塞节点(HumanNode)的 is_cancelled 轮询。 pub fn state_machine(&self) -> StateMachine { self.state_machine.clone() } /// 执行 DAG 工作流 /// /// 同一拓扑层内的节点并发执行,层与层之间串行; /// 本层全部节点完成后统一更新状态,任一失败则中止后续层。 pub async fn run( &mut self, dag: &Dag, initial_config: serde_json::Value, ) -> anyhow::Result> { let layers = dag.topological_layers()?; let mut outputs: HashMap = HashMap::new(); let start = std::time::Instant::now(); tracing::info!("DAG 执行开始,共 {} 层", layers.len()); // 预建入边索引:target → 直接前驱列表 + 边条件表达式(O(E) 一次构建) // 避免在内层按节点循环中调用 dag.predecessors()(每次 O(E) 全表扫描,整体 O(V·E))。 // 含 condition 是为 conditions-eval feature 接入:flag 开时按入边 condition 过滤前驱 input。 // flag 关时 condition 字段被忽略,行为与增强前完全一致(零破坏)。 let mut adjacency_in: HashMap)>> = HashMap::new(); for edge in &dag.edges { adjacency_in .entry(edge.target.clone()) .or_default() .push((edge.source.clone(), edge.condition.clone())); } for (layer_idx, layer) in layers.iter().enumerate() { tracing::info!("执行第 {} 层,共 {} 个节点", layer_idx, layer.len()); // 阶段一:逐节点发 NodeStarted、置为运行中,并构建执行 future let mut node_futures = Vec::with_capacity(layer.len()); for node_id in layer { let node = dag.nodes.get(node_id).ok_or_else(|| { anyhow::anyhow!("节点 {} 不存在于 DAG 中", node_id) })?; // 构建节点上下文:通过入边索引 O(入度) 取前驱,而非 O(E) 全表扫描。 // // conditions-eval feature(默认关): // 关:所有入边 inputs 全收集,condition 字段完全忽略 = 旧行为(零破坏)。 // 开:对带 condition 的入边以 source output.data 为 context 求值; // false 则不收集该前驱 input。若节点【存在带 condition 的入边,且无任何 // 入边(条件或无条件)放行】,则跳过该节点执行(不入 outputs、保持 Pending、 // 不 emit),实现条件路由。无条件边 source 有 output 即视为放行。 // 求值失败保守 false(对齐引擎语义)。 // // 注:跳过判定在 set_running 之前 —— 跳过的节点不应被标记 Running/触发事件, // 保持 Pending 终态(与"条件未命中"语义一致)。 let eval_conditions = cfg!(feature = "conditions-eval"); let mut inputs: HashMap = HashMap::new(); let mut has_any_cond_edge = false; let mut any_edge_passed = false; // 任一入边放行(含无条件边) if let Some(preds) = adjacency_in.get(node_id) { for (pred_id, cond) in preds { if let Some(out) = outputs.get(pred_id) { if eval_conditions { if let Some(cond) = cond { has_any_cond_edge = true; // 以 source output.data 为 context 求值;失败保守 false let passed = ConditionEngine::evaluate(cond, &out.data) .unwrap_or(false); if passed { inputs.insert(pred_id.clone(), out.clone()); any_edge_passed = true; } } else { // 无 condition 的入边:无条件放行(必收集) inputs.insert(pred_id.clone(), out.clone()); any_edge_passed = true; } } else { // flag 关:condition 完全忽略,旧行为全收集 inputs.insert(pred_id.clone(), out.clone()); any_edge_passed = true; } } } } else { // 根节点(无入边):不参与条件跳过判定,正常执行 any_edge_passed = true; } // 条件路由:flag 开 + 存在条件边 + 没有任何入边放行 → 跳过执行 if eval_conditions && has_any_cond_edge && !any_edge_passed { tracing::info!( "节点 {} 所有入边条件均不满足,跳过执行(条件路由)", node_id ); continue; } // 发送 NodeStarted 事件 self.event_bus .send(WorkflowEvent::NodeStarted { node_id: node_id.clone(), }) .await; self.state_machine.set_running(node_id.clone())?; let ctx = NodeContext { node_id: node_id.clone(), inputs, // 节点级覆盖全局级:deep_merge(initial_config, node_configs[id])。 // 节点 NodeDef.config 未定义(node_configs 缺该 key)→ 直接用全局 initial_config, // 与旧行为一致(零行为破坏现有 HumanNode/AiNode 等读全局 config 的调用方)。 config: match dag.node_configs.get(node_id) { Some(node_cfg) => crate::dag::deep_merge(&initial_config, node_cfg), None => initial_config.clone(), }, execution_id: self.execution_id.clone(), event_bus: self.event_bus.clone(), // 共享执行器状态机,使节点(如 HumanNode)能读取真实状态而非空状态机 node_status: self.state_machine.clone(), }; // 仅捕获节点共享引用与所有权上下文,避免与 self 借用冲突 let id = node_id.clone(); node_futures.push(async move { let node_start = std::time::Instant::now(); let result = node.execute(ctx).await; (id, result, node_start.elapsed().as_millis() as u64) }); } // 阶段二:同层节点并发执行,等待全部完成 let results = futures::future::join_all(node_futures).await; // 阶段三:统一更新状态并发送事件,任一失败则整体返回 Err let mut first_err: Option = None; for (node_id, result, duration) in results { match result { Ok(output) => { // 已取消的节点跳过 set_completed: // Ok 后 join_all 让出执行权,窗口内 cancel_workflow_node IPC → set_cancelled 改 Cancelled, // 随后此处 set_completed 走 transition 命中(Cancelled→Completed)非法 → bail 致已批准审批报失败(R-P1-3 TOCTOU) // 与 Err 分支 is_cancelled 短路对称:已取消节点保持 Cancelled 终态 if !self.state_machine.is_cancelled(&node_id) { self.state_machine.set_completed(node_id.clone())?; // 未取消才 emit NodeCompleted,取消时 emit NodeCancelled(对齐 Err 分支语义): // 避免 状态机=Cancelled 但事件=NodeCompleted 的矛盾信号 self.event_bus .send(WorkflowEvent::NodeCompleted { node_id: node_id.clone(), duration_ms: duration, }) .await; } else { // 对齐 Err 分支:取消节点 emit NodeCancelled(非 NodeCompleted), // 前端按 type 归「取消」,与状态机 Cancelled 一致 self.event_bus .send(WorkflowEvent::NodeCancelled { node_id: node_id.clone(), }) .await; } // outputs.insert 保留现状不动:取消时仍写入 output 供下游消费, // 当前测试锁定"取消不中止后续层",改 insert 会变行为致回归 outputs.insert(node_id, output); } Err(e) => { let error_msg = e.to_string(); // 已取消的节点(如 HumanNode 审批取消)跳过 set_failed: // Cancelled 已是终态,transition(Cancelled→Failed) 非法会 bail 致工作流崩溃(B-03b-R1) if self.state_machine.is_cancelled(&node_id) { // emit NodeCancelled(非 NodeFailed):取消语义有别于失败,前端按 type 归「取消」非「失败」 self.event_bus .send(WorkflowEvent::NodeCancelled { node_id: node_id.clone(), }) .await; } else { self.state_machine.set_failed(node_id.clone())?; self.event_bus .send(WorkflowEvent::NodeFailed { node_id: node_id.clone(), error: error_msg, }) .await; } // 取消与失败一致:节点返回 Err 即中止后续层(run 返回 Err); // 仅事件类型区分,工作流结果语义保持不变(零行为破坏)。 if first_err.is_none() { first_err = Some(e.context(format!("节点 {} 执行失败", node_id))); } } } } if let Some(e) = first_err { return Err(e); } } let total = start.elapsed().as_millis() as u64; // B-03b-R10 ③: WorkflowCompleted 携带 execution_id,供 forward 循环按 exec_id 匹配 // (全局 event_bus 单例下并发工作流的终态事件不再误触发他人 forward 的完成判定)。 self.event_bus .send(WorkflowEvent::WorkflowCompleted { execution_id: self.execution_id.clone(), total_duration_ms: total, }) .await; tracing::info!("DAG 执行完成,耗时 {}ms", total); Ok(outputs) } } #[cfg(test)] #[path = "executor_helpers.rs"] mod tests;