Quellcode durchsuchen

Automatic module reloading

Mher Movsisyan vor 13 Jahren
Ursprung
Commit
d27fe28dff
1 geänderte Dateien mit 178 neuen und 0 gelöschten Zeilen
  1. 178 0
      celery/worker/autoreload.py

+ 178 - 0
celery/worker/autoreload.py

@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+"""
+    celery.worker.autoreload
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    This module implements the automatic module reloading
+"""
+from __future__ import with_statement
+
+import os
+import sys
+import time
+import select
+import hashlib
+
+from collections import defaultdict
+
+from celery.task.control import broadcast
+
+
+def file_hash(filename, algorithm='md5'):
+    hobj = hashlib.new(algorithm)
+    with open(filename, 'rb') as f:
+        for chunk in iter(lambda: f.read(2 ** 20), ''):
+            hobj.update(chunk)
+    return hobj.digest()
+
+
+class StatMonitor(object):
+    """File change monitor based on `stat` system call"""
+    def __init__(self, files, on_change=None, interval=0.5):
+        self._files = files
+        self._interval = interval
+        self._on_change = on_change
+        self._modify_times = defaultdict(int)
+
+    def start(self):
+        while True:
+            modified = {}
+            for m in self._files:
+                mt = self._mtime(m)
+                if mt is None:
+                    break
+                if self._modify_times[m] != mt:
+                    modified[m] = mt
+            else:
+                if modified:
+                    self.on_change(modified.keys())
+                    self._modify_times.update(modified)
+
+            time.sleep(self._interval)
+
+    def on_change(self, modified):
+        if self._on_change:
+            return self._on_change(modified)
+
+    @classmethod
+    def _mtime(cls, path):
+        try:
+            return os.stat(path).st_mtime
+        except:
+            return
+
+
+class KQueueMonitor(object):
+    """File change monitor based on BSD kernel event notifications"""
+    def __init__(self, files, on_change=None):
+        assert hasattr(select, 'kqueue')
+        self._files = dict([(f, None) for f in files])
+        self._on_change = on_change
+
+    def start(self):
+        try:
+            self._kq = select.kqueue()
+            kevents = []
+            for f in self._files:
+                self._files[f] = fd = os.open(f, os.O_RDONLY)
+
+                ev = select.kevent(fd,
+                        filter=select.KQ_FILTER_VNODE,
+                        flags=select.KQ_EV_ADD |
+                              select.KQ_EV_ENABLE |
+                              select.KQ_EV_CLEAR,
+                        fflags=select.KQ_NOTE_WRITE |
+                               select.KQ_NOTE_EXTEND)
+                kevents.append(ev)
+
+            events = self._kq.control(kevents, 0)
+            while True:
+                events = self._kq.control(kevents, 1)
+                fds = [e.ident for e in events]
+                modified = [k for k, v in self._files.iteritems()
+                                            if v in fds]
+                self.on_change(modified)
+        finally:
+            self.close()
+
+    def close(self):
+        self._kq.close()
+        for f in self._files:
+            if self._files[f] is not None:
+                os.close(self._files[f])
+                self._files[f] = None
+
+    def on_change(self, modified):
+        if self._on_change:
+            return self._on_change(modified)
+
+
+try:
+    import pyinotify
+except ImportError:
+    pyinotify = None    # noqa
+
+
+class InotifyMonitor(pyinotify and pyinotify.ProcessEvent or object):
+    """File change monitor based on  Linux kernel `inotify` subsystem"""
+    def __init__(self, modules, on_change=None):
+        assert pyinotify
+        self._modules = modules
+        self._on_change = on_change
+
+    def start(self):
+        try:
+            self._wm = pyinotify.WatchManager()
+            self._notifier = pyinotify.Notifier(self._wm)
+            for m in self._modules:
+                self._wm.add_watch(m, pyinotify.IN_MODIFY)
+            self._notifier.loop()
+        finally:
+            self.close()
+
+    def close(self):
+        self._notifier.stop()
+        self._wm.close()
+
+    def process_IN_MODIFY(self, event):
+        self.on_change(event.pathname)
+
+    def on_change(self, modified):
+        if self._on_change:
+            self._on_change(modified)
+
+
+if hasattr(select, 'kqueue'):
+    _monitor_cls = KQueueMonitor
+elif sys.platform.startswith('linux') and pyinotify:
+    _monitor_cls = InotifyMonitor
+else:
+    _monitor_cls = StatMonitor
+
+
+class AutoReloader(object):
+    """Tracks changes in modules and fires reload commands"""
+    def __init__(self, modules, monitor_cls=_monitor_cls, *args, **kwargs):
+        self._monitor = monitor_cls(modules, self.on_change, *args, **kwargs)
+        self._hashes = dict([(f, file_hash(f)) for f in modules])
+
+    def start(self):
+        self._monitor.start()
+
+    def on_change(self, files):
+        modified = []
+        for f in files:
+            fhash = file_hash(f)
+            if fhash != self._hashes[f]:
+                modified.append(f)
+                self._hashes[f] = fhash
+        if modified:
+            self._reload(map(self._module_name, modified))
+
+    def _reload(self, modules):
+        broadcast("pool_restart",
+                arguments={"imports": modules, "reload_modules": True})
+
+    @classmethod
+    def _module_name(cls, path):
+        return os.path.splitext(os.path.basename(path))[0]