Fixed #9722 - used pyinotify as change detection system when available

Used pyinotify (when available) to replace the "pool-every-one-second"
mechanism in `django.utils.autoreload`.

Thanks Chris Lamb and Pascal Hartig for work on the patch.
This commit is contained in:
Unai Zalakain 2013-10-14 07:33:45 +02:00 committed by Tim Graham
parent e9cb333bc3
commit 15f82c7011
4 changed files with 81 additions and 11 deletions

View File

@ -286,6 +286,7 @@ answer newbie questions, and generally made Django that much better:
Will Hardy <django@willhardy.com.au> Will Hardy <django@willhardy.com.au>
Brian Harring <ferringb@gmail.com> Brian Harring <ferringb@gmail.com>
Brant Harris Brant Harris
Pascal Hartig <phartig@rdrei.net>
Ronny Haryanto <http://ronny.haryan.to/> Ronny Haryanto <http://ronny.haryan.to/>
Axel Haustant <noirbizarre@gmail.com> Axel Haustant <noirbizarre@gmail.com>
Hawkeye Hawkeye
@ -372,6 +373,7 @@ answer newbie questions, and generally made Django that much better:
Vladimir Kuzma <vladimirkuzma.ch@gmail.com> Vladimir Kuzma <vladimirkuzma.ch@gmail.com>
Denis Kuzmichyov <kuzmichyov@gmail.com> Denis Kuzmichyov <kuzmichyov@gmail.com>
Panos Laganakos <panos.laganakos@gmail.com> Panos Laganakos <panos.laganakos@gmail.com>
Chris Lamb <lamby@debian.org>
Nick Lane <nick.lane.au@gmail.com> Nick Lane <nick.lane.au@gmail.com>
Łukasz Langa <lukasz@langa.pl> Łukasz Langa <lukasz@langa.pl>
Stuart Langridge <http://www.kryogenix.org/> Stuart Langridge <http://www.kryogenix.org/>
@ -666,6 +668,7 @@ answer newbie questions, and generally made Django that much better:
Jesse Young <adunar@gmail.com> Jesse Young <adunar@gmail.com>
Marc Aymerich Gubern Marc Aymerich Gubern
Wiktor Kołodziej <wiktor@pykonik.org> Wiktor Kołodziej <wiktor@pykonik.org>
Unai Zalakain <unai@gisa-elkartea.org>
Mykola Zamkovoi <nickzam@gmail.com> Mykola Zamkovoi <nickzam@gmail.com>
zegor zegor
Gasper Zejn <zejn@kiberpipa.org> Gasper Zejn <zejn@kiberpipa.org>

View File

@ -28,12 +28,14 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import datetime
import os import os
import signal import signal
import sys import sys
import time import time
import traceback import traceback
from django.core.signals import request_finished
try: try:
from django.utils.six.moves import _thread as thread from django.utils.six.moves import _thread as thread
except ImportError: except ImportError:
@ -51,6 +53,18 @@ try:
except ImportError: except ImportError:
termios = None termios = None
USE_INOTIFY = False
try:
# Test whether inotify is enabled and likely to work
import pyinotify
fd = pyinotify.INotifyWrapper.create().inotify_init()
if fd >= 0:
USE_INOTIFY = True
os.close(fd)
except ImportError:
pass
RUN_RELOADER = True RUN_RELOADER = True
_mtimes = {} _mtimes = {}
@ -58,14 +72,13 @@ _win = (sys.platform == "win32")
_error_files = [] _error_files = []
def code_changed():
global _mtimes, _win def gen_filenames():
filenames = [] """
for m in list(sys.modules.values()): Yields a generator over filenames referenced in sys.modules.
try: """
filenames.append(m.__file__) filenames = [filename.__file__ for filename in sys.modules.values()
except AttributeError: if hasattr(filename, '__file__')]
pass
for filename in filenames + _error_files: for filename in filenames + _error_files:
if not filename: if not filename:
continue continue
@ -73,8 +86,42 @@ def code_changed():
filename = filename[:-1] filename = filename[:-1]
if filename.endswith("$py.class"): if filename.endswith("$py.class"):
filename = filename[:-9] + ".py" filename = filename[:-9] + ".py"
if not os.path.exists(filename): if os.path.exists(filename):
continue # File might be in an egg, so it can't be reloaded. yield filename
def inotify_code_changed():
"""
Checks for changed code using inotify. After being called
it blocks until a change event has been fired.
"""
wm = pyinotify.WatchManager()
notifier = pyinotify.Notifier(wm)
def update_watch(sender=None, **kwargs):
mask = (
pyinotify.IN_MODIFY |
pyinotify.IN_DELETE |
pyinotify.IN_ATTRIB |
pyinotify.IN_MOVED_FROM |
pyinotify.IN_MOVED_TO |
pyinotify.IN_CREATE
)
for path in gen_filenames():
wm.add_watch(path, mask)
request_finished.connect(update_watch)
update_watch()
# Block forever
notifier.check_events(timeout=None)
notifier.stop()
# If we are here the code must have changed.
return True
def code_changed():
global _mtimes, _win
for filename in gen_filenames():
stat = os.stat(filename) stat = os.stat(filename)
mtime = stat.st_mtime mtime = stat.st_mtime
if _win: if _win:
@ -129,11 +176,16 @@ def ensure_echo_on():
def reloader_thread(): def reloader_thread():
ensure_echo_on() ensure_echo_on()
if USE_INOTIFY:
fn = inotify_code_changed
else:
fn = code_changed
while RUN_RELOADER: while RUN_RELOADER:
if code_changed(): if fn():
sys.exit(3) # force reload sys.exit(3) # force reload
time.sleep(1) time.sleep(1)
def restart_with_reloader(): def restart_with_reloader():
while True: while True:
args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv

View File

@ -794,6 +794,18 @@ needed. You don't need to restart the server for code changes to take effect.
However, some actions like adding files or compiling translation files don't However, some actions like adding files or compiling translation files don't
trigger a restart, so you'll have to restart the server in these cases. trigger a restart, so you'll have to restart the server in these cases.
If you are using Linux and install `pyinotify`_, kernel signals will be used to
autoreload the server (rather than polling file modification timestamps each
second). This offers better scaling to large projects, reduction in response
time to code modification, more robust change detection, and battery usage
reduction.
.. _pyinotify: https://pypi.python.org/pypi/pyinotify/
.. versionadded:: 1.7
``pyinotify`` support was added.
When you start the server, and each time you change Python code while the When you start the server, and each time you change Python code while the
server is running, the server will validate all of your installed models. (See server is running, the server will validate all of your installed models. (See
the ``validate`` command below.) If the validator finds errors, it will print the ``validate`` command below.) If the validator finds errors, it will print

View File

@ -343,6 +343,9 @@ Management Commands
Django takes this information from your settings file. If you have configured Django takes this information from your settings file. If you have configured
multiple caches or multiple databases, all cache tables are created. multiple caches or multiple databases, all cache tables are created.
* The :djadmin:`runserver` command now uses ``inotify`` Linux kernel signals
for autoreloading if ``pyinotify`` is installed.
Models Models
^^^^^^ ^^^^^^