-
Notifications
You must be signed in to change notification settings - Fork 468
Fix CrontabSchedule task run before schedule time #913
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
base: main
Are you sure you want to change the base?
Changes from all commits
874fc84
a582772
bdea5ee
6dd26e0
c4486c8
88c166f
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 | ||||
---|---|---|---|---|---|---|
|
@@ -94,13 +94,19 @@ def __init__(self, model, app=None): | |||||
|
||||||
if not model.last_run_at: | ||||||
model.last_run_at = model.date_changed or self._default_now() | ||||||
# if last_run_at is not set and | ||||||
# model.start_time last_run_at should be in way past. | ||||||
# This will trigger the job to run at start_time | ||||||
# and avoid the heap block. | ||||||
|
||||||
if self.model.start_time: | ||||||
model.last_run_at = model.last_run_at \ | ||||||
- datetime.timedelta(days=365 * 30) | ||||||
if isinstance(model.schedule, schedules.schedule) \ | ||||||
and not isinstance(model.schedule, schedules.crontab): | ||||||
auvipy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
# if last_run_at is not set and | ||||||
# model.start_time last_run_at should be in way past. | ||||||
# This will trigger the job to run at start_time | ||||||
# and avoid the heap block. | ||||||
model.last_run_at = model.last_run_at \ | ||||||
- datetime.timedelta(days=365 * 30) | ||||||
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. [nitpick] The
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
else: | ||||||
# last_run_at should be the time the task started. | ||||||
model.last_run_at = model.start_time | ||||||
|
||||||
self.last_run_at = model.last_run_at | ||||||
|
||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1035,6 +1035,24 @@ def test_crontab_with_start_time_after_crontab(self, app): | |||||||||||||||||||||||||
assert not is_due | ||||||||||||||||||||||||||
assert next_check == pytest.approx(expected_delay, abs=60) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def test_crontab_with_start_time_before_crontab(self, app): | ||||||||||||||||||||||||||
now = app.now() | ||||||||||||||||||||||||||
delay_minutes = 2 | ||||||||||||||||||||||||||
test_start_time = now - timedelta(minutes=delay_minutes) | ||||||||||||||||||||||||||
crontab_time = now + timedelta(minutes=delay_minutes) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# start_time(now - 2min) < now < crontab_time(now + 2min) | ||||||||||||||||||||||||||
task = self.create_model_crontab( | ||||||||||||||||||||||||||
crontab(minute=f'{crontab_time.minute}'), | ||||||||||||||||||||||||||
start_time=test_start_time) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
entry = EntryTrackSave(task, app=app) | ||||||||||||||||||||||||||
is_due, next_check = entry.is_due() | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
expected_delay = delay_minutes * 60 | ||||||||||||||||||||||||||
assert not is_due | ||||||||||||||||||||||||||
assert next_check < expected_delay | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def test_crontab_with_start_time_different_time_zone(self, app): | ||||||||||||||||||||||||||
now = app.now() | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
@@ -1083,31 +1101,42 @@ def test_crontab_with_start_time_different_time_zone(self, app): | |||||||||||||||||||||||||
assert next_check == pytest.approx(expected_delay, abs=60) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def test_crontab_with_start_time_tick(self, app): | ||||||||||||||||||||||||||
# Ensure the heapq does not block by new task with start_time | ||||||||||||||||||||||||||
PeriodicTask.objects.all().delete() | ||||||||||||||||||||||||||
s = self.Scheduler(app=self.app) | ||||||||||||||||||||||||||
assert not s._heap | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
m1 = self.create_model_interval(schedule(timedelta(seconds=3))) | ||||||||||||||||||||||||||
m1.save() | ||||||||||||||||||||||||||
s.tick() | ||||||||||||||||||||||||||
assert len(s._heap) == 2 | ||||||||||||||||||||||||||
Comment on lines
1107
to
+1112
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. [nitpick] Avoid inspecting the private
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
now = timezone.now() | ||||||||||||||||||||||||||
start_time = now + timedelta(minutes=1) | ||||||||||||||||||||||||||
crontab_trigger_time = now + timedelta(minutes=2) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# now < start_time(now + 1min) < crontab_time(now + 2min) | ||||||||||||||||||||||||||
m2 = self.create_model_crontab( | ||||||||||||||||||||||||||
crontab(minute=f'{crontab_trigger_time.minute}'), | ||||||||||||||||||||||||||
start_time=start_time) | ||||||||||||||||||||||||||
m2.save() | ||||||||||||||||||||||||||
s.tick() | ||||||||||||||||||||||||||
assert len(s._heap) == 3 | ||||||||||||||||||||||||||
assert s._heap[0][2].name == m1.name | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
e2 = EntryTrackSave(m2, app=self.app) | ||||||||||||||||||||||||||
is_due, _ = e2.is_due() | ||||||||||||||||||||||||||
is_due, delay = e2.is_due() | ||||||||||||||||||||||||||
assert not is_due | ||||||||||||||||||||||||||
assert 60 < delay < 120 | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
max_iterations = 1000 | ||||||||||||||||||||||||||
iterations = 0 | ||||||||||||||||||||||||||
while (not is_due and iterations < max_iterations): | ||||||||||||||||||||||||||
# tick twice to make sure the heap is not blocked by m2 | ||||||||||||||||||||||||||
# before it reaches its start_time | ||||||||||||||||||||||||||
time.sleep(3) | ||||||||||||||||||||||||||
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. Using real Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||||
s.tick() | ||||||||||||||||||||||||||
Comment on lines
+1134
to
+1135
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. Using
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||||
assert s._heap[0][2].name == m1.name | ||||||||||||||||||||||||||
with patch('time.monotonic', side_effect=lambda: monotonic() + 54): | ||||||||||||||||||||||||||
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. Patching
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||||||||||||||||
s.tick() | ||||||||||||||||||||||||||
assert s._heap[0][2].name != m2.name | ||||||||||||||||||||||||||
is_due, _ = e2.is_due() | ||||||||||||||||||||||||||
assert s._heap[0][2].name == m1.name | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@pytest.mark.django_db | ||||||||||||||||||||||||||
def test_crontab_exclusion_logic_basic(self): | ||||||||||||||||||||||||||
|
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.
[nitpick] For consistency with the surrounding code that uses the local
model
variable, consider replacingself.model.start_time
withmodel.start_time
.Copilot uses AI. Check for mistakes.