__init__.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.worker
  4. ~~~~~~~~~~~~~
  5. :class:`WorkController` can be used to instantiate in-process workers.
  6. The worker consists of several components, all managed by boot-steps
  7. (mod:`celery.worker.bootsteps`).
  8. """
  9. from __future__ import absolute_import
  10. import socket
  11. import sys
  12. import traceback
  13. from threading import Event
  14. from billiard import cpu_count
  15. from billiard.exceptions import WorkerLostError
  16. from kombu.syn import detect_environment
  17. from kombu.utils.finalize import Finalize
  18. from celery import concurrency as _concurrency
  19. from celery import platforms
  20. from celery import signals
  21. from celery.app import app_or_default, set_default_app
  22. from celery.app.abstract import configurated, from_config
  23. from celery.exceptions import (
  24. ImproperlyConfigured, SystemTerminate, TaskRevokedError,
  25. )
  26. from celery.task import trace
  27. from celery.utils import worker_direct
  28. from celery.utils.imports import qualname, reload_from_cwd
  29. from celery.utils.log import get_logger, mlevel
  30. from . import bootsteps
  31. from . import state
  32. try:
  33. from greenlet import GreenletExit
  34. IGNORE_ERRORS = (GreenletExit, )
  35. except ImportError: # pragma: no cover
  36. IGNORE_ERRORS = ()
  37. RUN = 0x1
  38. CLOSE = 0x2
  39. TERMINATE = 0x3
  40. UNKNOWN_QUEUE = """\
  41. Trying to select queue subset of {0!r}, but queue {1} is not
  42. defined in the CELERY_QUEUES setting.
  43. If you want to automatically declare unknown queues you can
  44. enable the CELERY_CREATE_MISSING_QUEUES setting.
  45. """
  46. logger = get_logger(__name__)
  47. class Namespace(bootsteps.Namespace):
  48. """This is the boot-step namespace of the :class:`WorkController`.
  49. It loads modules from :setting:`CELERYD_BOOT_STEPS`, and its
  50. own set of built-in boot-step modules.
  51. """
  52. name = 'worker'
  53. builtin_boot_steps = ('celery.worker.components',
  54. 'celery.worker.autoscale',
  55. 'celery.worker.autoreload',
  56. 'celery.worker.consumer',
  57. 'celery.worker.mediator')
  58. def modules(self):
  59. return self.builtin_boot_steps + self.app.conf.CELERYD_BOOT_STEPS
  60. class WorkController(configurated):
  61. """Unmanaged worker instance."""
  62. RUN = RUN
  63. CLOSE = CLOSE
  64. TERMINATE = TERMINATE
  65. app = None
  66. concurrency = from_config()
  67. loglevel = from_config('log_level')
  68. logfile = from_config('log_file')
  69. send_events = from_config()
  70. pool_cls = from_config('pool')
  71. consumer_cls = from_config('consumer')
  72. mediator_cls = from_config('mediator')
  73. timer_cls = from_config('timer')
  74. timer_precision = from_config('timer_precision')
  75. autoscaler_cls = from_config('autoscaler')
  76. autoreloader_cls = from_config('autoreloader')
  77. schedule_filename = from_config()
  78. scheduler_cls = from_config('celerybeat_scheduler')
  79. task_time_limit = from_config()
  80. task_soft_time_limit = from_config()
  81. max_tasks_per_child = from_config()
  82. pool_putlocks = from_config()
  83. pool_restarts = from_config()
  84. force_execv = from_config()
  85. prefetch_multiplier = from_config()
  86. state_db = from_config()
  87. disable_rate_limits = from_config()
  88. worker_lost_wait = from_config()
  89. _state = None
  90. _running = 0
  91. pidlock = None
  92. def __init__(self, app=None, hostname=None, **kwargs):
  93. self.app = app_or_default(app or self.app)
  94. # all new threads start without a current app, so if an app is not
  95. # passed on to the thread it will fall back to the "default app",
  96. # which then could be the wrong app. So for the worker
  97. # we set this to always return our app. This is a hack,
  98. # and means that only a single app can be used for workers
  99. # running in the same process.
  100. set_default_app(self.app)
  101. self.app.finalize()
  102. trace._tasks = self.app._tasks # optimization
  103. self.hostname = hostname or socket.gethostname()
  104. self.on_before_init(**kwargs)
  105. self._finalize = Finalize(self, self.stop, exitpriority=1)
  106. self._shutdown_complete = Event()
  107. self.setup_instance(**self.prepare_args(**kwargs))
  108. def on_before_init(self, **kwargs):
  109. pass
  110. def on_start(self):
  111. pass
  112. def on_consumer_ready(self, consumer):
  113. pass
  114. def setup_instance(self, queues=None, ready_callback=None,
  115. pidfile=None, include=None, **kwargs):
  116. self.pidfile = pidfile
  117. self.app.loader.init_worker()
  118. self.setup_defaults(kwargs, namespace='celeryd')
  119. self.setup_queues(queues)
  120. self.setup_includes(include)
  121. # Set default concurrency
  122. if not self.concurrency:
  123. try:
  124. self.concurrency = cpu_count()
  125. except NotImplementedError:
  126. self.concurrency = 2
  127. # Options
  128. self.loglevel = mlevel(self.loglevel)
  129. self.ready_callback = ready_callback or self.on_consumer_ready
  130. self.use_eventloop = self.should_use_eventloop()
  131. signals.worker_init.send(sender=self)
  132. # Initialize boot steps
  133. self.pool_cls = _concurrency.get_implementation(self.pool_cls)
  134. self.components = []
  135. self.namespace = Namespace(app=self.app).apply(self, **kwargs)
  136. def setup_queues(self, queues):
  137. if isinstance(queues, basestring):
  138. queues = queues.split(',')
  139. self.queues = queues
  140. try:
  141. self.app.select_queues(queues)
  142. except KeyError as exc:
  143. raise ImproperlyConfigured(
  144. UNKNOWN_QUEUE.format(queues, exc))
  145. if self.app.conf.CELERY_WORKER_DIRECT:
  146. self.app.amqp.queues.select_add(worker_direct(self.hostname))
  147. def setup_includes(self, includes):
  148. # Update celery_include to have all known task modules, so that we
  149. # ensure all task modules are imported in case an execv happens.
  150. inc = self.app.conf.CELERY_INCLUDE
  151. if includes:
  152. if isinstance(includes, basestring):
  153. includes = includes.split(',')
  154. inc = self.app.conf.CELERY_INCLUDE = tuple(inc) + tuple(includes)
  155. self.include = includes
  156. task_modules = set(task.__class__.__module__
  157. for task in self.app.tasks.itervalues())
  158. self.app.conf.CELERY_INCLUDE = tuple(set(inc) | task_modules)
  159. def prepare_args(self, **kwargs):
  160. return kwargs
  161. def start(self):
  162. """Starts the workers main loop."""
  163. self.on_start()
  164. self._state = self.RUN
  165. if self.pidfile:
  166. self.pidlock = platforms.create_pidlock(self.pidfile)
  167. try:
  168. for i, component in enumerate(self.components):
  169. logger.debug('Starting %s...', qualname(component))
  170. self._running = i + 1
  171. if component:
  172. component.start()
  173. logger.debug('%s OK!', qualname(component))
  174. except SystemTerminate:
  175. self.terminate()
  176. except Exception as exc:
  177. logger.error('Unrecoverable error: %r', exc,
  178. exc_info=True)
  179. self.stop()
  180. except (KeyboardInterrupt, SystemExit):
  181. self.stop()
  182. try:
  183. # Will only get here if running green,
  184. # makes sure all greenthreads have exited.
  185. self._shutdown_complete.wait()
  186. except IGNORE_ERRORS:
  187. pass
  188. run = start # XXX Compat
  189. def process_task_sem(self, req):
  190. return self._quick_acquire(self.process_task, req)
  191. def process_task(self, req):
  192. """Process task by sending it to the pool of workers."""
  193. try:
  194. req.execute_using_pool(self.pool)
  195. except TaskRevokedError:
  196. try:
  197. self._quick_release() # Issue 877
  198. except AttributeError:
  199. pass
  200. except Exception as exc:
  201. logger.critical('Internal error: %r\n%s',
  202. exc, traceback.format_exc(), exc_info=True)
  203. except SystemTerminate:
  204. self.terminate()
  205. raise
  206. except BaseException as exc:
  207. self.stop()
  208. raise exc
  209. def signal_consumer_close(self):
  210. try:
  211. self.consumer.close()
  212. except AttributeError:
  213. pass
  214. def should_use_eventloop(self):
  215. return (detect_environment() == 'default' and
  216. self.app.connection().is_evented and not self.app.IS_WINDOWS)
  217. def stop(self, in_sighandler=False):
  218. """Graceful shutdown of the worker server."""
  219. self.signal_consumer_close()
  220. if not in_sighandler or self.pool.signal_safe:
  221. self._shutdown(warm=True)
  222. def terminate(self, in_sighandler=False):
  223. """Not so graceful shutdown of the worker server."""
  224. self.signal_consumer_close()
  225. if not in_sighandler or self.pool.signal_safe:
  226. self._shutdown(warm=False)
  227. def _shutdown(self, warm=True):
  228. what = 'Stopping' if warm else 'Terminating'
  229. if self._state in (self.CLOSE, self.TERMINATE):
  230. return
  231. self.app.loader.shutdown_worker()
  232. if self.pool:
  233. self.pool.close()
  234. if self._state != self.RUN or self._running != len(self.components):
  235. # Not fully started, can safely exit.
  236. self._state = self.TERMINATE
  237. self._shutdown_complete.set()
  238. return
  239. self._state = self.CLOSE
  240. for component in reversed(self.components):
  241. logger.debug('%s %s...', what, qualname(component))
  242. if component:
  243. stop = component.stop
  244. if not warm:
  245. stop = getattr(component, 'terminate', None) or stop
  246. stop()
  247. self.timer.stop()
  248. self.consumer.close_connection()
  249. if self.pidlock:
  250. self.pidlock.release()
  251. self._state = self.TERMINATE
  252. self._shutdown_complete.set()
  253. def reload(self, modules=None, reload=False, reloader=None):
  254. modules = self.app.loader.task_modules if modules is None else modules
  255. imp = self.app.loader.import_from_cwd
  256. for module in set(modules or ()):
  257. if module not in sys.modules:
  258. logger.debug('importing module %s', module)
  259. imp(module)
  260. elif reload:
  261. logger.debug('reloading module %s', module)
  262. reload_from_cwd(sys.modules[module], reloader)
  263. self.pool.restart()
  264. @property
  265. def state(self):
  266. return state