DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
腾讯云;让你的想法跑起来;轻量服务器 · 建站 / Docker / API;查看产品 →

Celery 给 chord 加回调,为什么报 Cannot add link to group

chord 的 apply_async(link=...) 可能把回调选项继续传给 header group。把后续任务放进 body,或在发送前绑定 body 回调,并保留结果对象语义。

代码已经使用 chord,追加成功回调时却收到:

TypeError: Cannot add link to group: use a chord

常见触发形状是 workflow.apply_async(link=callback.s()),其中 workflow 是一个 chord,或以 chord 结尾的链。问题不一定是少写了 chord:link 作为调用选项传递时,可能继续落到 chord 的 header group 上,而 group 不接受这个参数。

这一传递路径在 Celery 5.6.3 正式源码中仍存在。这里讨论成功回调,不把 link_error、header 失败后的处理或任意 Canvas 异常归入同一原因。

eager 模式可能绕过出错位置

chord 由并行 header 与汇总 body 组成。非 eager 路径会准备结果后端的屏障,再发送 header group;调用时传入的选项会向下传递,group 在真正发任务前就检查 link 并抛出异常。

task_always_eager=True 走另一条直接执行路径,可能得到预期结果,因而掩盖 worker 环境中的选项转发问题。只用 eager 模式通过的单元测试,不能确认实际 broker 与结果后端上的行为。

保存原 Canvas 定义、Celery 版本与完整 traceback。确认抛错的是 header group 的 apply_async(),并检查 link 是否由通用提交包装器统一追加。若前面的任务已经执行成功,之后在调度下一段 Canvas 时才失败,它的日志也可能出现失败状态;不要据此认定业务函数本身在相同位置抛了异常。

把后续操作明确放进 body

如果含义是“所有 header 完成、汇总成功之后再通知”,可以直接把通知串到 body 后面。以下示例省略了已有应用的 broker 和结果后端配置;这些任务必须已在 worker 注册,且 chord 所需的结果不能被忽略:

from celery import chord

# add、total、announce 是现有的 Celery 任务。
# total 接收 header 结果列表;announce 接收 total 的返回值。
header = [add.s(1, 2), add.s(3, 4)]
workflow = chord(header, total.s() | announce.s())
result = workflow.apply_async()

这里不再给外层 apply_async() 传 link。header 的列表先交给 total,其返回值再交给 announce。若通知函数不接收前一步结果,应按它的参数契约使用不可变签名,例如 announce.si(),而不是让一个多余参数在 worker 中另行报错。

仅想保留 chord 的汇总结果,并另外附一个成功回调时,可在发送前把回调绑定到 body:

workflow = chord(header, total.s())
workflow.link(announce.s())
result = workflow.apply_async()

Celery 的 chord 专门把 .link() 交给 body。不过 .link() 返回的是回调签名,不是原 chord;不要写成 workflow = workflow.link(...) 后再发送,以免实际发送的对象被替换。

两种写法的结果对象语义不同:把通知放进 body 链后,最终结果对应链末任务;单独附回调时,chord 的结果仍对应汇总 body。根据调用方真正需要等待和读取的结果选择,不要只为避开 TypeError 更换流程。

重新提交前先核对已经发生的工作

改动前保留原定义及任务 ID。在隔离队列中使用真实 worker、broker 和支持 chord 的结果后端,核对 header 各执行一次、body 收到完整结果、回调只在约定的成功条件下执行,以及最终结果属于哪一步。还要检查 header 失败时不会发出成功通知。

若线上异常发生在链的后半段,前半段可能已经写入数据。先按业务操作号判断哪些步骤完成,再决定补做范围,不应无条件重跑整个链。恢复原 Canvas 也不会撤销已发出的任务,应继续追踪旧任务 ID,避免两份流程同时推进。