| #!/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. |
| |
| """A module to automatically restart the server when a module is modified. |
| |
| Most applications should not call this module directly. Instead, pass the |
| keyword argument ``debug=True`` to the `tornado.web.Application` constructor. |
| This will enable autoreload mode as well as checking for changes to templates |
| and static resources. |
| |
| This module depends on IOLoop, so it will not work in WSGI applications |
| and Google AppEngine. It also will not work correctly when HTTPServer's |
| multi-process mode is used. |
| """ |
| |
| import functools |
| import logging |
| import os |
| import sys |
| import types |
| |
| from tornado import ioloop |
| |
| try: |
| import signal |
| except ImportError: |
| signal = None |
| |
| def start(io_loop=None, check_time=500): |
| """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() |
| |
| |
| _reload_attempted = False |
| |
| def _reload_on_update(io_loop, modify_times): |
| global _reload_attempted |
| if _reload_attempted: |
| # We already tried to reload and it didn't work, so don't try again. |
| return |
| for module in sys.modules.values(): |
| # Some modules play games with sys.modules (e.g. email/__init__.py |
| # in the standard library), and occasionally this can cause strange |
| # failures in getattr. Just ignore anything that's not an ordinary |
| # module. |
| if not isinstance(module, types.ModuleType): continue |
| 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) |
| _reload_attempted = True |
| for fd in io_loop._handlers.keys(): |
| try: |
| os.close(fd) |
| except: |
| pass |
| if hasattr(signal, "setitimer"): |
| # Clear the alarm signal set by |
| # ioloop.set_blocking_log_threshold so it doesn't fire |
| # after the exec. |
| signal.setitimer(signal.ITIMER_REAL, 0, 0) |
| try: |
| os.execv(sys.executable, [sys.executable] + sys.argv) |
| except OSError: |
| # Mac OS X versions prior to 10.6 do not support execv in |
| # a process that contains multiple threads. Instead of |
| # re-executing in the current process, start a new one |
| # and cause the current process to exit. This isn't |
| # ideal since the new process is detached from the parent |
| # terminal and thus cannot easily be killed with ctrl-C, |
| # but it's better than not being able to autoreload at |
| # all. |
| # Unfortunately the errno returned in this case does not |
| # appear to be consistent, so we can't easily check for |
| # this error specifically. |
| os.spawnv(os.P_NOWAIT, sys.executable, |
| [sys.executable] + sys.argv) |
| sys.exit(0) |