fix(canvas): fire task lifecycle hooks for map/starmap items by Sanjays2402 · Pull Request #10880 · celery/celery · GitHub
Skip to content

fix(canvas): fire task lifecycle hooks for map/starmap items - #10880

Open
Sanjays2402 wants to merge 1 commit into
celery:mainfrom
Sanjays2402:fix-map-starmap-lifecycle-hooks-7585
Open

Sanjays2402 wants to merge 1 commit into
celery:mainfrom
Sanjays2402:fix-map-starmap-lifecycle-hooks-7585

Conversation

@Sanjays2402

@Sanjays2402 Sanjays2402 commented Oct 18, 2026 •

Copy link
Copy Markdown

Description

Fixes #7585
Fixes #7586

The celery.map and celery.starmap builtin tasks ran each item by calling the task instance directly (task(item) / task(*item)), which skips the task trace completely. That means before_start, on_success, on_failure and after_return never fired for mapped items, and task signals were never sent, even though all of them fire for the same task executed on its own. chunks is affected too since it fans out through xstarmap.

The fix routes every item through Task.apply() so each one gets a full trace. .get(disable_sync_subtasks=False) is safe here: the EagerResult is already computed, so it never blocks, and passing False keeps this working inside real workers where the join guard is on. Exceptions still propagate out of the map task exactly as before, and now on_failure fires for them.

Repro (before the fix, map apply hooks was []):

from celery import Celery, Task

app = Celery('x', broker='memory://', backend='cache+memory://')
app.conf.task_always_eager = True

calls = []

class MyTask(Task):
    def before_start(self, task_id, args, kwargs):
        calls.append(('before_start', args))
    def after_return(self, status, retval, task_id, args, kwargs, einfo):
        calls.append(('after_return', status))

@app.task(base=MyTask)
def add1(x):
    return x + 1

add1.map([1, 2, 3]).apply()
print(calls)  # now: before_start/after_return per item

Tests: added regression tests in t/unit/app/test_builtins.py covering hook order per mapped item for map and starmap, plus on_failure firing when an item raises. Ran t/unit/app/test_builtins.py, t/unit/tasks/test_canvas.py, t/unit/app/test_trace.py, t/unit/tasks/test_result.py, t/unit/tasks/test_tasks.py: 638 passed, 1 skipped, 1 xfailed, all green.

Also fixes #7586: autoretry_for was ignored for mapped items because the retry logic lives in the trace path. Verified: a task with autoretry_for=(ValueError,) and max_retries=3 raised after a single attempt through map before this change; now each item retries independently.

The celery.map and celery.starmap builtin tasks executed each item by
calling the task directly, bypassing the task trace entirely. Task
lifecycle hooks (before_start/on_success/on_failure/after_return) and
task signals never fired for mapped items, unlike single tasks.

Route every item through Task.apply() so the trace runs per item.
get(disable_sync_subtasks=False) is safe here because the EagerResult
is already computed and never blocks.

Fixes celery#7585
@auvipy
auvipy self-requested a review October 18, 2026 03:57
@codecov

codecov Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

@auvipy auvipy modified the milestones: 5.7.0, 5.7.x Oct 18, 2026

@MehrazRumman MehrazRumman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked at faaec5c.

What checks out

  • Every mapped item now goes through the trace. The three new tests fail on main and pass here, and t/unit/app/test_builtins.py plus t/unit/tasks/test_canvas.py pass (378 passed, 1 xfailed).
  • Exceptions from an item still propagate out of the map task, now with on_failure/after_return fired first.

Behaviour changes beyond the hooks

Running items through apply() changes more than hook delivery. Compared with main:

                                                     main                 this PR
request inside an item (has id, is_eager,
  called_directly)                                   (False, False, True) (True, True, False)
INFO "Task ... succeeded" lines for a 100-item map   0                    100
item raises Ignore (map over [1, 2, 3], x == 2)      map task raises      [1, None, 3]
  1. Log and signal volume. Inside a worker at INFO level, every item now logs its own "succeeded" line, and task_prerun/task_postrun/task_success fire per item. A chunks() call over thousands of items produces thousands of log lines, and signal-based metrics (Prometheus exporters and similar) will count each item as a task. That is probably acceptable for hooks, but it should be called out. A quieter path, such as suppressing the per-item success log, may be worth considering.
  2. Ignore/Reject from an item no longer stop the map. On main the exception propagated and the whole map task ended as ignored. Now apply() records the item as IGNORED, .get() returns None, and the map carries on with None in the results. To keep the old semantics, check the EagerResult state and re-raise for IGNORED/REJECTED.
  3. The request context changes for the task. Items now see a fresh request.id, is_eager=True and called_directly=False. Code that branches on those flags behaves differently. In particular, self.retry() / autoretry_for in eager mode re-runs the item inline and immediately, so the #7586 fix retries without honouring countdown/retry_backoff, and it blocks the map task while doing so. Worth a sentence in the docs.
  4. With task_store_eager_result = True, every item's result is now written to the backend.

None of these is necessarily wrong, but together they are a user-visible change to map/starmap/chunks. They belong in the changelog, and point 2 probably needs a decision from the maintainers.

@auvipy

auvipy commented Oct 18, 2026

Copy link
Copy Markdown
Member

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

autoretry ignored on map/chunk calls map/chunks ignore task inheritance class before_start/after_return/on_success/on_failure

3 participants