Skip to content

Fix chain/group repr mangling task names and arguments - #10855

Merged
auvipy merged 12 commits into
celery:mainfrom
LemonCondor:fix/canvas-repr-remove-repeating
Oct 14, 2026
Merged

auvipy merged 12 commits into
celery:mainfrom
LemonCondor:fix/canvas-repr-remove-repeating

Conversation

@LemonCondor

Copy link
Copy Markdown

Note: Before submitting this pull request, please review our contributing
guidelines
.

Description

repr() of a chain/group shortens later task names by removing the first task's module prefix with remove_repeating_from_task(), which uses str.replace() on the rest of the string. Two cases produce wrong output:

  • The prefix is also removed from inside other names: with app.tasks.a first, webapp.tasks.mul(3) is shown as webmul(3).
  • When the first task name has no module part (e.g. add), the prefix is ".", so every later dot is removed, including in arguments:
>>> add.s(1.5) | mul.s(2.5)   # tasks named "add" and "proj.tasks.mul"
add(1.5) | projtasksmul(25)    # before
add(1.5) | proj.tasks.mul(2.5) # after

This only affects repr()/log output, but it shows wrong task names and argument values.

The fix returns the string unchanged when the task name has no module, and only strips the prefix where it starts a name (not when preceded by a word character or a dot).

t/unit/utils/test_text.py::test_remove_repeating_from_task covers the two docstring examples and both cases above; the two new cases fail on main. t/unit/utils/test_text.py and t/unit/tasks/test_canvas.py pass (319 passed, 1 xfailed); flake8/isort clean.

remove_repeating_from_task() removed every occurrence of the first
task's module prefix from the rest of the repr, including in the middle
of other names: with 'app.tasks.add' first, 'webapp.tasks.mul' was shown
as 'webmul'. When the first task name had no module (e.g. 'add') the
prefix became '.', so every following dot was removed, turning
'proj.tasks.mul(2.5)' into 'projtasksmul(25)'.

Only strip the prefix where it starts a name, and do nothing when the
task name has no module part.

Signed-off-by: Guillaume Marty <[email protected]>
@auvipy

auvipy commented Oct 13, 2026

Copy link
Copy Markdown
Member

is there any existing issue for this pr? if not not need to open one

@LemonCondor

LemonCondor commented Oct 13, 2026 •

Copy link
Copy Markdown
Author

No existing issue that I know .

@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.48%. Comparing base (f8d5258) to head (6866077).

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #10855   +/-   ##
=======================================
  Coverage   90.47%   90.48%           
=======================================
  Files         153      153           
  Lines       21277    21287   +10     
  Branches     2590     2593    +3     
=======================================
+ Hits        19251    19261   +10     
  Misses       1710     1710           
  Partials      316      316           
Flag Coverage Δ
unittests 90.46% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@auvipy auvipy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please check the failures in CI

@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.

Ran this locally against main at 5a635c2.

CI

The one red job, Smoke-tests (t/smoke/tests/test_worker_shutdown_acks.py) / 3.12, failed in apt-get update before any test started:

E: Failed to fetch https://dl.google.com/linux/chrome-stable/deb/dists/stable/main/binary-amd64/Packages.gz  File has unexpected size (1412 != 1408). Mirror sync in progress?

The other 59 smoke jobs were cancelled as a result. It is a runner mirror problem, not this change; a re-run should clear it.

What checks out

t/unit/utils/test_text.py and t/unit/tasks/test_canvas.py pass (330 passed, 1 xfailed). Both cases from the description are fixed, and since group and chord use the same helper their reprs are fixed too:

                 main                                    this branch
chain            app.tasks.a(1) | webmul(3)              app.tasks.a(1) | webapp.tasks.mul(3)
chain            add(1.5) | projtasksmul(25)             add(1.5) | proj.tasks.mul(2.5)
group            group((app.tasks.a(1), webb(2), c(3)))  group((app.tasks.a(1), webapp.tasks.b(2), c(3)))
chord            %tsum([p.qa(15), pqb(05)])              %tsum([p.q.a(1.5), p.q.b(0.5)])

Two cases still left (same output on main, so neither is a regression)

  1. A module name ending in -<prefix>: with app.tasks.a first, my-app.tasks.b() is shown as my-b(). Adding - to the lookbehind covers it:

    re.sub(r'(?<![\w.-])' + re.escape(substr), '', ...)
  2. String arguments that contain the prefix are still rewritten:

    >>> tasks.add.s(1) | tasks.mul.s('tasks.report.csv')
    tasks.add(1) | mul('report.csv')

    The regex can't tell a task name from a quoted value once everything is joined into one string, so this needs the shortening to happen per task name before joining. Fine as a follow-up.

Optional: the new test only exercises the helper. One repr() assertion for a group or chord in t/unit/tasks/test_canvas.py would pin the behaviour where users see it.

As suggested in review: "my-app.tasks.b" was shortened to "my-b" when
"app.tasks." was the first task's module. Also pin the fix where users
see it with a group repr test.

Signed-off-by: Guillaume Marty <[email protected]>
@LemonCondor

Copy link
Copy Markdown
Author

Thanks @auvipy and @MehrazRumman! The red smoke jobs failed in apt-get update (Google Chrome mirror), before any test ran, so they're not related to this change. I've pushed a follow-up:

  • module names after a dash (my-app.tasks.b) are no longer shortened, as suggested;
  • added a repr() test on a group to pin the behaviour users see.

The quoted-argument case ('tasks.report.csv') needs per-task shortening before joining, so I'd keep it for a follow-up PR if that's OK.

@auvipy auvipy added this to the 5.7.0 milestone Oct 13, 2026
@auvipy

auvipy commented Oct 14, 2026

Copy link
Copy Markdown
Member

@GangEunzzang @phanky1 please have a final look

@GangEunzzang

Copy link
Copy Markdown
Contributor

Looks good to me. Both of Mehraz's points are in (- in the lookbehind, and the group repr test), and CI is green now.

I compared repr() on this branch and main over 5,000 random canvases (chains, groups and chords, nested, with names like add, webapp.tasks.b, my-app.tasks.c, my_app.tasks.d, and string/float arguments containing dots):

main this PR
canvases with a mangled task name 994 0
canvases with an altered argument 544 157

Every difference from main restores something main dropped (webb → webapp.tasks.b, 25 → 2.5, app.tasks.sue → app.tasks.sub.e). I didn't find a canvas that renders worse than on main. The 157 left are all the quoted-value case you're deferring, and it applies to keyword values too: tasks.mul.s(path='tasks.x') still shows mul(path='x').

The new tests fail on main and pass here.

Minor: repr() of deeply nested canvases is slower, since each level runs the regex over its own substring. A group/chain nested 250 levels takes 42 ms against 3 ms on main (60 levels: 3 ms against 0.7 ms). Flat canvases are about the same (chain of 2,000 tasks: 6.1 ms against 5.2 ms). Probably fine for a repr.

@auvipy

auvipy commented Oct 14, 2026

Copy link
Copy Markdown
Member

Minor: repr() of deeply nested canvases is slower, since each level runs the regex over its own substring. A group/chain nested 250 levels takes 42 ms against 3 ms on main (60 levels: 3 ms against 0.7 ms). Flat canvases are about the same (chain of 2,000 tasks: 6.1 ms against 5.2 ms). Probably fine for a repr.

@LemonCondor may be the last things to improve.

auvipy and others added 2 commits October 14, 2026 19:48
Replace the per-call regex with str.split and a check of the character
before each occurrence. The output is identical (checked against the
regex version on 200,000 random strings), and repr() of a canvas nested
250 levels deep goes from 31 ms back to 13 ms (12 ms on main).

Signed-off-by: Guillaume Marty <[email protected]>
@LemonCondor

Copy link
Copy Markdown
Author

Thanks @GangEunzzang for the thorough comparison, and @auvipy! I've pushed a change for the nested-canvas slowdown: remove_repeating now uses str.split and checks the character before each occurrence instead of running a regex per level.

  • The output is identical to the previous version (checked on 200,000 random strings).
  • repr() of a canvas nested 250 levels deep: 31 ms → 13 ms (main: 12 ms).
  • Flat canvases are unchanged.

The quoted-value case (path='tasks.x') is still left for a follow-up, since it needs shortening per task name before joining.

@auvipy
auvipy merged commit 0120a9e into celery:main Oct 14, 2026
470 checks passed
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.

4 participants