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.

autoreload.py 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.worker.autoreload
  4. ~~~~~~~~~~~~~~~~~~~~~~~~
  5. This module implements automatic module reloading
  6. """
  7. from __future__ import absolute_import
  8. import hashlib
  9. import os
  10. import select
  11. import sys
  12. import time
  13. from collections import defaultdict
  14. from threading import Event
  15. from kombu.utils import eventio
  16. from kombu.utils.encoding import ensure_bytes
  17. from celery import bootsteps
  18. from celery.five import items
  19. from celery.platforms import ignore_errno
  20. from celery.utils.imports import module_file
  21. from celery.utils.log import get_logger
  22. from celery.utils.threads import bgThread
  23. from .components import Pool
  24. try: # pragma: no cover
  25. import pyinotify
  26. _ProcessEvent = pyinotify.ProcessEvent
  27. except ImportError: # pragma: no cover
  28. pyinotify = None # noqa
  29. _ProcessEvent = object # noqa
  30. __all__ = [
  31. 'WorkerComponent', 'Autoreloader', 'Monitor', 'BaseMonitor',
  32. 'StatMonitor', 'KQueueMonitor', 'InotifyMonitor', 'file_hash',
  33. ]
  34. logger = get_logger(__name__)
  35. class WorkerComponent(bootsteps.StartStopStep):
  36. label = 'Autoreloader'
  37. conditional = True
  38. requires = (Pool, )
  39. def __init__(self, w, autoreload=None, **kwargs):
  40. self.enabled = w.autoreload = autoreload
  41. w.autoreloader = None
  42. def create(self, w):
  43. w.autoreloader = self.instantiate(w.autoreloader_cls, w)
  44. return w.autoreloader if not w.use_eventloop else None
  45. def register_with_event_loop(self, w, hub):
  46. w.autoreloader.register_with_event_loop(hub)
  47. hub.on_close.add(w.autoreloader.on_event_loop_close)
  48. def file_hash(filename, algorithm='md5'):
  49. hobj = hashlib.new(algorithm)
  50. with open(filename, 'rb') as f:
  51. for chunk in iter(lambda: f.read(2 ** 20), ''):
  52. hobj.update(ensure_bytes(chunk))
  53. return hobj.digest()
  54. class BaseMonitor(object):
  55. def __init__(self, files,
  56. on_change=None, shutdown_event=None, interval=0.5):
  57. self.files = files
  58. self.interval = interval
  59. self._on_change = on_change
  60. self.modify_times = defaultdict(int)
  61. self.shutdown_event = shutdown_event or Event()
  62. def start(self):
  63. raise NotImplementedError('Subclass responsibility')
  64. def stop(self):
  65. pass
  66. def on_change(self, modified):
  67. if self._on_change:
  68. return self._on_change(modified)
  69. def on_event_loop_close(self, hub):
  70. pass
  71. class StatMonitor(BaseMonitor):
  72. """File change monitor based on the ``stat`` system call."""
  73. def _mtimes(self):
  74. return ((f, self._mtime(f)) for f in self.files)
  75. def _maybe_modified(self, f, mt):
  76. return mt is not None and self.modify_times[f] != mt
  77. def register_with_event_loop(self, hub):
  78. hub.call_repeatedly(2.0, self.find_changes)
  79. def find_changes(self):
  80. maybe_modified = self._maybe_modified
  81. modified = dict((f, mt) for f, mt in self._mtimes()
  82. if maybe_modified(f, mt))
  83. if modified:
  84. self.on_change(modified)
  85. self.modify_times.update(modified)
  86. def start(self):
  87. while not self.shutdown_event.is_set():
  88. self.find_changes()
  89. time.sleep(self.interval)
  90. @staticmethod
  91. def _mtime(path):
  92. try:
  93. return os.stat(path).st_mtime
  94. except Exception:
  95. pass
  96. class KQueueMonitor(BaseMonitor):
  97. """File change monitor based on BSD kernel event notifications"""
  98. def __init__(self, *args, **kwargs):
  99. super(KQueueMonitor, self).__init__(*args, **kwargs)
  100. self.filemap = dict((f, None) for f in self.files)
  101. self.fdmap = {}
  102. def register_with_event_loop(self, hub):
  103. if eventio.kqueue is not None:
  104. self._kq = eventio._kqueue()
  105. self.add_events(self._kq)
  106. self._kq.on_file_change = self.handle_event
  107. hub.add_reader(self._kq._kqueue, self._kq.poll, 0)
  108. def on_event_loop_close(self, hub):
  109. self.close(self._kq)
  110. def add_events(self, poller):
  111. for f in self.filemap:
  112. self.filemap[f] = fd = os.open(f, os.O_RDONLY)
  113. self.fdmap[fd] = f
  114. poller.watch_file(fd)
  115. def handle_event(self, events):
  116. self.on_change([self.fdmap[e.ident] for e in events])
  117. def start(self):
  118. self.poller = eventio.poll()
  119. self.add_events(self.poller)
  120. self.poller.on_file_change = self.handle_event
  121. while not self.shutdown_event.is_set():
  122. self.poller.poll(1)
  123. def close(self, poller):
  124. for f, fd in items(self.filemap):
  125. if fd is not None:
  126. poller.unregister(fd)
  127. with ignore_errno('EBADF'): # pragma: no cover
  128. os.close(fd)
  129. self.filemap.clear()
  130. self.fdmap.clear()
  131. def stop(self):
  132. self.close(self.poller)
  133. self.poller.close()
  134. class InotifyMonitor(_ProcessEvent):
  135. """File change monitor based on Linux kernel `inotify` subsystem"""
  136. def __init__(self, modules, on_change=None, **kwargs):
  137. assert pyinotify
  138. self._modules = modules
  139. self._on_change = on_change
  140. self._wm = None
  141. self._notifier = None
  142. def register_with_event_loop(self, hub):
  143. self.create_notifier()
  144. hub.add_reader(self._wm.get_fd(), self.on_readable)
  145. def on_event_loop_close(self, hub):
  146. pass
  147. def on_readable(self):
  148. self._notifier.read_events()
  149. self._notifier.process_events()
  150. def create_notifier(self):
  151. self._wm = pyinotify.WatchManager()
  152. self._notifier = pyinotify.Notifier(self._wm, self)
  153. add_watch = self._wm.add_watch
  154. flags = pyinotify.IN_MODIFY | pyinotify.IN_ATTRIB
  155. for m in self._modules:
  156. add_watch(m, flags)
  157. def start(self):
  158. try:
  159. self.create_notifier()
  160. self._notifier.loop()
  161. finally:
  162. if self._wm:
  163. self._wm.close()
  164. # Notifier.close is called at the end of Notifier.loop
  165. self._wm = self._notifier = None
  166. def stop(self):
  167. pass
  168. def process_(self, event):
  169. self.on_change([event.path])
  170. process_IN_ATTRIB = process_IN_MODIFY = process_
  171. def on_change(self, modified):
  172. if self._on_change:
  173. return self._on_change(modified)
  174. def default_implementation():
  175. if hasattr(select, 'kqueue') and eventio.kqueue is not None:
  176. return 'kqueue'
  177. elif sys.platform.startswith('linux') and pyinotify:
  178. return 'inotify'
  179. else:
  180. return 'stat'
  181. implementations = {'kqueue': KQueueMonitor,
  182. 'inotify': InotifyMonitor,
  183. 'stat': StatMonitor}
  184. Monitor = implementations[
  185. os.environ.get('CELERYD_FSNOTIFY') or default_implementation()]
  186. class Autoreloader(bgThread):
  187. """Tracks changes in modules and fires reload commands"""
  188. Monitor = Monitor
  189. def __init__(self, controller, modules=None, monitor_cls=None, **options):
  190. super(Autoreloader, self).__init__()
  191. self.controller = controller
  192. app = self.controller.app
  193. self.modules = app.loader.task_modules if modules is None else modules
  194. self.options = options
  195. self._monitor = None
  196. self._hashes = None
  197. self.file_to_module = {}
  198. def on_init(self):
  199. files = self.file_to_module
  200. files.update(dict(
  201. (module_file(sys.modules[m]), m) for m in self.modules))
  202. self._monitor = self.Monitor(
  203. files, self.on_change,
  204. shutdown_event=self._is_shutdown, **self.options)
  205. self._hashes = dict([(f, file_hash(f)) for f in files])
  206. def register_with_event_loop(self, hub):
  207. if self._monitor is None:
  208. self.on_init()
  209. self._monitor.register_with_event_loop(hub)
  210. def on_event_loop_close(self, hub):
  211. if self._monitor is not None:
  212. self._monitor.on_event_loop_close(hub)
  213. def body(self):
  214. self.on_init()
  215. with ignore_errno('EINTR', 'EAGAIN'):
  216. self._monitor.start()
  217. def _maybe_modified(self, f):
  218. if os.path.exists(f):
  219. digest = file_hash(f)
  220. if digest != self._hashes[f]:
  221. self._hashes[f] = digest
  222. return True
  223. return False
  224. def on_change(self, files):
  225. modified = [f for f in files if self._maybe_modified(f)]
  226. if modified:
  227. names = [self.file_to_module[module] for module in modified]
  228. logger.info('Detected modified modules: %r', names)
  229. self._reload(names)
  230. def _reload(self, modules):
  231. self.controller.reload(modules, reload=True)
  232. def stop(self):
  233. if self._monitor:
  234. self._monitor.stop()