mirror of https://github.com/peter4431/rq.git
Reliable queue (#1911)
* Use lmove() when working on a single queue * Skip reliable queue tests if Redis server doesn't support LMOVE * Better test coverage * job.origin should be string * Added test for job that gets orphaned if worker.execute_job() fails * Fix job tests * worker.run_maintenance_tasks() now cleans intermediate queues * Fixed import ordering * No need to run slow tests and flake8 on SSL tests * Minor typing fixes * Fixed lintingmain
parent
107221fd9e
commit
37ddcb51cd
@ -1,2 +1,2 @@
|
|||||||
redis>=3.5.0
|
redis>=4.0.0
|
||||||
click>=5.0.0
|
click>=5.0.0
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from .queue import Queue
|
||||||
|
from .utils import as_text
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .worker import BaseWorker
|
||||||
|
|
||||||
|
|
||||||
|
def clean_intermediate_queue(worker: 'BaseWorker', queue: Queue) -> None:
|
||||||
|
"""
|
||||||
|
Check whether there are any jobs stuck in the intermediate queue.
|
||||||
|
|
||||||
|
A job may be stuck in the intermediate queue if a worker has successfully dequeued a job
|
||||||
|
but was not able to push it to the StartedJobRegistry. This may happen in rare cases
|
||||||
|
of hardware or network failure.
|
||||||
|
|
||||||
|
We consider a job to be stuck in the intermediate queue if it doesn't exist in the StartedJobRegistry.
|
||||||
|
"""
|
||||||
|
job_ids = [as_text(job_id) for job_id in queue.connection.lrange(queue.intermediate_queue_key, 0, -1)]
|
||||||
|
for job_id in job_ids:
|
||||||
|
if job_id not in queue.started_job_registry:
|
||||||
|
job = queue.fetch_job(job_id)
|
||||||
|
worker.handle_job_failure(job, queue, exc_string='Job was stuck in the intermediate queue.')
|
||||||
|
queue.connection.lrem(queue.intermediate_queue_key, 1, job_id)
|
@ -0,0 +1,36 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from redis import Redis
|
||||||
|
|
||||||
|
from rq.job import JobStatus
|
||||||
|
from rq.maintenance import clean_intermediate_queue
|
||||||
|
from rq.queue import Queue
|
||||||
|
from rq.utils import get_version
|
||||||
|
from rq.worker import Worker
|
||||||
|
from tests import RQTestCase
|
||||||
|
from tests.fixtures import say_hello
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceTestCase(RQTestCase):
|
||||||
|
@unittest.skipIf(get_version(Redis()) < (6, 2, 0), 'Skip if Redis server < 6.2.0')
|
||||||
|
def test_cleanup_intermediate_queue(self):
|
||||||
|
"""Ensure jobs stuck in the intermediate queue are cleaned up."""
|
||||||
|
queue = Queue('foo', connection=self.testconn)
|
||||||
|
job = queue.enqueue(say_hello)
|
||||||
|
|
||||||
|
# If job execution fails after it's dequeued, job should be in the intermediate queue
|
||||||
|
# # and it's status is still QUEUED
|
||||||
|
with patch.object(Worker, 'execute_job'):
|
||||||
|
# mocked.execute_job.side_effect = Exception()
|
||||||
|
worker = Worker(queue, connection=self.testconn)
|
||||||
|
worker.work(burst=True)
|
||||||
|
|
||||||
|
self.assertEqual(job.get_status(), JobStatus.QUEUED)
|
||||||
|
self.assertFalse(job.id in queue.get_job_ids())
|
||||||
|
self.assertIsNotNone(self.testconn.lpos(queue.intermediate_queue_key, job.id))
|
||||||
|
# After cleaning up the intermediate queue, job status should be `FAILED`
|
||||||
|
# and job is also removed from the intermediate queue
|
||||||
|
clean_intermediate_queue(worker, queue)
|
||||||
|
self.assertEqual(job.get_status(), JobStatus.FAILED)
|
||||||
|
self.assertIsNone(self.testconn.lpos(queue.intermediate_queue_key, job.id))
|
Loading…
Reference in New Issue