summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUnai Zalakain <unai@gisa-elkartea.org>2013-10-14 07:33:45 +0200
committerTim Graham <timograham@gmail.com>2013-10-30 11:45:43 -0400
commit15f82c701161b327cef54b469c00b6cbe01534db (patch)
tree4e9be0bbf1708c2c08f825ee2c83ae7e1095f352
parente9cb333bc359a31c548c97dc07e392cfe39be18e (diff)
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.
-rw-r--r--AUTHORS3
-rw-r--r--django/utils/autoreload.py74
-rw-r--r--docs/ref/django-admin.txt12
-rw-r--r--docs/releases/1.7.txt3
4 files changed, 81 insertions, 11 deletions
diff --git a/AUTHORS b/AUTHORS
index c7490182bf..6e208cd369 100644
--- a/AUTHORS
+++ b/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>
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index e1627f4954..ba98530f1b 100644
--- a/django/utils/autoreload.py
+++ b/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 @@ 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
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 2d9dcd9920..6bb03347c9 100644
--- a/docs/ref/django-admin.txt
+++ b/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
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 784ee86493..9a7ff41cf7 100644
--- a/docs/releases/1.7.txt
+++ b/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
^^^^^^