2017-11-16 00:43:27 +01:00
|
|
|
import logging
|
2021-11-14 19:31:59 +01:00
|
|
|
import os
|
2017-11-16 00:43:27 +01:00
|
|
|
import signal
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
from argparse import ArgumentParser
|
2021-11-14 19:31:59 +01:00
|
|
|
from contextlib import contextmanager
|
2016-07-30 05:23:46 +02:00
|
|
|
from types import FrameType
|
2021-12-23 06:53:42 +01:00
|
|
|
from typing import Any, Iterator, List, Optional
|
2016-06-04 16:52:18 +02:00
|
|
|
|
2013-08-29 23:41:03 +02:00
|
|
|
from django.conf import settings
|
2019-05-03 23:20:39 +02:00
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
2016-06-23 20:00:27 +02:00
|
|
|
from django.utils import autoreload
|
2020-09-18 23:13:13 +02:00
|
|
|
from sentry_sdk import configure_scope
|
2017-11-16 00:43:27 +01:00
|
|
|
|
|
|
|
from zerver.worker.queue_processors import get_active_worker_queues, get_worker
|
2013-08-29 23:41:03 +02:00
|
|
|
|
2020-01-14 21:59:46 +01:00
|
|
|
|
2021-11-14 19:31:59 +01:00
|
|
|
@contextmanager
|
|
|
|
def log_and_exit_if_exception(
|
|
|
|
logger: logging.Logger, queue_name: str, threaded: bool
|
|
|
|
) -> Iterator[None]:
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
except Exception:
|
|
|
|
logger.exception("Unhandled exception from queue: %s", queue_name, stack_info=True)
|
|
|
|
if threaded:
|
|
|
|
# Sending SIGUSR1 is the right way to exit - triggering the
|
|
|
|
# exit_with_three signal handler, causing exit and reload.
|
|
|
|
os.kill(os.getpid(), signal.SIGUSR1)
|
|
|
|
else:
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2013-08-29 23:41:03 +02:00
|
|
|
class Command(BaseCommand):
|
2017-10-26 11:35:57 +02:00
|
|
|
def add_arguments(self, parser: ArgumentParser) -> None:
|
2021-02-12 08:20:45 +01:00
|
|
|
parser.add_argument("--queue_name", metavar="<queue name>", help="queue to process")
|
2021-02-12 08:19:30 +01:00
|
|
|
parser.add_argument(
|
2021-02-12 08:20:45 +01:00
|
|
|
"--worker_num", metavar="<worker number>", type=int, default=0, help="worker label"
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
2021-02-12 08:20:45 +01:00
|
|
|
parser.add_argument("--all", action="store_true", help="run all queues")
|
2021-02-12 08:19:30 +01:00
|
|
|
parser.add_argument(
|
2021-02-12 08:20:45 +01:00
|
|
|
"--multi_threaded",
|
|
|
|
nargs="+",
|
|
|
|
metavar="<list of queue name>",
|
2021-02-12 08:19:30 +01:00
|
|
|
required=False,
|
|
|
|
help="list of queue to process",
|
|
|
|
)
|
2015-08-21 02:10:41 +02:00
|
|
|
|
2013-08-29 23:41:03 +02:00
|
|
|
help = "Runs a queue processing worker"
|
2016-11-29 07:22:02 +01:00
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def handle(self, *args: Any, **options: Any) -> None:
|
2013-08-29 23:41:03 +02:00
|
|
|
logging.basicConfig()
|
2021-02-12 08:20:45 +01:00
|
|
|
logger = logging.getLogger("process_queue")
|
2013-08-29 23:41:03 +02:00
|
|
|
|
2021-12-23 06:53:42 +01:00
|
|
|
def exit_with_three(signal: int, frame: Optional[FrameType]) -> None:
|
2017-07-03 12:52:55 +02:00
|
|
|
"""
|
|
|
|
This process is watched by Django's autoreload, so exiting
|
|
|
|
with status code 3 will cause this process to restart.
|
|
|
|
"""
|
2017-10-02 11:11:42 +02:00
|
|
|
logger.warning("SIGUSR1 received. Restarting this queue processor.")
|
2017-07-03 12:52:55 +02:00
|
|
|
sys.exit(3)
|
|
|
|
|
2013-10-23 21:14:17 +02:00
|
|
|
if not settings.USING_RABBITMQ:
|
2016-10-27 21:35:36 +02:00
|
|
|
# Make the warning silent when running the tests
|
|
|
|
if settings.TEST_SUITE:
|
|
|
|
logger.info("Not using RabbitMQ queue workers in the test suite.")
|
|
|
|
else:
|
|
|
|
logger.error("Cannot run a queue processor when USING_RABBITMQ is False!")
|
2019-05-03 23:20:39 +02:00
|
|
|
raise CommandError
|
2013-10-23 21:14:17 +02:00
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def run_threaded_workers(queues: List[str], logger: logging.Logger) -> None:
|
2016-11-16 21:05:54 +01:00
|
|
|
cnt = 0
|
2017-05-28 08:17:29 +02:00
|
|
|
for queue_name in queues:
|
2016-11-16 21:05:54 +01:00
|
|
|
if not settings.DEVELOPMENT:
|
2022-06-03 05:51:16 +02:00
|
|
|
logger.info("launching queue worker thread %s", queue_name)
|
2016-11-16 21:05:54 +01:00
|
|
|
cnt += 1
|
2021-11-16 16:30:47 +01:00
|
|
|
td = ThreadedWorker(queue_name, logger)
|
2015-11-24 07:01:35 +01:00
|
|
|
td.start()
|
2017-05-28 08:17:29 +02:00
|
|
|
assert len(queues) == cnt
|
2021-02-12 08:20:45 +01:00
|
|
|
logger.info("%d queue worker threads were launched", cnt)
|
2016-06-23 20:00:27 +02:00
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
if options["all"]:
|
2017-07-03 12:52:55 +02:00
|
|
|
signal.signal(signal.SIGUSR1, exit_with_three)
|
2018-02-02 05:43:18 +01:00
|
|
|
autoreload.run_with_reloader(run_threaded_workers, get_active_worker_queues(), logger)
|
2021-02-12 08:20:45 +01:00
|
|
|
elif options["multi_threaded"]:
|
2017-07-03 12:52:55 +02:00
|
|
|
signal.signal(signal.SIGUSR1, exit_with_three)
|
2021-02-12 08:20:45 +01:00
|
|
|
queues = options["multi_threaded"]
|
2018-02-02 05:43:18 +01:00
|
|
|
autoreload.run_with_reloader(run_threaded_workers, queues, logger)
|
2015-11-24 07:01:35 +01:00
|
|
|
else:
|
2021-02-12 08:20:45 +01:00
|
|
|
queue_name = options["queue_name"]
|
|
|
|
worker_num = options["worker_num"]
|
2015-11-24 07:01:35 +01:00
|
|
|
|
2021-12-23 06:53:42 +01:00
|
|
|
def signal_handler(signal: int, frame: Optional[FrameType]) -> None:
|
2020-05-02 08:44:14 +02:00
|
|
|
logger.info("Worker %d disconnecting from queue %s", worker_num, queue_name)
|
2015-11-24 07:01:35 +01:00
|
|
|
worker.stop()
|
|
|
|
sys.exit(0)
|
|
|
|
|
2020-09-18 23:13:13 +02:00
|
|
|
logger.info("Worker %d connecting to queue %s", worker_num, queue_name)
|
2021-11-14 19:31:59 +01:00
|
|
|
with log_and_exit_if_exception(logger, queue_name, threaded=False):
|
|
|
|
worker = get_worker(queue_name)
|
|
|
|
with configure_scope() as scope:
|
|
|
|
scope.set_tag("queue_worker", queue_name)
|
|
|
|
scope.set_tag("worker_num", worker_num)
|
2020-09-18 23:13:13 +02:00
|
|
|
|
2021-11-14 19:31:59 +01:00
|
|
|
worker.setup()
|
|
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
signal.signal(signal.SIGUSR1, signal_handler)
|
|
|
|
worker.ENABLE_TIMEOUTS = True
|
|
|
|
worker.start()
|
2015-11-24 07:01:35 +01:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2021-11-16 16:30:47 +01:00
|
|
|
class ThreadedWorker(threading.Thread):
|
2021-11-14 19:31:59 +01:00
|
|
|
def __init__(self, queue_name: str, logger: logging.Logger) -> None:
|
2015-11-24 07:01:35 +01:00
|
|
|
threading.Thread.__init__(self)
|
2021-11-14 19:31:59 +01:00
|
|
|
self.logger = logger
|
|
|
|
self.queue_name = queue_name
|
|
|
|
|
|
|
|
with log_and_exit_if_exception(logger, queue_name, threaded=True):
|
|
|
|
self.worker = get_worker(queue_name)
|
2015-11-24 07:01:35 +01:00
|
|
|
|
2017-10-26 11:35:57 +02:00
|
|
|
def run(self) -> None:
|
2021-11-14 19:31:59 +01:00
|
|
|
with configure_scope() as scope, log_and_exit_if_exception(
|
|
|
|
self.logger, self.queue_name, threaded=True
|
|
|
|
):
|
2020-09-18 23:13:13 +02:00
|
|
|
scope.set_tag("queue_worker", self.worker.queue_name)
|
|
|
|
self.worker.setup()
|
2022-06-03 05:51:16 +02:00
|
|
|
logging.debug("starting consuming %s", self.worker.queue_name)
|
2020-09-18 23:13:13 +02:00
|
|
|
self.worker.start()
|