Skip to content

Commit 6e2a78d

Browse files
committed
fix:infinite loop on callback calling is_scheduled()
1 parent fb5636f commit 6e2a78d

File tree

6 files changed

+36
-28
lines changed

6 files changed

+36
-28
lines changed

docs/changelog.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# Changelog
22

3+
## v1.2.1 🌈
4+
5+
### 🐛 Bug Fixes
6+
7+
- Fix infinite loop on callback calling is_scheduled() #37.
8+
39
## v1.2.0 🌈
410

511
### 🚀 Features
612

713
- Rename `*Job` models to `*Task` to differentiate.
814

9-
1015
## v1.1.0 🌈
1116

1217
### 🚀 Features
@@ -16,7 +21,7 @@
1621

1722
### 🐛 Bug Fixes
1823

19-
- #32 Running jobs should be marked as scheduled jobs. @rstalbow (#33)
24+
- #32 Running jobs should be marked as scheduled jobs. @rstalbow (#33)
2025

2126
## v1.0.2 🌈
2227

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "django-tasks-scheduler"
77
packages = [
88
{ include = "scheduler" },
99
]
10-
version = "1.2.0"
10+
version = "1.2.1"
1111
description = "An async job scheduler for django using redis"
1212
readme = "README.md"
1313
keywords = ["redis", "django", "background-jobs", "job-queue", "task-queue", "redis-queue", "scheduled-jobs"]

scheduler/admin/job.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ def enable_selected(self, request, queryset):
139139

140140
@admin.action(description="Enqueue now", permissions=('change',))
141141
def enqueue_job_now(self, request, queryset):
142-
job_names = []
143-
for job in queryset:
144-
job.enqueue_to_run()
145-
job_names.append(job.name)
146-
self.message_user(request, f"The following jobs have been enqueued: {', '.join(job_names)}", )
142+
task_names = []
143+
for task in queryset:
144+
task.enqueue_to_run()
145+
task_names.append(task.name)
146+
self.message_user(request, f"The following jobs have been enqueued: {', '.join(task_names)}", )

scheduler/models/scheduled_task.py

+19-16
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@
2525

2626

2727
def callback_save_job(job, connection, result, *args, **kwargs):
28-
model_name = job.meta.get('job_type', None)
28+
model_name = job.meta.get('task_type', None)
2929
if model_name is None:
3030
return
3131
model = apps.get_model(app_label='scheduler', model_name=model_name)
3232
task = model.objects.filter(job_id=job.id).first()
3333
if task is not None:
34-
task.unschedule()
35-
task.schedule()
34+
task.force_schedule()
3635

3736

3837
class BaseTask(models.Model):
@@ -80,18 +79,20 @@ def callable_func(self):
8079
"""Translate callable string to callable"""
8180
return tools.callable_func(self.callable)
8281

83-
@admin.display(boolean=True, description=_('is next scheduled?'))
82+
@admin.display(boolean=True, description=_('is scheduled?'))
8483
def is_scheduled(self) -> bool:
8584
"""Check whether a next job for this task is queued/scheduled to be executed"""
86-
if not self.job_id: # no job_id => is not scheduled
85+
if self.job_id is None: # no job_id => is not scheduled
8786
return False
8887
# check whether job_id is in scheduled/enqueued/active jobs
8988
scheduled_jobs = self.rqueue.scheduled_job_registry.get_job_ids()
9089
enqueued_jobs = self.rqueue.get_job_ids()
90+
active_jobs = self.rqueue.started_job_registry.get_job_ids()
9191
res = ((self.job_id in scheduled_jobs)
92-
or (self.job_id in enqueued_jobs))
93-
# If the job_id is not scheduled/enqueued, update the job_id to None.
94-
# (The job_id belongs to a previous run which is completed or currently running)
92+
or (self.job_id in enqueued_jobs)
93+
or (self.job_id in active_jobs))
94+
# If the job_id is not scheduled/enqueued/started,
95+
# update the job_id to None. (The job_id belongs to a previous run which is completed)
9596
if not res:
9697
self.job_id = None
9798
super(BaseTask, self).save()
@@ -132,8 +133,8 @@ def _enqueue_args(self) -> Dict:
132133
res = dict(
133134
meta=dict(
134135
repeat=self.repeat,
135-
job_type=self.TASK_TYPE,
136-
scheduled_job_id=self.id,
136+
task_type=self.TASK_TYPE,
137+
scheduled_task_id=self.id,
137138
),
138139
on_success=callback_save_job,
139140
on_failure=callback_save_job,
@@ -175,6 +176,11 @@ def schedule(self) -> bool:
175176
"""
176177
if not self.ready_for_schedule():
177178
return False
179+
self.force_schedule()
180+
return True
181+
182+
def force_schedule(self):
183+
"""Schedule task regardless of its current status"""
178184
schedule_time = self._schedule_time()
179185
kwargs = self._enqueue_args()
180186
job = self.rqueue.enqueue_at(
@@ -184,19 +190,17 @@ def schedule(self) -> bool:
184190
**kwargs, )
185191
self.job_id = job.id
186192
super(BaseTask, self).save()
187-
return True
188193

189194
def enqueue_to_run(self) -> bool:
190-
"""Enqueue job to run now.
191-
"""
195+
"""Enqueue job to run now."""
192196
kwargs = self._enqueue_args()
193197
job = self.rqueue.enqueue(
194198
tools.run_task,
195199
args=(self.TASK_TYPE, self.id),
196200
**kwargs,
197201
)
198202
self.job_id = job.id
199-
super(BaseTask, self).save()
203+
self.save(schedule_job=False)
200204
return True
201205

202206
def unschedule(self) -> bool:
@@ -217,8 +221,7 @@ def _schedule_time(self):
217221
return utc(self.scheduled_time)
218222

219223
def to_dict(self) -> Dict:
220-
"""Export model to dictionary, so it can be saved as external file backup
221-
"""
224+
"""Export model to dictionary, so it can be saved as external file backup"""
222225
res = dict(
223226
model=self.TASK_TYPE,
224227
name=self.name,

scheduler/rq_classes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def is_scheduled_task(self):
6161
return self.meta.get('scheduled_task_id', None) is not None
6262

6363
def is_execution_of(self, scheduled_job):
64-
return (self.meta.get('job_type', None) == scheduled_job.TASK_TYPE
64+
return (self.meta.get('task_type', None) == scheduled_job.TASK_TYPE
6565
and self.meta.get('scheduled_task_id', None) == scheduled_job.id)
6666

6767
def stop_execution(self, connection: Redis):

scheduler/tests/test_models.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,9 @@ def test_admin_enqueue_job_now(self):
333333
# assert part 1
334334
self.assertEqual(200, res.status_code)
335335
entry = _get_job_from_scheduled_registry(task)
336-
task_model, scheduled_job_id = entry.args
336+
task_model, scheduled_task_id = entry.args
337337
self.assertEqual(task_model, task.TASK_TYPE)
338-
self.assertEqual(scheduled_job_id, task.id)
338+
self.assertEqual(scheduled_task_id, task.id)
339339
self.assertEqual('scheduled', entry.get_status())
340340
assert_has_execution_with_status(task, 'queued')
341341

@@ -346,7 +346,7 @@ def test_admin_enqueue_job_now(self):
346346
# assert 2
347347
entry = _get_job_from_scheduled_registry(task)
348348
self.assertEqual(task_model, task.TASK_TYPE)
349-
self.assertEqual(scheduled_job_id, task.id)
349+
self.assertEqual(scheduled_task_id, task.id)
350350
assert_has_execution_with_status(task, 'finished')
351351

352352
def test_admin_enable_job(self):

0 commit comments

Comments
 (0)