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 for multiple job dependencies #279
Changes from all commits
fb02367
e3ab0d6
04dad3c
d4b64db
5ab2f30
8666c41
d45b575
fa56474
388184e
99ad58d
d009930
c226a42
28a8e0a
41e0594
091f2ef
f4deab5
f64e327
0122487
26add7d
f572f70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,6 @@ | |
NoSuchJobError, UnpickleError) | ||
from .compat import total_ordering, string_types, as_text | ||
|
||
from redis import WatchError | ||
|
||
|
||
def get_failed_queue(connection=None): | ||
"""Returns a handle to the special failed queue.""" | ||
|
@@ -146,28 +144,19 @@ def enqueue_call(self, func, args=None, kwargs=None, timeout=None, | |
""" | ||
timeout = timeout or self._default_timeout | ||
|
||
# TODO: job with dependency shouldn't have "queued" as status | ||
job = Job.create(func, args, kwargs, connection=self.connection, | ||
result_ttl=result_ttl, status=Status.QUEUED, | ||
result_ttl=result_ttl, status=None if depends_on else Status.QUEUED, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, a job always starts with I think there's a bigger problem here related to what you're talking about. A user could call Thus, user error can lead to inconsistent state. How much of the lower-level (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The main trouble is that there are many states and it's currently inconceivable from the implementation what the allowed/correct/desired state transitions are and which are not. Even more confusing, prior to this patch, state wasn't anything that RQ was really depending on—it was just there for monitoring purposes / user convenience. Now, we're making decisions based on this value, which was never the intention. The property wasn't designed with a 100% correctness guarantee in mind for a computer to trust. Additionally, I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe even with this patch, RQ still doesn't depend on status value. I just made the distinction that if a new job depends on other jobs, it's not really enqueued, so I set the status to None. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess my point would be that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
description=description, depends_on=depends_on, timeout=timeout) | ||
|
||
# If job depends on an unfinished job, register itself on it's | ||
# parent's dependents instead of enqueueing it. | ||
# If WatchError is raised in the process, that means something else is | ||
# modifying the dependency. In this case we simply retry | ||
if depends_on is not None: | ||
with self.connection.pipeline() as pipe: | ||
while True: | ||
try: | ||
pipe.watch(depends_on.key) | ||
if depends_on.status != Status.FINISHED: | ||
job.register_dependency() | ||
job.save() | ||
return job | ||
break | ||
except WatchError: | ||
continue | ||
|
||
# A job having unmet dependencies will not be enqueued right away | ||
if depends_on: | ||
if isinstance(depends_on, Job): | ||
depends_on = [depends_on] | ||
remaining_dependencies = job.register_dependencies(depends_on) | ||
if remaining_dependencies: | ||
job.save() | ||
return job | ||
|
||
return self.enqueue_job(job) | ||
|
||
def enqueue(self, f, *args, **kwargs): | ||
|
@@ -233,16 +222,6 @@ def enqueue_job(self, job, set_meta_data=True): | |
job.save() | ||
return job | ||
|
||
def enqueue_dependents(self, job): | ||
"""Enqueues all jobs in the given job's dependents set and clears it.""" | ||
# TODO: can probably be pipelined | ||
while True: | ||
job_id = as_text(self.connection.spop(job.dependents_key)) | ||
if job_id is None: | ||
break | ||
dependent = Job.fetch(job_id, connection=self.connection) | ||
self.enqueue_job(dependent) | ||
|
||
def pop_job_id(self): | ||
"""Pops a given job ID from this Redis queue.""" | ||
return as_text(self.connection.lpop(self.key)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are these cleaned up?