You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You have signed the CLA already but the status is still pending? Let us recheck it.
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You have signed the CLA already but the status is still pending? Let us recheck it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Paired: GitHub #69 ↔ GitCode !212
InterruptRecovery093 重复执行修复方案
一、结论
之前把:
放宽成:
这个处理不正确。
这不是“没有执行但计数增加”的幽灵计数。
times++就在节点的invoke()中,次数超过 3 说明节点确实被再次执行了。真正的问题是:因此不能通过放宽断言接受重复执行,应该修复 Core 的任务取消与恢复语义,并恢复精确次数断言。
二、093 的正确执行次数
InterruptRecovery093有 3 个循环元素:loopNode1、loopNode3、loopNode4:每个元素成功执行一次,因此都是3次。loopNode2:每个元素第一次异常、恢复后第二次成功,因此是3 × 2 = 6次。Python 原测试也是严格断言
3 / 6 / 3 / 3,见 [test_interrupt_recovery_093.py]。如果 Java 中
loopNode1/4出现4~6次,说明失败恢复时重复执行了已经运行过的节点。由于测试节点只是“加十”,最终结果可能仍然正确;但真实业务节点可能已经发送消息、写数据库或调用外部接口,重复执行会产生真实副作用。三、根因分析
Python 使用单线程
asyncio协作调度。AddTenNode.invoke()从times += 1到正常返回之间没有await,这一段不会被兄弟任务抢占,见 [workflow_aw.py]。Java 翻译后使用虚拟线程并行执行兄弟节点。旧实现发生了以下竞态:
旧实现的问题点在于
FutureTask.cancel(true):cancelled;TaskFuture.done()会进一步取消外层结果 Future;这和 Python
asyncio.Task.cancel()的协作式取消并不等价。Python 是在后续await注入取消异常,协程可以响应、清理,甚至捕获取消后正常返回;JavaFutureTask.cancel(true)则先永久丢弃结果。四、修复原则
核心规则是:
为此需要同时区分“节点执行阶段”和“取消来源”。
cancelAll()NOT_STARTEDINVOKINGcancel(true)ROUTINGCOMPLETED兄弟失败和显式取消必须分开:
SIBLING_FAILURE:目标是停止不必要的工作,但不能丢弃正常执行结果。FORCED:调用方明确要求停止整个执行,可以强制取消并丢弃结果。五、具体代码修改
1. 增加 invocation 完成边界
在 [NodeTask.java]中,节点函数正常返回后、路由开始前通知执行器:
只有
NodeTask知道“业务调用已经正常返回”的准确位置,所以不能只根据整个 Future 是否完成来判断。原有公开构造方法保留,默认使用 no-op callback,不改变独立使用
NodeTask的行为。2. 引入任务阶段
在 [TaskExecutorPool.java] 中增加:
原来的
hasStarted布尔值只能判断“开始/未开始”,无法区分:这正是旧实现误判的根源。
3. 兄弟失败改为协作式取消
在 [TaskExecutorPool.java]中:
NOT_STARTED:仍然取消 Future,确保节点不会进入。INVOKING:只 interrupt 执行线程,不调用FutureTask.cancel()。ROUTING:不 interrupt,等待正常结果。4. 用锁封闭竞态窗口
phase、cancellation、executionThread都由每个任务自己的lifecycleLock保护。这样无论哪个动作先发生,结果都确定:
ROUTING;后到的兄弟失败不再中断它。FORCED,不会误清强制中断信号。没有增加全局锁,不影响不同节点之间的并行执行。
5. 等待真实执行和结果发布都完成
[settlePendingTasksAfterFailure()]同时等待:
不能只看到 Future 被标记为 cancelled 就立即写 checkpoint,否则旧任务可能还在执行,而恢复任务已经启动,仍会产生并行重复执行。
六、为什么不能采用其他简单方案
isBetween(3, 6):这会把重复副作用合法化。cancel(true)改成cancel(false)用于运行中任务:运行中的FutureTask.cancel(false)仍可能把 Future 永久标记为 cancelled,只是不发送 interrupt,结果仍会丢失。times++:真实业务副作用可能发生在节点执行任意位置,修改计数器只是掩盖问题。七、测试修改及验证
测试断言已恢复为精确次数,见 [InterruptRecovery093Test.java]。
新增 Core 回归测试见 [TaskExecutorPoolTest.java],确定性制造:
旧实现结果为:
新实现保证:
failed;cancelAll()仍能强制中断 router。验证结果:
TaskExecutorPoolTest:通过。ReActAgentInterruptRegressionTest:通过。index断言后重复运行 10 次,均稳定为3 / 6 / 3 / 3;临时改动已撤销,index未纳入本次修改。3981个测试,2 failures + 2 errors。/var与/private/var路径问题,并已在未修改的HEAD上原样复现,确认不是本次修改引入。system-test标签。八、影响范围与边界
本次只修改“同一 super-step 中普通节点异常后,兄弟任务如何收敛”:
GraphInterrupt的交互恢复语义。cancelAll()仍保持强制取消。index问题保持不动。这不是全局 exactly-once 保证。如果节点产生外部副作用后主动抛出异常、JVM 崩溃或 router 自身失败,框架仍可能重试;这类场景需要幂等键、事务或补偿机制。
当前单元测试已经覆盖
INVOKING + sibling failure窗口。合入前建议再增加一个确定性的ROUTING + sibling failure测试:让成功节点进入阻塞 router 后再触发兄弟异常,断言 router 不被 sibling failure 中断且节点不进入 pending。这是测试覆盖增强,不改变当前修复方向。