]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Experimental module auto-reloading on modification
authorBret Taylor <btaylor@gmail.com>
Wed, 16 Sep 2009 08:29:12 +0000 (01:29 -0700)
committerBret Taylor <btaylor@gmail.com>
Wed, 16 Sep 2009 08:29:12 +0000 (01:29 -0700)
tornado/ioloop.py
tornado/reloader.py [new file with mode: 0644]
tornado/web.py

index 8bbd8dcedeaf71ed38c17845b3040072b3a409c0..baa21c25f303929643a2e95741969e404130e5bf 100644 (file)
@@ -264,6 +264,33 @@ class _Timeout(object):
                    (other.deadline, id(other.callback)))
 
 
+class PeriodicCallback(object):
+    """Schedules the given callback to be called periodically.
+
+    The callback is called every callback_time milliseconds.
+    """
+    def __init__(self, callback, callback_time, io_loop=None):
+        self.callback = callback
+        self.callback_time = callback_time
+        self.io_loop = io_loop or ioloop.IOLoop.instance()
+        self._running = True
+
+    def start(self):
+        timeout = time.time() + self.callback_time / 1000.0
+        self.io_loop.add_timeout(timeout, self._run)
+
+    def stop(self):
+        self._running = False
+
+    def _run(self):
+        if not self._running: return
+        try:
+            self.callback()
+        except:
+            logging.error("Error in periodic callback", exc_info=True)
+        self.start()
+
+
 class _EPoll(object):
     """An epoll-based event loop using our C module for Python 2.5 systems"""
     _EPOLL_CTL_ADD = 1
diff --git a/tornado/reloader.py b/tornado/reloader.py
new file mode 100644 (file)
index 0000000..d0a1389
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import functools
+import ioloop
+import logging
+import os
+import os.path
+import sys
+
+
+def start(io_loop=None, check_time=200):
+    """Restarts the process automatically when a module is modified.
+
+    We run on the I/O loop, and restarting is a destructive operation,
+    so will terminate any pending requests.
+    """
+    io_loop = io_loop or ioloop.IOLoop.instance()
+    modify_times = {}
+    callback = functools.partial(_reload_on_update, io_loop, modify_times)
+    scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop)
+    scheduler.start()
+
+
+def _reload_on_update(io_loop, modify_times):
+    for module in sys.modules.values():
+        path = getattr(module, "__file__", None)
+        if not path: continue
+        if path.endswith(".pyc") or path.endswith(".pyo"):
+            path = path[:-1]
+        try:
+            modified = os.stat(path).st_mtime
+        except:
+            continue
+        if path not in modify_times:
+            modify_times[path] = modified
+            continue
+        if modify_times[path] != modified:
+            logging.info("%s modified; restarting server", path)
+            for fd in io_loop._handlers.keys():
+                try:
+                    os.close(fd)
+                except:
+                    pass
+            os.execv(sys.executable, [sys.executable] + sys.argv)
index c4d8701198e95e48175064078bbb6dead10e054c..2e23e748d0685dba4ef7832379bf135c3d77f8cc 100644 (file)
@@ -877,6 +877,11 @@ class Application(object):
             ])
         if handlers: self.add_handlers(".*$", handlers)
 
+        # Automatically reload modified modules
+        if self.settings.get("auto_reload"):
+            import reloader
+            reloader.start()
+
     def add_handlers(self, host_pattern, host_handlers):
         """Appends the given handlers to our handler list."""
         if not host_pattern.endswith("$"):