Skip to content

Support chords with empty headers#4443

Merged
thedrow merged 1 commit into
celery:masterfrom
AlexHill:empty_header_chords
Dec 13, 2017
Merged

Support chords with empty headers#4443
thedrow merged 1 commit into
celery:masterfrom
AlexHill:empty_header_chords

Conversation

@AlexHill

Copy link
Copy Markdown
Contributor

When a chord's header group contains no tasks, its body is never executed. The reason for this is pretty straightforward: execution of the body is triggered by completion of header tasks. If there are no header tasks to be completed, the body is never executed.

An empty GroupResult is considered completed and successful, so I think it's reasonable to expect a chord with an empty group header to complete.

The fix is to run the body "manually" in chord.run() when there are no header tasks.

In addition, I've pulled out the parts of the various apply_chord()s that actually call the header and changed its signature accordingly, instead calling the header in chord.run() before calling apply_chord() only if there are header tasks.

Fixes #4333

@AlexHill
AlexHill force-pushed the empty_header_chords branch 2 times, most recently from 6588867 to 13578e3 Compare December 12, 2017 18:17
@AlexHill
AlexHill force-pushed the empty_header_chords branch from 13578e3 to dd66d85 Compare December 12, 2017 18:20
@AlexHill AlexHill added this to the v4.2 milestone Dec 13, 2017
@AlexHill AlexHill self-assigned this Dec 13, 2017
@AlexHill
AlexHill requested a review from thedrow December 13, 2017 06:40
@thedrow

thedrow commented Dec 13, 2017

Copy link
Copy Markdown
Contributor

Quite the radical change we have here. I'd have to review this after coffee :)

@AlexHill

Copy link
Copy Markdown
Contributor Author

Yep, fair enough - the kernel of the changes is that the header is called in chord.run instead of in apply_chord - the rest of the changes flow on from there. Let me know if anything needs clarification :)

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

I'd rather avoid changing public APIs as this can result in code breakage. So I need you to be 100% sure this won't affect anyone before merging this.

Furthermore, other result backends might be affected by this change so I'm quite surprised why this works with the DynamoDB backend for example.

If we can answer yes to both questions then LGTM.

Comment thread celery/backends/base.py
def fallback_chord_unlock(self, group_id, body, result=None,
countdown=1, **kwargs):
kwargs['result'] = [r.as_tuple() for r in result]
def fallback_chord_unlock(self, header_result, body, countdown=1,

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.

Are we sure this won't break anyone's code?

Comment thread celery/backends/redis.py
# this flag.
options['task_id'] = group_id
return header(*partial_args, **options or {})
pass

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.

Are we sure this applies for all cases? That code is there for a reason.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep - apply_chord() is only called from chord.run(), and we're unconditionally calling the header (with the replaced task ID) there now: https://github.com/celery/celery/pull/4443/files#diff-e27381cb5c64031dd28aa6855c78cfddR1277

@AlexHill

AlexHill commented Dec 13, 2017

Copy link
Copy Markdown
Contributor Author

Yeah, I wasn't entirely sure about what's considered a public. If there are third-party backends that out there that override apply_chord or fallback_chord_unlock this could certainly break them. Googling "celery third-party backends" doesn't yield anything I significant that I can see.

If we can get away with it's a nice simplification - we were calling the header (and replacing the group ID) in three separate places, and having to pass along the arguments everywhere and so on. Now we just call the header in the one place the same way, and the backends use apply_chord to do whatever setup is required to handle results.

That said if you want to be strict about maintaining these signatures (and the behaviour of calling the header inside each backend's apply_chord) then I get that, and can dial this back a little.

@thedrow
thedrow merged commit 25f5e29 into celery:master Dec 13, 2017
@thedrow

thedrow commented Dec 13, 2017

Copy link
Copy Markdown
Contributor

This fixes a real problem so I'm going to risk it.

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.

chord does nothing when the header is an empty list

2 participants