Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support chords with empty headers #4443

Merged
merged 1 commit into from Dec 13, 2017

Conversation

AlexHill
Copy link
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
@thedrow
Copy link
Member

thedrow commented Dec 13, 2017

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

@AlexHill
Copy link
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 :)

Copy link
Member

@thedrow thedrow left a comment

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.

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
Member

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?

# Overrides this to avoid calling GroupResult.save
# pylint: disable=method-hidden
# Note that KeyValueStoreBackend.__init__ sets self.apply_chord
# if the implements_incr attr is set. Redis backend doesn't set
# this flag.
options['task_id'] = group_id
return header(*partial_args, **options or {})
pass
Copy link
Member

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
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
Copy link
Contributor Author

AlexHill commented Dec 13, 2017

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
Copy link
Member

thedrow commented Dec 13, 2017

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