25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

223 lines
5.7 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals, absolute_import
  4. from celery import Celery
  5. # initiate logger
  6. from celery.utils.log import get_task_logger
  7. task_logger = get_task_logger(__name__)
  8. from datetime import timedelta
  9. import frappe
  10. import os
  11. import threading
  12. import time
  13. SITES_PATH = os.environ.get('SITES_PATH', '.')
  14. # defaults
  15. DEFAULT_CELERY_BROKER = "redis://localhost"
  16. DEFAULT_CELERY_BACKEND = "redis://localhost"
  17. DEFAULT_SCHEDULER_INTERVAL = 300
  18. LONGJOBS_PREFIX = "longjobs@"
  19. ASYNC_TASKS_PREFIX = "async@"
  20. _app = None
  21. def get_celery():
  22. global _app
  23. if not _app:
  24. _app = get_celery_app()
  25. return _app
  26. def get_celery_app():
  27. conf = get_site_config()
  28. app = Celery('frappe',
  29. broker=conf.celery_broker or DEFAULT_CELERY_BROKER,
  30. backend=conf.async_redis_server or DEFAULT_CELERY_BACKEND)
  31. app.autodiscover_tasks(frappe.get_all_apps(with_frappe=True, with_internal_apps=False,
  32. sites_path=SITES_PATH))
  33. app.conf.CELERY_TASK_SERIALIZER = 'json'
  34. app.conf.CELERY_ACCEPT_CONTENT = ['json']
  35. app.conf.CELERY_TIMEZONE = 'UTC'
  36. app.conf.CELERY_RESULT_SERIALIZER = 'json'
  37. app.conf.CELERY_TASK_RESULT_EXPIRES = timedelta(0, 3600)
  38. if conf.monitory_celery:
  39. app.conf.CELERY_SEND_EVENTS = True
  40. app.conf.CELERY_SEND_TASK_SENT_EVENT = True
  41. app.conf.CELERY_ROUTES = (SiteRouter(), AsyncTaskRouter())
  42. app.conf.CELERYBEAT_SCHEDULE = get_beat_schedule(conf)
  43. if conf.celery_error_emails:
  44. app.conf.CELERY_SEND_TASK_ERROR_EMAILS = True
  45. for k, v in conf.celery_error_emails.iteritems():
  46. setattr(app.conf, k, v)
  47. return app
  48. def get_site_config():
  49. return frappe.get_site_config(sites_path=SITES_PATH)
  50. class SiteRouter(object):
  51. def route_for_task(self, task, args=None, kwargs=None):
  52. if hasattr(frappe.local, 'site'):
  53. if kwargs and kwargs.get("event", "").endswith("_long"):
  54. return get_queue(frappe.local.site, LONGJOBS_PREFIX)
  55. else:
  56. return get_queue(frappe.local.site)
  57. return None
  58. class AsyncTaskRouter(object):
  59. def route_for_task(self, task, args=None, kwargs=None):
  60. if task == "frappe.tasks.run_async_task" and hasattr(frappe.local, 'site'):
  61. return get_queue(frappe.local.site, ASYNC_TASKS_PREFIX)
  62. def get_queue(site, prefix=None):
  63. return {'queue': "{}{}".format(prefix or "", site)}
  64. def get_beat_schedule(conf):
  65. schedule = {
  66. 'scheduler': {
  67. 'task': 'frappe.tasks.enqueue_scheduler_events',
  68. 'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL)
  69. },
  70. }
  71. schedule['sync_queues'] = {
  72. 'task': 'frappe.tasks.sync_queues',
  73. 'schedule': timedelta(seconds=conf.scheduler_interval or DEFAULT_SCHEDULER_INTERVAL)
  74. }
  75. return schedule
  76. def celery_task(*args, **kwargs):
  77. return get_celery().task(*args, **kwargs)
  78. def make_async_task(args):
  79. task = frappe.new_doc("Async Task")
  80. task.update(args)
  81. task.status = "Queued"
  82. task.set_docstatus_user_and_timestamp()
  83. task.db_insert()
  84. task.notify_update()
  85. def run_test():
  86. for i in xrange(30):
  87. test.delay(site=frappe.local.site)
  88. @celery_task()
  89. def test(site=None):
  90. time.sleep(1)
  91. print "task"
  92. class MonitorThread(object):
  93. """Thread manager for monitoring celery events"""
  94. def __init__(self, celery_app, interval=1):
  95. self.celery_app = celery_app
  96. self.interval = interval
  97. self.state = self.celery_app.events.State()
  98. self.thread = threading.Thread(target=self.run, args=())
  99. self.thread.daemon = True
  100. self.thread.start()
  101. def catchall(self, event):
  102. if event['type'] != 'worker-heartbeat':
  103. self.state.event(event)
  104. if not 'uuid' in event:
  105. return
  106. task = self.state.tasks.get(event['uuid'])
  107. info = task.info()
  108. if 'name' in event and 'enqueue_events_for_site' in event['name']:
  109. return
  110. try:
  111. kwargs = eval(info.get('kwargs'))
  112. if 'site' in kwargs:
  113. frappe.connect(kwargs['site'])
  114. if event['type']=='task-sent':
  115. make_async_task({
  116. 'name': event['uuid'],
  117. 'task_name': kwargs.get("cmd") or event['name']
  118. })
  119. elif event['type']=='task-received':
  120. try:
  121. task = frappe.get_doc("Async Task", event['uuid'])
  122. task.status = 'Started'
  123. task.set_docstatus_user_and_timestamp()
  124. task.db_update()
  125. task.notify_update()
  126. except frappe.DoesNotExistError:
  127. pass
  128. elif event['type']=='task-succeeded':
  129. try:
  130. task = frappe.get_doc("Async Task", event['uuid'])
  131. task.status = 'Succeeded'
  132. task.result = info.get('result')
  133. task.runtime = info.get('runtime')
  134. task.set_docstatus_user_and_timestamp()
  135. task.db_update()
  136. task.notify_update()
  137. except frappe.DoesNotExistError:
  138. pass
  139. elif event['type']=='task-failed':
  140. try:
  141. task = frappe.get_doc("Async Task", event['uuid'])
  142. task.status = 'Failed'
  143. task.traceback = event.get('traceback') or event.get('exception')
  144. task.traceback = frappe.as_json(info) + "\n\n" + task.traceback
  145. task.runtime = info.get('runtime')
  146. task.set_docstatus_user_and_timestamp()
  147. task.db_update()
  148. task.notify_update()
  149. except frappe.DoesNotExistError:
  150. pass
  151. frappe.db.commit()
  152. except Exception:
  153. print frappe.get_traceback()
  154. finally:
  155. frappe.destroy()
  156. def run(self):
  157. while True:
  158. try:
  159. with self.celery_app.connection() as connection:
  160. recv = self.celery_app.events.Receiver(connection, handlers={
  161. '*': self.catchall
  162. })
  163. recv.capture(limit=None, timeout=None, wakeup=True)
  164. except (KeyboardInterrupt, SystemExit):
  165. raise
  166. except Exception:
  167. # unable to capture
  168. print "unable to capture:"
  169. print frappe.get_traceback()
  170. time.sleep(self.interval)
  171. if __name__ == '__main__':
  172. app = get_celery()
  173. if get_site_config().get("monitor_celery"):
  174. MonitorThread(app)
  175. app.start()