autoreload.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #
  2. # Copyright 2009 Facebook
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """Automatically restart the server when a source file is modified.
  16. Most applications should not access this module directly. Instead,
  17. pass the keyword argument ``autoreload=True`` to the
  18. `tornado.web.Application` constructor (or ``debug=True``, which
  19. enables this setting and several others). This will enable autoreload
  20. mode as well as checking for changes to templates and static
  21. resources. Note that restarting is a destructive operation and any
  22. requests in progress will be aborted when the process restarts. (If
  23. you want to disable autoreload while using other debug-mode features,
  24. pass both ``debug=True`` and ``autoreload=False``).
  25. This module can also be used as a command-line wrapper around scripts
  26. such as unit test runners. See the `main` method for details.
  27. The command-line wrapper and Application debug modes can be used together.
  28. This combination is encouraged as the wrapper catches syntax errors and
  29. other import-time failures, while debug mode catches changes once
  30. the server has started.
  31. This module depends on `.IOLoop`, so it will not work in WSGI applications
  32. and Google App Engine. It also will not work correctly when `.HTTPServer`'s
  33. multi-process mode is used.
  34. Reloading loses any Python interpreter command-line arguments (e.g. ``-u``)
  35. because it re-executes Python using ``sys.executable`` and ``sys.argv``.
  36. Additionally, modifying these variables will cause reloading to behave
  37. incorrectly.
  38. """
  39. from __future__ import absolute_import, division, print_function
  40. import os
  41. import sys
  42. # sys.path handling
  43. # -----------------
  44. #
  45. # If a module is run with "python -m", the current directory (i.e. "")
  46. # is automatically prepended to sys.path, but not if it is run as
  47. # "path/to/file.py". The processing for "-m" rewrites the former to
  48. # the latter, so subsequent executions won't have the same path as the
  49. # original.
  50. #
  51. # Conversely, when run as path/to/file.py, the directory containing
  52. # file.py gets added to the path, which can cause confusion as imports
  53. # may become relative in spite of the future import.
  54. #
  55. # We address the former problem by reconstructing the original command
  56. # line (Python >= 3.4) or by setting the $PYTHONPATH environment
  57. # variable (Python < 3.4) before re-execution so the new process will
  58. # see the correct path. We attempt to address the latter problem when
  59. # tornado.autoreload is run as __main__.
  60. if __name__ == "__main__":
  61. # This sys.path manipulation must come before our imports (as much
  62. # as possible - if we introduced a tornado.sys or tornado.os
  63. # module we'd be in trouble), or else our imports would become
  64. # relative again despite the future import.
  65. #
  66. # There is a separate __main__ block at the end of the file to call main().
  67. if sys.path[0] == os.path.dirname(__file__):
  68. del sys.path[0]
  69. import functools
  70. import logging
  71. import os
  72. import pkgutil # type: ignore
  73. import sys
  74. import traceback
  75. import types
  76. import subprocess
  77. import weakref
  78. from tornado import ioloop
  79. from tornado.log import gen_log
  80. from tornado import process
  81. from tornado.util import exec_in
  82. try:
  83. import signal
  84. except ImportError:
  85. signal = None
  86. # os.execv is broken on Windows and can't properly parse command line
  87. # arguments and executable name if they contain whitespaces. subprocess
  88. # fixes that behavior.
  89. _has_execv = sys.platform != 'win32'
  90. _watched_files = set()
  91. _reload_hooks = []
  92. _reload_attempted = False
  93. _io_loops = weakref.WeakKeyDictionary() # type: ignore
  94. _autoreload_is_main = False
  95. _original_argv = None
  96. _original_spec = None
  97. def start(check_time=500):
  98. """Begins watching source files for changes.
  99. .. versionchanged:: 5.0
  100. The ``io_loop`` argument (deprecated since version 4.1) has been removed.
  101. """
  102. io_loop = ioloop.IOLoop.current()
  103. if io_loop in _io_loops:
  104. return
  105. _io_loops[io_loop] = True
  106. if len(_io_loops) > 1:
  107. gen_log.warning("tornado.autoreload started more than once in the same process")
  108. modify_times = {}
  109. callback = functools.partial(_reload_on_update, modify_times)
  110. scheduler = ioloop.PeriodicCallback(callback, check_time)
  111. scheduler.start()
  112. def wait():
  113. """Wait for a watched file to change, then restart the process.
  114. Intended to be used at the end of scripts like unit test runners,
  115. to run the tests again after any source file changes (but see also
  116. the command-line interface in `main`)
  117. """
  118. io_loop = ioloop.IOLoop()
  119. io_loop.add_callback(start)
  120. io_loop.start()
  121. def watch(filename):
  122. """Add a file to the watch list.
  123. All imported modules are watched by default.
  124. """
  125. _watched_files.add(filename)
  126. def add_reload_hook(fn):
  127. """Add a function to be called before reloading the process.
  128. Note that for open file and socket handles it is generally
  129. preferable to set the ``FD_CLOEXEC`` flag (using `fcntl` or
  130. ``tornado.platform.auto.set_close_exec``) instead
  131. of using a reload hook to close them.
  132. """
  133. _reload_hooks.append(fn)
  134. def _reload_on_update(modify_times):
  135. if _reload_attempted:
  136. # We already tried to reload and it didn't work, so don't try again.
  137. return
  138. if process.task_id() is not None:
  139. # We're in a child process created by fork_processes. If child
  140. # processes restarted themselves, they'd all restart and then
  141. # all call fork_processes again.
  142. return
  143. for module in list(sys.modules.values()):
  144. # Some modules play games with sys.modules (e.g. email/__init__.py
  145. # in the standard library), and occasionally this can cause strange
  146. # failures in getattr. Just ignore anything that's not an ordinary
  147. # module.
  148. if not isinstance(module, types.ModuleType):
  149. continue
  150. path = getattr(module, "__file__", None)
  151. if not path:
  152. continue
  153. if path.endswith(".pyc") or path.endswith(".pyo"):
  154. path = path[:-1]
  155. _check_file(modify_times, path)
  156. for path in _watched_files:
  157. _check_file(modify_times, path)
  158. def _check_file(modify_times, path):
  159. try:
  160. modified = os.stat(path).st_mtime
  161. except Exception:
  162. return
  163. if path not in modify_times:
  164. modify_times[path] = modified
  165. return
  166. if modify_times[path] != modified:
  167. gen_log.info("%s modified; restarting server", path)
  168. _reload()
  169. def _reload():
  170. global _reload_attempted
  171. _reload_attempted = True
  172. for fn in _reload_hooks:
  173. fn()
  174. if hasattr(signal, "setitimer"):
  175. # Clear the alarm signal set by
  176. # ioloop.set_blocking_log_threshold so it doesn't fire
  177. # after the exec.
  178. signal.setitimer(signal.ITIMER_REAL, 0, 0)
  179. # sys.path fixes: see comments at top of file. If __main__.__spec__
  180. # exists, we were invoked with -m and the effective path is about to
  181. # change on re-exec. Reconstruct the original command line to
  182. # ensure that the new process sees the same path we did. If
  183. # __spec__ is not available (Python < 3.4), check instead if
  184. # sys.path[0] is an empty string and add the current directory to
  185. # $PYTHONPATH.
  186. if _autoreload_is_main:
  187. spec = _original_spec
  188. argv = _original_argv
  189. else:
  190. spec = getattr(sys.modules['__main__'], '__spec__', None)
  191. argv = sys.argv
  192. if spec:
  193. argv = ['-m', spec.name] + argv[1:]
  194. else:
  195. path_prefix = '.' + os.pathsep
  196. if (sys.path[0] == '' and
  197. not os.environ.get("PYTHONPATH", "").startswith(path_prefix)):
  198. os.environ["PYTHONPATH"] = (path_prefix +
  199. os.environ.get("PYTHONPATH", ""))
  200. if not _has_execv:
  201. subprocess.Popen([sys.executable] + argv)
  202. os._exit(0)
  203. else:
  204. try:
  205. os.execv(sys.executable, [sys.executable] + argv)
  206. except OSError:
  207. # Mac OS X versions prior to 10.6 do not support execv in
  208. # a process that contains multiple threads. Instead of
  209. # re-executing in the current process, start a new one
  210. # and cause the current process to exit. This isn't
  211. # ideal since the new process is detached from the parent
  212. # terminal and thus cannot easily be killed with ctrl-C,
  213. # but it's better than not being able to autoreload at
  214. # all.
  215. # Unfortunately the errno returned in this case does not
  216. # appear to be consistent, so we can't easily check for
  217. # this error specifically.
  218. os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable] + argv)
  219. # At this point the IOLoop has been closed and finally
  220. # blocks will experience errors if we allow the stack to
  221. # unwind, so just exit uncleanly.
  222. os._exit(0)
  223. _USAGE = """\
  224. Usage:
  225. python -m tornado.autoreload -m module.to.run [args...]
  226. python -m tornado.autoreload path/to/script.py [args...]
  227. """
  228. def main():
  229. """Command-line wrapper to re-run a script whenever its source changes.
  230. Scripts may be specified by filename or module name::
  231. python -m tornado.autoreload -m tornado.test.runtests
  232. python -m tornado.autoreload tornado/test/runtests.py
  233. Running a script with this wrapper is similar to calling
  234. `tornado.autoreload.wait` at the end of the script, but this wrapper
  235. can catch import-time problems like syntax errors that would otherwise
  236. prevent the script from reaching its call to `wait`.
  237. """
  238. # Remember that we were launched with autoreload as main.
  239. # The main module can be tricky; set the variables both in our globals
  240. # (which may be __main__) and the real importable version.
  241. import tornado.autoreload
  242. global _autoreload_is_main
  243. global _original_argv, _original_spec
  244. tornado.autoreload._autoreload_is_main = _autoreload_is_main = True
  245. original_argv = sys.argv
  246. tornado.autoreload._original_argv = _original_argv = original_argv
  247. original_spec = getattr(sys.modules['__main__'], '__spec__', None)
  248. tornado.autoreload._original_spec = _original_spec = original_spec
  249. sys.argv = sys.argv[:]
  250. if len(sys.argv) >= 3 and sys.argv[1] == "-m":
  251. mode = "module"
  252. module = sys.argv[2]
  253. del sys.argv[1:3]
  254. elif len(sys.argv) >= 2:
  255. mode = "script"
  256. script = sys.argv[1]
  257. sys.argv = sys.argv[1:]
  258. else:
  259. print(_USAGE, file=sys.stderr)
  260. sys.exit(1)
  261. try:
  262. if mode == "module":
  263. import runpy
  264. runpy.run_module(module, run_name="__main__", alter_sys=True)
  265. elif mode == "script":
  266. with open(script) as f:
  267. # Execute the script in our namespace instead of creating
  268. # a new one so that something that tries to import __main__
  269. # (e.g. the unittest module) will see names defined in the
  270. # script instead of just those defined in this module.
  271. global __file__
  272. __file__ = script
  273. # If __package__ is defined, imports may be incorrectly
  274. # interpreted as relative to this module.
  275. global __package__
  276. del __package__
  277. exec_in(f.read(), globals(), globals())
  278. except SystemExit as e:
  279. logging.basicConfig()
  280. gen_log.info("Script exited with status %s", e.code)
  281. except Exception as e:
  282. logging.basicConfig()
  283. gen_log.warning("Script exited with uncaught exception", exc_info=True)
  284. # If an exception occurred at import time, the file with the error
  285. # never made it into sys.modules and so we won't know to watch it.
  286. # Just to make sure we've covered everything, walk the stack trace
  287. # from the exception and watch every file.
  288. for (filename, lineno, name, line) in traceback.extract_tb(sys.exc_info()[2]):
  289. watch(filename)
  290. if isinstance(e, SyntaxError):
  291. # SyntaxErrors are special: their innermost stack frame is fake
  292. # so extract_tb won't see it and we have to get the filename
  293. # from the exception object.
  294. watch(e.filename)
  295. else:
  296. logging.basicConfig()
  297. gen_log.info("Script exited normally")
  298. # restore sys.argv so subsequent executions will include autoreload
  299. sys.argv = original_argv
  300. if mode == 'module':
  301. # runpy did a fake import of the module as __main__, but now it's
  302. # no longer in sys.modules. Figure out where it is and watch it.
  303. loader = pkgutil.get_loader(module)
  304. if loader is not None:
  305. watch(loader.get_filename())
  306. wait()
  307. if __name__ == "__main__":
  308. # See also the other __main__ block at the top of the file, which modifies
  309. # sys.path before our imports
  310. main()