Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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.
  • Loading branch information...
commit 15f82c701161b327cef54b469c00b6cbe01534db 1 parent e9cb333
@unaizalakain unaizalakain authored timgraham committed
View
3  AUTHORS
@@ -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
74 django/utils/autoreload.py
@@ -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 @@
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 @@
_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
12 docs/ref/django-admin.txt
@@ -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
3  docs/releases/1.7.txt
@@ -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
^^^^^^
Please sign in to comment.
Something went wrong with that request. Please try again.