Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

trace.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.app.trace
  4. ~~~~~~~~~~~~~~~~
  5. This module defines how the task execution is traced:
  6. errors are recorded, handlers are applied and so on.
  7. """
  8. from __future__ import absolute_import
  9. # ## ---
  10. # This is the heart of the worker, the inner loop so to speak.
  11. # It used to be split up into nice little classes and methods,
  12. # but in the end it only resulted in bad performance and horrible tracebacks,
  13. # so instead we now use one closure per task class.
  14. import os
  15. import socket
  16. import sys
  17. from warnings import warn
  18. from billiard.einfo import ExceptionInfo
  19. from kombu.exceptions import EncodeError
  20. from kombu.utils import kwdict
  21. from celery import current_app, group
  22. from celery import states, signals
  23. from celery._state import _task_stack
  24. from celery.app import set_default_app
  25. from celery.app.task import Task as BaseTask, Context
  26. from celery.exceptions import Ignore, Reject, Retry
  27. from celery.utils.log import get_logger
  28. from celery.utils.objects import mro_lookup
  29. from celery.utils.serialization import (
  30. get_pickleable_exception,
  31. get_pickleable_etype,
  32. )
  33. __all__ = ['TraceInfo', 'build_tracer', 'trace_task', 'eager_trace_task',
  34. 'setup_worker_optimizations', 'reset_worker_optimizations']
  35. _logger = get_logger(__name__)
  36. send_prerun = signals.task_prerun.send
  37. send_postrun = signals.task_postrun.send
  38. send_success = signals.task_success.send
  39. STARTED = states.STARTED
  40. SUCCESS = states.SUCCESS
  41. IGNORED = states.IGNORED
  42. REJECTED = states.REJECTED
  43. RETRY = states.RETRY
  44. FAILURE = states.FAILURE
  45. EXCEPTION_STATES = states.EXCEPTION_STATES
  46. IGNORE_STATES = frozenset([IGNORED, RETRY, REJECTED])
  47. #: set by :func:`setup_worker_optimizations`
  48. _tasks = None
  49. _patched = {}
  50. def task_has_custom(task, attr):
  51. """Return true if the task or one of its bases
  52. defines ``attr`` (excluding the one in BaseTask)."""
  53. return mro_lookup(task.__class__, attr, stop=(BaseTask, object),
  54. monkey_patched=['celery.app.task'])
  55. class TraceInfo(object):
  56. __slots__ = ('state', 'retval')
  57. def __init__(self, state, retval=None):
  58. self.state = state
  59. self.retval = retval
  60. def handle_error_state(self, task, eager=False):
  61. store_errors = not eager
  62. if task.ignore_result:
  63. store_errors = task.store_errors_even_if_ignored
  64. return {
  65. RETRY: self.handle_retry,
  66. FAILURE: self.handle_failure,
  67. }[self.state](task, store_errors=store_errors)
  68. def handle_retry(self, task, store_errors=True):
  69. """Handle retry exception."""
  70. # the exception raised is the Retry semi-predicate,
  71. # and it's exc' attribute is the original exception raised (if any).
  72. req = task.request
  73. type_, _, tb = sys.exc_info()
  74. try:
  75. reason = self.retval
  76. einfo = ExceptionInfo((type_, reason, tb))
  77. if store_errors:
  78. task.backend.mark_as_retry(
  79. req.id, reason.exc, einfo.traceback, request=req,
  80. )
  81. task.on_retry(reason.exc, req.id, req.args, req.kwargs, einfo)
  82. signals.task_retry.send(sender=task, request=req,
  83. reason=reason, einfo=einfo)
  84. return einfo
  85. finally:
  86. del(tb)
  87. def handle_failure(self, task, store_errors=True):
  88. """Handle exception."""
  89. req = task.request
  90. type_, _, tb = sys.exc_info()
  91. try:
  92. exc = self.retval
  93. einfo = ExceptionInfo()
  94. einfo.exception = get_pickleable_exception(einfo.exception)
  95. einfo.type = get_pickleable_etype(einfo.type)
  96. if store_errors:
  97. task.backend.mark_as_failure(
  98. req.id, exc, einfo.traceback, request=req,
  99. )
  100. task.on_failure(exc, req.id, req.args, req.kwargs, einfo)
  101. signals.task_failure.send(sender=task, task_id=req.id,
  102. exception=exc, args=req.args,
  103. kwargs=req.kwargs,
  104. traceback=tb,
  105. einfo=einfo)
  106. return einfo
  107. finally:
  108. del(tb)
  109. def build_tracer(name, task, loader=None, hostname=None, store_errors=True,
  110. Info=TraceInfo, eager=False, propagate=False, app=None,
  111. IGNORE_STATES=IGNORE_STATES):
  112. """Return a function that traces task execution; catches all
  113. exceptions and updates result backend with the state and result
  114. If the call was successful, it saves the result to the task result
  115. backend, and sets the task status to `"SUCCESS"`.
  116. If the call raises :exc:`~@Retry`, it extracts
  117. the original exception, uses that as the result and sets the task state
  118. to `"RETRY"`.
  119. If the call results in an exception, it saves the exception as the task
  120. result, and sets the task state to `"FAILURE"`.
  121. Return a function that takes the following arguments:
  122. :param uuid: The id of the task.
  123. :param args: List of positional args to pass on to the function.
  124. :param kwargs: Keyword arguments mapping to pass on to the function.
  125. :keyword request: Request dict.
  126. """
  127. # If the task doesn't define a custom __call__ method
  128. # we optimize it away by simply calling the run method directly,
  129. # saving the extra method call and a line less in the stack trace.
  130. fun = task if task_has_custom(task, '__call__') else task.run
  131. loader = loader or app.loader
  132. backend = task.backend
  133. ignore_result = task.ignore_result
  134. track_started = task.track_started
  135. track_started = not eager and (task.track_started and not ignore_result)
  136. publish_result = not eager and not ignore_result
  137. hostname = hostname or socket.gethostname()
  138. loader_task_init = loader.on_task_init
  139. loader_cleanup = loader.on_process_cleanup
  140. task_on_success = None
  141. task_after_return = None
  142. if task_has_custom(task, 'on_success'):
  143. task_on_success = task.on_success
  144. if task_has_custom(task, 'after_return'):
  145. task_after_return = task.after_return
  146. store_result = backend.store_result
  147. backend_cleanup = backend.process_cleanup
  148. pid = os.getpid()
  149. request_stack = task.request_stack
  150. push_request = request_stack.push
  151. pop_request = request_stack.pop
  152. push_task = _task_stack.push
  153. pop_task = _task_stack.pop
  154. on_chord_part_return = backend.on_chord_part_return
  155. prerun_receivers = signals.task_prerun.receivers
  156. postrun_receivers = signals.task_postrun.receivers
  157. success_receivers = signals.task_success.receivers
  158. from celery import canvas
  159. signature = canvas.maybe_signature # maybe_ does not clone if already
  160. def on_error(request, exc, uuid, state=FAILURE, call_errbacks=True):
  161. if propagate:
  162. raise
  163. I = Info(state, exc)
  164. R = I.handle_error_state(task, eager=eager)
  165. if call_errbacks:
  166. group(
  167. [signature(errback, app=app)
  168. for errback in request.errbacks or []], app=app,
  169. ).apply_async((uuid, ))
  170. return I, R, I.state, I.retval
  171. def trace_task(uuid, args, kwargs, request=None):
  172. # R - is the possibly prepared return value.
  173. # I - is the Info object.
  174. # retval - is the always unmodified return value.
  175. # state - is the resulting task state.
  176. # This function is very long because we have unrolled all the calls
  177. # for performance reasons, and because the function is so long
  178. # we want the main variables (I, and R) to stand out visually from the
  179. # the rest of the variables, so breaking PEP8 is worth it ;)
  180. R = I = retval = state = None
  181. kwargs = kwdict(kwargs)
  182. try:
  183. push_task(task)
  184. task_request = Context(request or {}, args=args,
  185. called_directly=False, kwargs=kwargs)
  186. push_request(task_request)
  187. try:
  188. # -*- PRE -*-
  189. if prerun_receivers:
  190. send_prerun(sender=task, task_id=uuid, task=task,
  191. args=args, kwargs=kwargs)
  192. loader_task_init(uuid, task)
  193. if track_started:
  194. store_result(
  195. uuid, {'pid': pid, 'hostname': hostname}, STARTED,
  196. request=task_request,
  197. )
  198. # -*- TRACE -*-
  199. try:
  200. R = retval = fun(*args, **kwargs)
  201. state = SUCCESS
  202. except Reject as exc:
  203. I, R = Info(REJECTED, exc), ExceptionInfo(internal=True)
  204. state, retval = I.state, I.retval
  205. except Ignore as exc:
  206. I, R = Info(IGNORED, exc), ExceptionInfo(internal=True)
  207. state, retval = I.state, I.retval
  208. except Retry as exc:
  209. I, R, state, retval = on_error(
  210. task_request, exc, uuid, RETRY, call_errbacks=False,
  211. )
  212. except Exception as exc:
  213. I, R, state, retval = on_error(task_request, exc, uuid)
  214. except BaseException as exc:
  215. raise
  216. else:
  217. try:
  218. # callback tasks must be applied before the result is
  219. # stored, so that result.children is populated.
  220. # groups are called inline and will store trail
  221. # separately, so need to call them separately
  222. # so that the trail's not added multiple times :(
  223. # (Issue #1936)
  224. callbacks = task.request.callbacks
  225. if callbacks:
  226. if len(task.request.callbacks) > 1:
  227. sigs, groups = [], []
  228. for sig in callbacks:
  229. sig = signature(sig, app=app)
  230. if isinstance(sig, group):
  231. groups.append(sig)
  232. else:
  233. sigs.append(sig)
  234. for group_ in groups:
  235. group_.apply_async((retval, ))
  236. if sigs:
  237. group(sigs).apply_async((retval, ))
  238. else:
  239. signature(callbacks[0], app=app).delay(retval)
  240. if publish_result:
  241. store_result(
  242. uuid, retval, SUCCESS, request=task_request,
  243. )
  244. except EncodeError as exc:
  245. I, R, state, retval = on_error(task_request, exc, uuid)
  246. else:
  247. if task_on_success:
  248. task_on_success(retval, uuid, args, kwargs)
  249. if success_receivers:
  250. send_success(sender=task, result=retval)
  251. # -* POST *-
  252. if state not in IGNORE_STATES:
  253. if task_request.chord:
  254. on_chord_part_return(task, state, R)
  255. if task_after_return:
  256. task_after_return(
  257. state, retval, uuid, args, kwargs, None,
  258. )
  259. finally:
  260. try:
  261. if postrun_receivers:
  262. send_postrun(sender=task, task_id=uuid, task=task,
  263. args=args, kwargs=kwargs,
  264. retval=retval, state=state)
  265. finally:
  266. pop_task()
  267. pop_request()
  268. if not eager:
  269. try:
  270. backend_cleanup()
  271. loader_cleanup()
  272. except (KeyboardInterrupt, SystemExit, MemoryError):
  273. raise
  274. except Exception as exc:
  275. _logger.error('Process cleanup failed: %r', exc,
  276. exc_info=True)
  277. except MemoryError:
  278. raise
  279. except Exception as exc:
  280. if eager:
  281. raise
  282. R = report_internal_error(task, exc)
  283. return R, I
  284. return trace_task
  285. def trace_task(task, uuid, args, kwargs, request={}, **opts):
  286. try:
  287. if task.__trace__ is None:
  288. task.__trace__ = build_tracer(task.name, task, **opts)
  289. return task.__trace__(uuid, args, kwargs, request)[0]
  290. except Exception as exc:
  291. return report_internal_error(task, exc)
  292. def _trace_task_ret(name, uuid, args, kwargs, request={}, app=None, **opts):
  293. app = app or current_app
  294. return trace_task(app.tasks[name],
  295. uuid, args, kwargs, request, app=app, **opts)
  296. trace_task_ret = _trace_task_ret
  297. def _fast_trace_task(task, uuid, args, kwargs, request={}):
  298. # setup_worker_optimizations will point trace_task_ret to here,
  299. # so this is the function used in the worker.
  300. return _tasks[task].__trace__(uuid, args, kwargs, request)[0]
  301. def eager_trace_task(task, uuid, args, kwargs, request=None, **opts):
  302. opts.setdefault('eager', True)
  303. return build_tracer(task.name, task, **opts)(
  304. uuid, args, kwargs, request)
  305. def report_internal_error(task, exc):
  306. _type, _value, _tb = sys.exc_info()
  307. try:
  308. _value = task.backend.prepare_exception(exc, 'pickle')
  309. exc_info = ExceptionInfo((_type, _value, _tb), internal=True)
  310. warn(RuntimeWarning(
  311. 'Exception raised outside body: {0!r}:\n{1}'.format(
  312. exc, exc_info.traceback)))
  313. return exc_info
  314. finally:
  315. del(_tb)
  316. def setup_worker_optimizations(app):
  317. global _tasks
  318. global trace_task_ret
  319. # make sure custom Task.__call__ methods that calls super
  320. # will not mess up the request/task stack.
  321. _install_stack_protection()
  322. # all new threads start without a current app, so if an app is not
  323. # passed on to the thread it will fall back to the "default app",
  324. # which then could be the wrong app. So for the worker
  325. # we set this to always return our app. This is a hack,
  326. # and means that only a single app can be used for workers
  327. # running in the same process.
  328. app.set_current()
  329. set_default_app(app)
  330. # evaluate all task classes by finalizing the app.
  331. app.finalize()
  332. # set fast shortcut to task registry
  333. _tasks = app._tasks
  334. trace_task_ret = _fast_trace_task
  335. from celery.worker import job as job_module
  336. job_module.trace_task_ret = _fast_trace_task
  337. job_module.__optimize__()
  338. def reset_worker_optimizations():
  339. global trace_task_ret
  340. trace_task_ret = _trace_task_ret
  341. try:
  342. delattr(BaseTask, '_stackprotected')
  343. except AttributeError:
  344. pass
  345. try:
  346. BaseTask.__call__ = _patched.pop('BaseTask.__call__')
  347. except KeyError:
  348. pass
  349. from celery.worker import job as job_module
  350. job_module.trace_task_ret = _trace_task_ret
  351. def _install_stack_protection():
  352. # Patches BaseTask.__call__ in the worker to handle the edge case
  353. # where people override it and also call super.
  354. #
  355. # - The worker optimizes away BaseTask.__call__ and instead
  356. # calls task.run directly.
  357. # - so with the addition of current_task and the request stack
  358. # BaseTask.__call__ now pushes to those stacks so that
  359. # they work when tasks are called directly.
  360. #
  361. # The worker only optimizes away __call__ in the case
  362. # where it has not been overridden, so the request/task stack
  363. # will blow if a custom task class defines __call__ and also
  364. # calls super().
  365. if not getattr(BaseTask, '_stackprotected', False):
  366. _patched['BaseTask.__call__'] = orig = BaseTask.__call__
  367. def __protected_call__(self, *args, **kwargs):
  368. stack = self.request_stack
  369. req = stack.top
  370. if req and not req._protected and \
  371. len(stack) == 1 and not req.called_directly:
  372. req._protected = 1
  373. return self.run(*args, **kwargs)
  374. return orig(self, *args, **kwargs)
  375. BaseTask.__call__ = __protected_call__
  376. BaseTask._stackprotected = True