builtins.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.app.builtins
  4. ~~~~~~~~~~~~~~~~~~~
  5. Built-in tasks that are always available in all
  6. app instances. E.g. chord, group and xmap.
  7. """
  8. from __future__ import absolute_import, unicode_literals
  9. from celery._state import connect_on_app_finalize
  10. from celery.utils.log import get_logger
  11. __all__ = []
  12. logger = get_logger(__name__)
  13. @connect_on_app_finalize
  14. def add_backend_cleanup_task(app):
  15. """The backend cleanup task can be used to clean up the default result
  16. backend.
  17. If the configured backend requires periodic cleanup this task is also
  18. automatically configured to run every day at 4am (requires
  19. :program:`celery beat` to be running).
  20. """
  21. @app.task(name='celery.backend_cleanup', shared=False, lazy=False)
  22. def backend_cleanup():
  23. app.backend.cleanup()
  24. return backend_cleanup
  25. @connect_on_app_finalize
  26. def add_accumulate_task(app):
  27. """This task is used by Task.replace when replacing a task with
  28. a group, to "collect" results."""
  29. @app.task(bind=True, name='celery.accumulate', shared=False, lazy=False)
  30. def accumulate(self, *args, **kwargs):
  31. index = kwargs.get('index')
  32. return args[index] if index is not None else args
  33. @connect_on_app_finalize
  34. def add_unlock_chord_task(app):
  35. """This task is used by result backends without native chord support.
  36. It joins chords by creating a task chain polling the header for completion.
  37. """
  38. from celery.canvas import maybe_signature
  39. from celery.exceptions import ChordError
  40. from celery.result import allow_join_result, result_from_tuple
  41. @app.task(name='celery.chord_unlock', max_retries=None, shared=False,
  42. default_retry_delay=1, ignore_result=True, lazy=False, bind=True)
  43. def unlock_chord(self, group_id, callback, interval=None,
  44. max_retries=None, result=None,
  45. Result=app.AsyncResult, GroupResult=app.GroupResult,
  46. result_from_tuple=result_from_tuple, **kwargs):
  47. if interval is None:
  48. interval = self.default_retry_delay
  49. # check if the task group is ready, and if so apply the callback.
  50. callback = maybe_signature(callback, app)
  51. deps = GroupResult(
  52. group_id,
  53. [result_from_tuple(r, app=app) for r in result],
  54. app=app,
  55. )
  56. j = deps.join_native if deps.supports_native_join else deps.join
  57. try:
  58. ready = deps.ready()
  59. except Exception as exc:
  60. raise self.retry(
  61. exc=exc, countdown=interval, max_retries=max_retries,
  62. )
  63. else:
  64. if not ready:
  65. raise self.retry(countdown=interval, max_retries=max_retries)
  66. callback = maybe_signature(callback, app=app)
  67. try:
  68. with allow_join_result():
  69. ret = j(timeout=3.0, propagate=True)
  70. except Exception as exc:
  71. try:
  72. culprit = next(deps._failed_join_report())
  73. reason = 'Dependency {0.id} raised {1!r}'.format(
  74. culprit, exc,
  75. )
  76. except StopIteration:
  77. reason = repr(exc)
  78. logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
  79. app.backend.chord_error_from_stack(callback,
  80. ChordError(reason))
  81. else:
  82. try:
  83. callback.delay(ret)
  84. except Exception as exc:
  85. logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
  86. app.backend.chord_error_from_stack(
  87. callback,
  88. exc=ChordError('Callback error: {0!r}'.format(exc)),
  89. )
  90. return unlock_chord
  91. @connect_on_app_finalize
  92. def add_map_task(app):
  93. from celery.canvas import signature
  94. @app.task(name='celery.map', shared=False, lazy=False)
  95. def xmap(task, it):
  96. task = signature(task, app=app).type
  97. return [task(item) for item in it]
  98. return xmap
  99. @connect_on_app_finalize
  100. def add_starmap_task(app):
  101. from celery.canvas import signature
  102. @app.task(name='celery.starmap', shared=False, lazy=False)
  103. def xstarmap(task, it):
  104. task = signature(task, app=app).type
  105. return [task(*item) for item in it]
  106. return xstarmap
  107. @connect_on_app_finalize
  108. def add_chunk_task(app):
  109. from celery.canvas import chunks as _chunks
  110. @app.task(name='celery.chunks', shared=False, lazy=False)
  111. def chunks(task, it, n):
  112. return _chunks.apply_chunks(task, it, n)
  113. return chunks
  114. @connect_on_app_finalize
  115. def add_group_task(app):
  116. """No longer used, but here for backwards compatibility."""
  117. from celery.canvas import maybe_signature
  118. from celery.result import result_from_tuple
  119. @app.task(name='celery.group', bind=True, shared=False, lazy=False)
  120. def group(self, tasks, result, group_id, partial_args, add_to_parent=True):
  121. app = self.app
  122. result = result_from_tuple(result, app)
  123. # any partial args are added to all tasks in the group
  124. taskit = (maybe_signature(task, app=app).clone(partial_args)
  125. for i, task in enumerate(tasks))
  126. with app.producer_or_acquire() as producer:
  127. [stask.apply_async(group_id=group_id, producer=producer,
  128. add_to_parent=False) for stask in taskit]
  129. parent = app.current_worker_task
  130. if add_to_parent and parent:
  131. parent.add_trail(result)
  132. return result
  133. return group
  134. @connect_on_app_finalize
  135. def add_chain_task(app):
  136. """No longer used, but here for backwards compatibility."""
  137. @app.task(name='celery.chain', shared=False, lazy=False)
  138. def chain(*args, **kwargs):
  139. raise NotImplementedError('chain is not a real task')
  140. return chain
  141. @connect_on_app_finalize
  142. def add_chord_task(app):
  143. """No longer used, but here for backwards compatibility."""
  144. from celery import group, chord as _chord
  145. from celery.canvas import maybe_signature
  146. @app.task(name='celery.chord', bind=True, ignore_result=False,
  147. shared=False, lazy=False)
  148. def chord(self, header, body, partial_args=(), interval=None,
  149. countdown=1, max_retries=None, eager=False, **kwargs):
  150. app = self.app
  151. # - convert back to group if serialized
  152. tasks = header.tasks if isinstance(header, group) else header
  153. header = group([
  154. maybe_signature(s, app=app) for s in tasks
  155. ], app=self.app)
  156. body = maybe_signature(body, app=app)
  157. ch = _chord(header, body)
  158. return ch.run(header, body, partial_args, app, interval,
  159. countdown, max_retries, **kwargs)
  160. return chord