Skip to content

Commit b7bcdc3

Browse files
committed
[IMP] queue_job: cancel job before it retries
1 parent d522451 commit b7bcdc3

File tree

4 files changed

+91
-2
lines changed

4 files changed

+91
-2
lines changed

queue_job/job.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,17 @@ def store(self):
591591

592592
db_record = self.db_record()
593593
if db_record:
594+
# If job was cancelled manually, do not revive it for a retry
595+
if (
596+
self.retry > 0
597+
and db_record.state == CANCELLED
598+
and self.state in (PENDING, FAILED)
599+
):
600+
self.state = CANCELLED
601+
self.date_cancelled = db_record.date_cancelled
602+
if not self.result and db_record.result:
603+
self.result = db_record.result
604+
594605
db_record.with_context(_job_edit_sentinel=edit_sentinel).write(
595606
self._store_values()
596607
)

queue_job/views/queue_job_views.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/>
2626
<button
2727
name="button_cancelled"
28-
states="pending,enqueued,failed"
28+
states="pending,enqueued,started,failed"
2929
class="oe_highlight"
3030
string="Cancel job"
3131
type="object"

queue_job/wizards/queue_jobs_to_cancelled.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class SetJobsToCancelled(models.TransientModel):
1111

1212
def set_cancelled(self):
1313
jobs = self.job_ids.filtered(
14-
lambda x: x.state in ("pending", "failed", "enqueued")
14+
lambda x: x.state in ("pending", "failed", "enqueued", "started")
1515
)
1616
jobs.button_cancelled()
1717
return {"type": "ir.actions.act_window_close"}

test_queue_job/tests/test_job.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,84 @@ def test_job_identity_key_func_exact(self):
384384
job1 = Job.load(self.env, test_job_1.uuid)
385385
self.assertEqual(job1.identity_key, expected_key)
386386

387+
def test_job_cancelled_while_started(self):
388+
# Job finishes successfully:
389+
# if record was set to cancel in the meantime, it is overriden
390+
test_job = Job(self.method, max_retries=3)
391+
test_job.store()
392+
test_job.set_enqueued()
393+
test_job.set_started()
394+
test_job.store()
395+
stored = self.queue_job.search([("uuid", "=", test_job.uuid)])
396+
stored.button_cancelled()
397+
# Simulate successful job
398+
test_job.perform()
399+
datetime_path = "odoo.addons.queue_job.job.datetime"
400+
with mock.patch(datetime_path, autospec=True) as mock_datetime:
401+
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
402+
test_job.set_done("Job ended successfully")
403+
self.assertEqual(test_job.state, DONE)
404+
self.assertEqual(test_job.retry, 1)
405+
test_job.store()
406+
self.assertEqual(stored.state, DONE)
407+
self.assertEqual(stored.date_cancelled, False)
408+
self.assertEqual(stored.date_done, datetime(2015, 3, 15, 16, 41))
409+
self.assertEqual(stored.retry, 1)
410+
self.assertEqual(stored.result, "Job ended successfully")
411+
412+
def test_job_cancelled_while_started_not_retried(self):
413+
# Job fails with retryable error:
414+
# if record was set to cancel in the meantime, it is not retried
415+
test_job = Job(self.method, kwargs={"raise_retry": True}, max_retries=3)
416+
test_job.store()
417+
test_job.set_enqueued()
418+
test_job.set_started()
419+
test_job.store()
420+
stored = self.queue_job.search([("uuid", "=", test_job.uuid)])
421+
datetime_path = "odoo.addons.queue_job.job.datetime"
422+
with mock.patch(datetime_path, autospec=True) as mock_datetime:
423+
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
424+
stored.button_cancelled()
425+
# Simulate fail/retry job
426+
with self.assertRaises(RetryableJobError) as cm:
427+
test_job.perform()
428+
test_job.set_pending(result=str(cm.exception), reset_retry=False)
429+
self.assertEqual(test_job.state, PENDING)
430+
self.assertEqual(test_job.retry, 1)
431+
test_job.store()
432+
self.assertEqual(stored.state, CANCELLED)
433+
self.assertEqual(stored.date_cancelled, datetime(2015, 3, 15, 16, 41))
434+
self.assertEqual(stored.date_done, False)
435+
self.assertEqual(stored.retry, 1)
436+
self.assertEqual(stored.result, "Must be retried later")
437+
438+
def test_job_cancelled_while_started_failed(self):
439+
# Job fails:
440+
# if record was set to cancel in the meantime, keep state cancelled
441+
test_job = Job(self.method, max_retries=3)
442+
test_job.store()
443+
test_job.set_enqueued()
444+
test_job.set_started()
445+
test_job.store()
446+
stored = self.queue_job.search([("uuid", "=", test_job.uuid)])
447+
datetime_path = "odoo.addons.queue_job.job.datetime"
448+
with mock.patch(datetime_path, autospec=True) as mock_datetime:
449+
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
450+
stored.button_cancelled()
451+
# Simulate failed job
452+
test_job.perform()
453+
test_job.set_failed(result=False, exc_info="failed test", exc_name="FailedTest")
454+
self.assertEqual(test_job.state, FAILED)
455+
self.assertEqual(test_job.retry, 1)
456+
test_job.store()
457+
self.assertEqual(stored.state, CANCELLED)
458+
self.assertEqual(stored.date_cancelled, datetime(2015, 3, 15, 16, 41))
459+
self.assertEqual(stored.date_done, False)
460+
self.assertEqual(stored.retry, 1)
461+
self.assertEqual(stored.result, "Cancelled by OdooBot")
462+
self.assertEqual(stored.exc_info, "failed test")
463+
self.assertEqual(stored.exc_name, "FailedTest")
464+
387465

388466
class TestJobs(JobCommonCase):
389467
"""Test jobs on other methods or with different job configuration"""

0 commit comments

Comments
 (0)