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>
Brian Harring <ferringb@gmail.com>
Brant Harris
Pascal Hartig <phartig@rdrei.net>
Ronny Haryanto <http://ronny.haryan.to/>
Axel Haustant <noirbizarre@gmail.com>
Hawkeye
@ -372,6 +373,7 @@ answer newbie questions, and generally made Django that much better:
Vladimir Kuzma <vladimirkuzma.ch@gmail.com>
Denis Kuzmichyov <kuzmichyov@gmail.com>
Panos Laganakos <panos.laganakos@gmail.com>
Chris Lamb <lamby@debian.org>
Nick Lane <nick.lane.au@gmail.com>
Łukasz Langa <lukasz@langa.pl>
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>
Marc Aymerich Gubern
Wiktor Kołodziej <wiktor@pykonik.org>
Unai Zalakain <unai@gisa-elkartea.org>
Mykola Zamkovoi <nickzam@gmail.com>
zegor
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
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import datetime
import os
import signal
import sys
import time
import traceback
from django.core.signals import request_finished
try:
from django.utils.six.moves import _thread as thread
except ImportError:
@ -51,6 +53,18 @@ try:
except ImportError:
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
_mtimes = {}
@ -58,14 +72,13 @@ _win = (sys.platform == "win32")
_error_files = []
def code_changed():
global _mtimes, _win
filenames = []
for m in list(sys.modules.values()):
try:
filenames.append(m.__file__)
except AttributeError:
pass
def gen_filenames():
"""
Yields a generator over filenames referenced in sys.modules.
"""
filenames = [filename.__file__ for filename in sys.modules.values()
if hasattr(filename, '__file__')]
for filename in filenames + _error_files:
if not filename:
continue
@ -73,8 +86,42 @@ def code_changed():
filename = filename[:-1]
if filename.endswith("$py.class"):
filename = filename[:-9] + ".py"
if not os.path.exists(filename):
continue # File might be in an egg, so it can't be reloaded.
if os.path.exists(filename):
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)
mtime = stat.st_mtime
if _win:
@ -129,11 +176,16 @@ def ensure_echo_on():
def reloader_thread():
ensure_echo_on()
if USE_INOTIFY:
fn = inotify_code_changed
else:
fn = code_changed
while RUN_RELOADER:
if code_changed():
if fn():
sys.exit(3) # force reload
time.sleep(1)
def restart_with_reloader():
while True:
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
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
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

View File

@ -343,6 +343,9 @@ Management Commands
Django takes this information from your settings file. If you have configured
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
^^^^^^