2018-02-05 14:58:02 +00:00
|
|
|
import time
|
2021-03-10 13:55:06 +00:00
|
|
|
|
2017-08-31 12:52:06 +01:00
|
|
|
from celery import Celery, Task
|
2018-07-12 15:09:38 +01:00
|
|
|
from celery.signals import worker_process_shutdown
|
2020-04-24 11:21:41 +01:00
|
|
|
from flask import g, request
|
2021-03-10 13:55:06 +00:00
|
|
|
from flask.ctx import has_app_context, has_request_context
|
2018-07-12 15:09:38 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@worker_process_shutdown.connect
|
2020-04-24 11:21:41 +01:00
|
|
|
def log_on_worker_shutdown(sender, signal, pid, exitcode, **kwargs):
|
|
|
|
|
# imported here to avoid circular imports
|
|
|
|
|
from app import notify_celery
|
|
|
|
|
|
|
|
|
|
# if the worker has already restarted at least once, then we no longer have app context and current_app won't work
|
|
|
|
|
# to create a new one. Instead we have to create a new app context from the original flask app and use that instead.
|
|
|
|
|
with notify_celery._app.app_context():
|
|
|
|
|
# if the worker has restarted
|
|
|
|
|
notify_celery._app.logger.info('worker shutdown: PID: {} Exitcode: {}'.format(pid, exitcode))
|
2017-08-31 12:52:06 +01:00
|
|
|
|
|
|
|
|
|
2021-04-13 14:53:46 +01:00
|
|
|
def make_task(app):
|
2018-02-12 15:29:03 +00:00
|
|
|
class NotifyTask(Task):
|
|
|
|
|
abstract = True
|
|
|
|
|
start = None
|
Temporarily disable task argument checking
This was added in Celery 4 [1]. and appears to be incompatible with
our approach of injecting "request_id" into task arguments (example
exception below). Although our other apps are on Celery 5 our logs
don't show any similar issues, probably because all their tasks are
invoked without request IDs. In the longterm we should decide if we
want to enable argument checking and fix the tracing approach, or
stop tracing request IDs in Celery tasks.
[1]: https://docs.celeryproject.org/en/stable/userguide/tasks.html#argument-checking
2021-11-01T11:37:36 delivery delivery ERROR None "RETRY: Email notification f69a9305-686f-42eb-a2ee-61bc2ba1f5f3 failed" [in /Users/benthorner/Documents/Projects/api/app/celery/provider_tasks.py:68]
Traceback (most recent call last):
File "/Users/benthorner/Documents/Projects/api/app/celery/provider_tasks.py", line 53, in deliver_email
raise TypeError("test retry")
TypeError: test retry
[2021-11-01 11:37:36,385: ERROR/ForkPoolWorker-1] RETRY: Email notification f69a9305-686f-42eb-a2ee-61bc2ba1f5f3 failed
Traceback (most recent call last):
File "/Users/benthorner/Documents/Projects/api/app/celery/provider_tasks.py", line 53, in deliver_email
raise TypeError("test retry")
TypeError: test retry
[2021-11-01 11:37:36,394: WARNING/ForkPoolWorker-1] Task deliver_email[449cd221-173c-4e18-83ac-229e88c029a5] reject requeue=False: deliver_email() got an unexpected keyword argument 'request_id'
Traceback (most recent call last):
File "/Users/benthorner/Documents/Projects/api/app/celery/provider_tasks.py", line 53, in deliver_email
raise TypeError("test retry")
TypeError: test retry
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/benthorner/.pyenv/versions/notifications-api/lib/python3.6/site-packages/celery/app/task.py", line 731, in retry
S.apply_async()
File "/Users/benthorner/.pyenv/versions/notifications-api/lib/python3.6/site-packages/celery/canvas.py", line 219, in apply_async
return _apply(args, kwargs, **options)
File "/Users/benthorner/.pyenv/versions/notifications-api/lib/python3.6/site-packages/celery/app/task.py", line 537, in apply_async
check_arguments(*(args or ()), **(kwargs or {}))
TypeError: deliver_email() got an unexpected keyword argument 'request_id'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/benthorner/.pyenv/versions/notifications-api/lib/python3.6/site-packages/celery/app/trace.py", line 450, in trace_task
R = retval = fun(*args, **kwargs)
File "/Users/benthorner/Documents/Projects/api/app/celery/celery.py", line 74, in __call__
return super().__call__(*args, **kwargs)
File "/Users/benthorner/.pyenv/versions/notifications-api/lib/python3.6/site-packages/celery/app/trace.py", line 731, in __protected_call__
return self.run(*args, **kwargs)
File "/Users/benthorner/Documents/Projects/api/app/celery/provider_tasks.py", line 71, in deliver_email
self.retry(queue=QueueNames.RETRY)
File "/Users/benthorner/.pyenv/versions/notifications-api/lib/python3.6/site-packages/celery/app/task.py", line 733, in retry
raise Reject(exc, requeue=False)
celery.exceptions.Reject: (TypeError("deliver_email() got an unexpected keyword argument 'request_id'",), False)
2021-11-01 11:39:57 +00:00
|
|
|
typing = False
|
2018-02-05 14:58:02 +00:00
|
|
|
|
2018-02-12 15:29:03 +00:00
|
|
|
def on_success(self, retval, task_id, args, kwargs):
|
2021-04-07 13:56:10 +01:00
|
|
|
elapsed_time = time.monotonic() - self.start
|
2021-04-07 14:10:39 +01:00
|
|
|
delivery_info = self.request.delivery_info or {}
|
|
|
|
|
queue_name = delivery_info.get('routing_key', 'none')
|
|
|
|
|
|
2018-02-12 15:29:03 +00:00
|
|
|
app.logger.info(
|
2021-04-07 14:10:39 +01:00
|
|
|
"Celery task {task_name} (queue: {queue_name}) took {time}".format(
|
|
|
|
|
task_name=self.name,
|
|
|
|
|
queue_name=queue_name,
|
|
|
|
|
time="{0:.4f}".format(elapsed_time)
|
2018-02-12 15:29:03 +00:00
|
|
|
)
|
2018-02-05 14:58:02 +00:00
|
|
|
)
|
2017-08-31 12:52:06 +01:00
|
|
|
|
2021-04-13 14:53:46 +01:00
|
|
|
app.statsd_client.timing(
|
2021-04-07 14:32:24 +01:00
|
|
|
"celery.{queue_name}.{task_name}.success".format(
|
|
|
|
|
task_name=self.name,
|
|
|
|
|
queue_name=queue_name
|
|
|
|
|
), elapsed_time
|
|
|
|
|
)
|
|
|
|
|
|
2018-02-12 15:29:03 +00:00
|
|
|
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
2021-04-07 14:10:39 +01:00
|
|
|
delivery_info = self.request.delivery_info or {}
|
|
|
|
|
queue_name = delivery_info.get('routing_key', 'none')
|
|
|
|
|
|
|
|
|
|
app.logger.exception(
|
|
|
|
|
"Celery task {task_name} (queue: {queue_name}) failed".format(
|
|
|
|
|
task_name=self.name,
|
|
|
|
|
queue_name=queue_name,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2021-04-13 14:53:46 +01:00
|
|
|
app.statsd_client.incr(
|
2021-04-07 14:32:24 +01:00
|
|
|
"celery.{queue_name}.{task_name}.failure".format(
|
|
|
|
|
task_name=self.name,
|
|
|
|
|
queue_name=queue_name
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-02-12 15:29:03 +00:00
|
|
|
super().on_failure(exc, task_id, args, kwargs, einfo)
|
|
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
|
# ensure task has flask context to access config, logger, etc
|
|
|
|
|
with app.app_context():
|
2021-04-07 13:56:10 +01:00
|
|
|
self.start = time.monotonic()
|
2021-11-10 15:42:51 +00:00
|
|
|
# TEMPORARY: remove old piggyback values from kwargs
|
|
|
|
|
kwargs.pop('request_id', None)
|
|
|
|
|
# Add 'request_id' to 'g' so that it gets logged. Note
|
|
|
|
|
# that each header is a direct attribute of the task
|
|
|
|
|
# context (aka "request").
|
|
|
|
|
g.request_id = self.request.get('notify_request_id')
|
2021-04-07 14:10:39 +01:00
|
|
|
|
2018-02-12 15:29:03 +00:00
|
|
|
return super().__call__(*args, **kwargs)
|
2017-08-31 12:52:06 +01:00
|
|
|
|
2018-02-12 15:29:03 +00:00
|
|
|
return NotifyTask
|
2017-06-01 14:32:19 +01:00
|
|
|
|
2016-02-09 13:31:45 +00:00
|
|
|
|
|
|
|
|
class NotifyCelery(Celery):
|
2017-06-09 16:20:02 +01:00
|
|
|
|
2021-04-13 14:53:46 +01:00
|
|
|
def init_app(self, app):
|
2017-08-31 12:52:06 +01:00
|
|
|
super().__init__(
|
|
|
|
|
app.import_name,
|
2021-10-26 16:36:25 +01:00
|
|
|
broker=app.config['CELERY']['broker_url'],
|
2021-04-13 14:53:46 +01:00
|
|
|
task_cls=make_task(app),
|
2017-08-31 12:52:06 +01:00
|
|
|
)
|
2016-02-09 13:31:45 +00:00
|
|
|
|
2021-10-26 16:36:25 +01:00
|
|
|
self.conf.update(app.config['CELERY'])
|
2020-04-24 11:21:41 +01:00
|
|
|
self._app = app
|
2021-04-27 10:35:21 +01:00
|
|
|
|
|
|
|
|
def send_task(self, name, args=None, kwargs=None, **other_kwargs):
|
2021-11-10 15:42:51 +00:00
|
|
|
other_kwargs['headers'] = other_kwargs.get('headers') or {}
|
2021-04-27 10:35:21 +01:00
|
|
|
|
|
|
|
|
if has_request_context() and hasattr(request, 'request_id'):
|
2021-11-10 15:42:51 +00:00
|
|
|
other_kwargs['headers']['notify_request_id'] = request.request_id
|
2021-04-27 10:35:21 +01:00
|
|
|
elif has_app_context() and 'request_id' in g:
|
2021-11-10 15:42:51 +00:00
|
|
|
other_kwargs['headers']['notify_request_id'] = g.request_id
|
2021-04-27 10:35:21 +01:00
|
|
|
|
|
|
|
|
return super().send_task(name, args, kwargs, **other_kwargs)
|