修复: B-03b-R1 取消节点跳 set_failed + R2 取消测试(补完 B-03b 复核)
R1: executor Err 处理加 is_cancelled 检测,已取消节点跳 set_failed(Cancelled→Failed transition 非法会 bail 致工作流崩溃),状态保 Cancelled,emit NodeFailed 仍发 R2: test_cancelled_node_skips_set_failed 验证取消传播链(CancelSelfNode 共享 node_status 自取消→Err→executor 不 bail→状态 Cancelled) 补完 B-03b 端到端:4aa689e 只通 IPC→set_cancelled→HumanNode→Err 链,漏验 HumanNode→executor Err 处理与 Cancelled 冲突;df-workflow 14 test pass
This commit is contained in:
@@ -121,11 +121,16 @@ impl DagExecutor {
|
||||
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) {
|
||||
self.state_machine.set_failed(node_id.clone())?;
|
||||
}
|
||||
self.event_bus
|
||||
.send(WorkflowEvent::NodeFailed {
|
||||
node_id: node_id.clone(),
|
||||
error: e.to_string(),
|
||||
error: error_msg,
|
||||
})
|
||||
.await;
|
||||
// 同层多个失败时只报告第一个
|
||||
@@ -248,4 +253,53 @@ mod tests {
|
||||
assert_eq!(executor.state_machine.get(&"b".to_string()), NodeStatus::Completed);
|
||||
assert_eq!(executor.state_machine.get(&"c".to_string()), NodeStatus::Pending);
|
||||
}
|
||||
|
||||
/// 测试节点:执行中通过共享 node_status 自取消(模拟外部 IPC set_cancelled),返回 Err
|
||||
struct CancelSelfNode;
|
||||
|
||||
#[async_trait]
|
||||
impl Node for CancelSelfNode {
|
||||
async fn execute(&self, ctx: NodeContext) -> NodeResult {
|
||||
// 通过共享 node_status 置 Cancelled(写共享 HashMap,executor.state_machine 可见)
|
||||
ctx.node_status.set_cancelled(ctx.node_id.clone());
|
||||
Err(anyhow::anyhow!("人工审批被取消"))
|
||||
}
|
||||
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
params: serde_json::Value::Null,
|
||||
output: serde_json::Value::Null,
|
||||
}
|
||||
}
|
||||
|
||||
fn node_type(&self) -> &str {
|
||||
"cancel_self"
|
||||
}
|
||||
}
|
||||
|
||||
/// B-03b-R1:取消的节点返回 Err 时,executor 跳过 set_failed(Cancelled→Failed transition 非法会 bail),
|
||||
/// 状态保持 Cancelled,run 返回取消相关 Err 而非状态转换错误。
|
||||
#[tokio::test]
|
||||
async fn test_cancelled_node_skips_set_failed() {
|
||||
use df_core::types::NodeStatus;
|
||||
let mut dag = Dag::new();
|
||||
dag.add_node("x".to_string(), Box::new(CancelSelfNode));
|
||||
|
||||
let mut executor = DagExecutor::new(EventBus::new(), "test-cancel".to_string());
|
||||
let result = executor.run(&dag, serde_json::Value::Null).await;
|
||||
|
||||
// run 返回 Err(取消致中止后续层),但不 panic/bail transition
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(
|
||||
err.contains("取消") || err.contains("执行失败"),
|
||||
"应返回取消相关错误,实际: {}",
|
||||
err
|
||||
);
|
||||
// 状态保持 Cancelled(未被 set_failed 覆盖为 Failed)—— R1 修法生效的核心验证
|
||||
assert_eq!(
|
||||
executor.state_machine.get(&"x".to_string()),
|
||||
NodeStatus::Cancelled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
|
||||
> B-03b 端到端补完(commit 4aa689e)后独立复核发现 2 加重 + 3 低优。**①为真实 bug**:取消链 IPC→set_cancelled→HumanNode is_cancelled→Err 通了,但 HumanNode 返 Err 后 executor run:115 `set_failed` 走 transition,而 Cancelled 不在 `is_legal` 转换图 → bail → 工作流因状态转换错误异常终止(非优雅标记取消)。补完时只验 IPC→HumanNode 链,漏验 HumanNode→executor Err 处理。
|
||||
|
||||
- [ ] B-03b-R1 — **[P1]** Cancelled 未纳入转换图双踩 — executor Err 处理 `set_failed(node)` 在节点已 Cancelled 时 `transition(Cancelled→Failed)` 非法 bail。修法二选一:A. `is_legal` 加 Cancelled 为终态(Cancelled→Failed/Completed 合法或显式拒绝并特殊处理)/ B. executor Err 处理先 `is_cancelled` 检测,已取消则跳过 set_failed 改 emit 取消事件。推荐 B(取消语义清晰,不动转换图)
|
||||
- [ ] B-03b-R2 — **[P1]** 零端到端取消测试 — clone_shares 仅验共享语义,缺 IPC set_cancelled→HumanNode is_cancelled 命中→Err→executor 处理 全链测试;测试夹具不支持共享 StateMachine 验证(需构造共享实例 + 并发 set_cancelled)
|
||||
- [x] B-03b-R1 ~~Cancelled 转换图双踩~~ ✅ 已修(executor Err 处理加 is_cancelled 检测,已取消节点跳 set_failed,Cancelled→Failed 非法 transition 不再 bail;emit NodeFailed 仍发,状态保 Cancelled;采修法 B 不动 is_legal 转换图)
|
||||
- [x] B-03b-R2 ~~零端到端取消测试~~ ✅ 已补(test_cancelled_node_skips_set_failed:CancelSelfNode 经共享 node_status 自取消→Err→executor 不 bail→状态保 Cancelled;df-workflow 14 test pass)
|
||||
- [ ] B-03b-R3 — **[P2 低优]** std::sync::Mutex 取舍 — 当前选 std(快速临界区纳秒级),若 async 持锁场景增多评估换 tokio::sync::Mutex
|
||||
- [ ] B-03b-R4 — **[P2 低优]** set_* 改 &self 后调用方残留 &mut 冗余清理
|
||||
- [ ] B-03b-R5 — **[P2 低优]** `lock().expect()` poison panic 漏清理(poison 时直接 panic 无优雅降级)
|
||||
|
||||
Reference in New Issue
Block a user