我有一个芹菜链,可以完成一些任务。每个任务都可能失败并重试。请参阅下面的快速示例:
from celery import task
@task(ignore_result=True)
def add(x, y, fail=True):
try:
if fail:
raise Exception('Ugly exception.')
print '%d + %d = %d' % (x, y, x+y)
except Exception as e:
raise add.retry(args=(x, y, False), exc=e, countdown=10)
@task(ignore_result=True)
def mul(x, y):
print '%d * %d = %d' % (x, y, x*y)
和链:
from celery.canvas import chain
chain(add.si(1, 2), mul.si(3, 4)).apply_async()
运行这两项任务(并假设没有任何失败),你会得到/看到打印:
1 + 2 = 3
3 * 4 = 12
但是,当第一次添加任务失败并在后续重试调用中成功时,链中的其余任务不会运行,即添加任务失败,链中的所有其他任务都不会运行,并且在几秒钟,添加任务再次运行并成功,链中的其余任务(在这种情况下为mul.si(3,4))不会运行。
芹菜是否提供了从失败的任务开始继续失败链的方法?如果没有,那么实现这一目标的最佳方法是什么,并确保链的任务按指定的顺序运行,并且只有在上一个任务成功执行后才能成功执行,即使任务被重试几次?
注1:问题可以通过
来解决add.delay(1, 2).get()
mul.delay(3, 4).get()
但我有兴趣了解为什么链不能用于失败的任务。
答案 0 :(得分:9)
你发现了一个错误:)
已修复https://github.com/celery/celery/commit/b2b9d922fdaed5571cf685249bdc46f28acacde3 将成为3.0.4的一部分。
答案 1 :(得分:0)
我也有兴趣了解为什么链不能用于失败的任务。
我挖了一些芹菜代码,到目前为止我发现的是:
实施发生在app.builtins.py
@shared_task
def add_chain_task(app):
from celery.canvas import chord, group, maybe_subtask
_app = app
class Chain(app.Task):
app = _app
name = 'celery.chain'
accept_magic_kwargs = False
def prepare_steps(self, args, tasks):
steps = deque(tasks)
next_step = prev_task = prev_res = None
tasks, results = [], []
i = 0
while steps:
# First task get partial args from chain.
task = maybe_subtask(steps.popleft())
task = task.clone() if i else task.clone(args)
i += 1
tid = task.options.get('task_id')
if tid is None:
tid = task.options['task_id'] = uuid()
res = task.type.AsyncResult(tid)
# automatically upgrade group(..) | s to chord(group, s)
if isinstance(task, group):
try:
next_step = steps.popleft()
except IndexError:
next_step = None
if next_step is not None:
task = chord(task, body=next_step, task_id=tid)
if prev_task:
# link previous task to this task.
prev_task.link(task)
# set the results parent attribute.
res.parent = prev_res
results.append(res)
tasks.append(task)
prev_task, prev_res = task, res
return tasks, results
def apply_async(self, args=(), kwargs={}, group_id=None, chord=None,
task_id=None, **options):
if self.app.conf.CELERY_ALWAYS_EAGER:
return self.apply(args, kwargs, **options)
options.pop('publisher', None)
tasks, results = self.prepare_steps(args, kwargs['tasks'])
result = results[-1]
if group_id:
tasks[-1].set(group_id=group_id)
if chord:
tasks[-1].set(chord=chord)
if task_id:
tasks[-1].set(task_id=task_id)
result = tasks[-1].type.AsyncResult(task_id)
tasks[0].apply_async()
return result
def apply(self, args=(), kwargs={}, **options):
tasks = [maybe_subtask(task).clone() for task in kwargs['tasks']]
res = prev = None
for task in tasks:
res = task.apply((prev.get(), ) if prev else ())
res.parent, prev = prev, res
return res
return Chain
您可以看到最后prepare_steps
prev_task
与下一个任务相关联。
当prev_task失败时,不会调用下一个任务。
我正在测试将prev任务中的link_error添加到下一个任务:
if prev_task:
# link and link_error previous task to this task.
prev_task.link(task)
prev_task.link_error(task)
# set the results parent attribute.
res.parent = prev_res
但是,接下来的任务必须处理两种情况(可能,除非它被配置为不可变,例如不接受更多参数)。
我认为链可以通过允许一些语法来支持它:
c = chain(t1, (t2, t1e), (t3, t2e))
表示:
t1
link
至t2
和link_error
至t1e
t2
link
至t3
和link_error
至t2e