2020-10-22 00:26:24 +00:00
|
|
|
import time
|
|
|
|
from multiprocessing import Process
|
2024-10-17 04:15:47 +00:00
|
|
|
from unittest import mock
|
2020-10-22 00:26:24 +00:00
|
|
|
|
2022-11-21 13:20:53 +00:00
|
|
|
from redis import Redis
|
|
|
|
|
2020-10-22 00:26:24 +00:00
|
|
|
from rq import Queue, Worker
|
2020-11-14 12:59:20 +00:00
|
|
|
from rq.command import send_command, send_kill_horse_command, send_shutdown_command, send_stop_job_command
|
|
|
|
from rq.exceptions import InvalidJobOperation, NoSuchJobError
|
2022-11-21 13:20:53 +00:00
|
|
|
from rq.serializers import JSONSerializer
|
2020-11-14 12:59:20 +00:00
|
|
|
from rq.worker import WorkerStatus
|
2023-05-17 16:19:14 +00:00
|
|
|
from tests import RQTestCase
|
2024-10-17 04:15:47 +00:00
|
|
|
from tests.fixtures import _send_kill_horse_command, _send_shutdown_command, long_running_job, raise_exc_mock
|
2020-10-22 00:26:24 +00:00
|
|
|
|
|
|
|
|
2022-11-21 13:20:53 +00:00
|
|
|
def start_work(queue_name, worker_name, connection_kwargs):
|
2023-05-01 05:44:32 +00:00
|
|
|
worker = Worker(queue_name, name=worker_name, connection=Redis(**connection_kwargs))
|
2022-11-21 13:20:53 +00:00
|
|
|
worker.work()
|
|
|
|
|
|
|
|
|
|
|
|
def start_work_burst(queue_name, worker_name, connection_kwargs):
|
2023-05-01 05:44:32 +00:00
|
|
|
worker = Worker(queue_name, name=worker_name, connection=Redis(**connection_kwargs), serializer=JSONSerializer)
|
2022-11-21 13:20:53 +00:00
|
|
|
worker.work(burst=True)
|
|
|
|
|
|
|
|
|
2020-10-22 00:26:24 +00:00
|
|
|
class TestCommands(RQTestCase):
|
|
|
|
def test_shutdown_command(self):
|
|
|
|
"""Ensure that shutdown command works properly."""
|
2024-02-21 00:37:15 +00:00
|
|
|
connection = self.connection
|
2020-10-22 00:26:24 +00:00
|
|
|
worker = Worker('foo', connection=connection)
|
|
|
|
|
2023-05-01 05:44:32 +00:00
|
|
|
p = Process(
|
|
|
|
target=_send_shutdown_command, args=(worker.name, connection.connection_pool.connection_kwargs.copy())
|
|
|
|
)
|
2020-10-22 00:26:24 +00:00
|
|
|
p.start()
|
|
|
|
worker.work()
|
|
|
|
p.join(1)
|
|
|
|
|
2024-10-17 04:15:47 +00:00
|
|
|
def test_pubsub_thread_survives_connection_error(self):
|
|
|
|
"""Ensure that the pubsub thread is still alive after its Redis connection is killed"""
|
|
|
|
connection = self.connection
|
|
|
|
worker = Worker('foo', connection=connection)
|
|
|
|
worker.subscribe()
|
|
|
|
|
|
|
|
assert worker.pubsub_thread.is_alive()
|
|
|
|
|
|
|
|
for client in connection.client_list():
|
2024-11-20 08:34:27 +00:00
|
|
|
connection.client_kill(client['addr'])
|
2024-10-17 04:15:47 +00:00
|
|
|
|
|
|
|
time.sleep(0.0) # Allow other threads to run
|
|
|
|
assert worker.pubsub_thread.is_alive()
|
|
|
|
|
|
|
|
def test_pubsub_thread_exits_other_error(self):
|
|
|
|
"""Ensure that the pubsub thread exits on other than redis.exceptions.ConnectionError"""
|
|
|
|
connection = self.connection
|
|
|
|
worker = Worker('foo', connection=connection)
|
|
|
|
|
2024-11-20 08:34:27 +00:00
|
|
|
with mock.patch('redis.client.PubSub.get_message', new_callable=raise_exc_mock):
|
2024-10-17 04:15:47 +00:00
|
|
|
worker.subscribe()
|
|
|
|
|
|
|
|
worker.pubsub_thread.join()
|
|
|
|
assert not worker.pubsub_thread.is_alive()
|
|
|
|
|
2020-10-22 00:26:24 +00:00
|
|
|
def test_kill_horse_command(self):
|
|
|
|
"""Ensure that shutdown command works properly."""
|
2024-02-21 00:37:15 +00:00
|
|
|
connection = self.connection
|
2020-10-22 00:26:24 +00:00
|
|
|
queue = Queue('foo', connection=connection)
|
|
|
|
job = queue.enqueue(long_running_job, 4)
|
|
|
|
worker = Worker('foo', connection=connection)
|
|
|
|
|
2023-05-01 05:44:32 +00:00
|
|
|
p = Process(
|
|
|
|
target=_send_kill_horse_command, args=(worker.name, connection.connection_pool.connection_kwargs.copy())
|
|
|
|
)
|
2020-10-22 00:26:24 +00:00
|
|
|
p.start()
|
|
|
|
worker.work(burst=True)
|
|
|
|
p.join(1)
|
|
|
|
job.refresh()
|
2020-11-14 12:59:20 +00:00
|
|
|
self.assertTrue(job.id in queue.failed_job_registry)
|
|
|
|
|
2023-05-01 05:44:32 +00:00
|
|
|
p = Process(target=start_work, args=('foo', worker.name, connection.connection_pool.connection_kwargs.copy()))
|
2020-11-14 12:59:20 +00:00
|
|
|
p.start()
|
|
|
|
p.join(2)
|
|
|
|
|
|
|
|
send_kill_horse_command(connection, worker.name)
|
|
|
|
worker.refresh()
|
|
|
|
# Since worker is not busy, command will be ignored
|
|
|
|
self.assertEqual(worker.get_state(), WorkerStatus.IDLE)
|
|
|
|
send_shutdown_command(connection, worker.name)
|
|
|
|
|
|
|
|
def test_stop_job_command(self):
|
|
|
|
"""Ensure that stop_job command works properly."""
|
|
|
|
|
2024-02-21 00:37:15 +00:00
|
|
|
connection = self.connection
|
2021-08-24 00:40:29 +00:00
|
|
|
queue = Queue('foo', connection=connection, serializer=JSONSerializer)
|
2020-11-14 12:59:20 +00:00
|
|
|
job = queue.enqueue(long_running_job, 3)
|
2021-08-24 00:40:29 +00:00
|
|
|
worker = Worker('foo', connection=connection, serializer=JSONSerializer)
|
2020-11-14 12:59:20 +00:00
|
|
|
|
|
|
|
# If job is not executing, an error is raised
|
|
|
|
with self.assertRaises(InvalidJobOperation):
|
2021-08-24 00:40:29 +00:00
|
|
|
send_stop_job_command(connection, job_id=job.id, serializer=JSONSerializer)
|
2020-11-14 12:59:20 +00:00
|
|
|
|
2021-02-09 01:19:33 +00:00
|
|
|
# An exception is raised if job ID is invalid
|
2020-11-14 12:59:20 +00:00
|
|
|
with self.assertRaises(NoSuchJobError):
|
2021-08-24 00:40:29 +00:00
|
|
|
send_stop_job_command(connection, job_id='1', serializer=JSONSerializer)
|
2020-11-14 12:59:20 +00:00
|
|
|
|
2023-05-01 05:44:32 +00:00
|
|
|
p = Process(
|
|
|
|
target=start_work_burst, args=('foo', worker.name, connection.connection_pool.connection_kwargs.copy())
|
|
|
|
)
|
2020-11-14 12:59:20 +00:00
|
|
|
p.start()
|
|
|
|
p.join(1)
|
|
|
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
|
|
|
send_command(connection, worker.name, 'stop-job', job_id=1)
|
|
|
|
time.sleep(0.25)
|
|
|
|
# Worker still working due to job_id mismatch
|
|
|
|
worker.refresh()
|
|
|
|
self.assertEqual(worker.get_state(), WorkerStatus.BUSY)
|
|
|
|
|
2021-08-24 00:40:29 +00:00
|
|
|
send_stop_job_command(connection, job_id=job.id, serializer=JSONSerializer)
|
2020-11-14 12:59:20 +00:00
|
|
|
time.sleep(0.25)
|
2021-02-09 01:19:33 +00:00
|
|
|
|
|
|
|
# Job status is set appropriately
|
|
|
|
self.assertTrue(job.is_stopped)
|
|
|
|
|
|
|
|
# Worker has stopped working
|
2020-11-14 12:59:20 +00:00
|
|
|
worker.refresh()
|
|
|
|
self.assertEqual(worker.get_state(), WorkerStatus.IDLE)
|