Skip to content

Commit 15f82c7

Browse files
Unai Zalakaintimgraham
authored andcommitted
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.
1 parent e9cb333 commit 15f82c7

File tree

4 files changed

+81
-11
lines changed

4 files changed

+81
-11
lines changed

AUTHORS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ answer newbie questions, and generally made Django that much better:
286286
Will Hardy <[email protected]>
287287
Brian Harring <[email protected]>
288288
Brant Harris
289+
Pascal Hartig <[email protected]>
289290
Ronny Haryanto <http://ronny.haryan.to/>
290291
Axel Haustant <[email protected]>
291292
Hawkeye
@@ -372,6 +373,7 @@ answer newbie questions, and generally made Django that much better:
372373
Vladimir Kuzma <[email protected]>
373374
Denis Kuzmichyov <[email protected]>
374375
Panos Laganakos <[email protected]>
376+
Chris Lamb <[email protected]>
375377
Nick Lane <[email protected]>
376378
Łukasz Langa <[email protected]>
377379
Stuart Langridge <http://www.kryogenix.org/>
@@ -666,6 +668,7 @@ answer newbie questions, and generally made Django that much better:
666668
Jesse Young <[email protected]>
667669
Marc Aymerich Gubern
668670
Wiktor Kołodziej <[email protected]>
671+
Unai Zalakain <[email protected]>
669672
Mykola Zamkovoi <[email protected]>
670673
zegor
671674
Gasper Zejn <[email protected]>

django/utils/autoreload.py

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2929
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030

31+
import datetime
3132
import os
3233
import signal
3334
import sys
3435
import time
3536
import traceback
3637

38+
from django.core.signals import request_finished
3739
try:
3840
from django.utils.six.moves import _thread as thread
3941
except ImportError:
@@ -51,30 +53,75 @@
5153
except ImportError:
5254
termios = None
5355

56+
USE_INOTIFY = False
57+
try:
58+
# Test whether inotify is enabled and likely to work
59+
import pyinotify
60+
61+
fd = pyinotify.INotifyWrapper.create().inotify_init()
62+
if fd >= 0:
63+
USE_INOTIFY = True
64+
os.close(fd)
65+
except ImportError:
66+
pass
67+
5468
RUN_RELOADER = True
5569

5670
_mtimes = {}
5771
_win = (sys.platform == "win32")
5872

5973
_error_files = []
6074

61-
def code_changed():
62-
global _mtimes, _win
63-
filenames = []
64-
for m in list(sys.modules.values()):
65-
try:
66-
filenames.append(m.__file__)
67-
except AttributeError:
68-
pass
75+
76+
def gen_filenames():
77+
"""
78+
Yields a generator over filenames referenced in sys.modules.
79+
"""
80+
filenames = [filename.__file__ for filename in sys.modules.values()
81+
if hasattr(filename, '__file__')]
6982
for filename in filenames + _error_files:
7083
if not filename:
7184
continue
7285
if filename.endswith(".pyc") or filename.endswith(".pyo"):
7386
filename = filename[:-1]
7487
if filename.endswith("$py.class"):
7588
filename = filename[:-9] + ".py"
76-
if not os.path.exists(filename):
77-
continue # File might be in an egg, so it can't be reloaded.
89+
if os.path.exists(filename):
90+
yield filename
91+
92+
def inotify_code_changed():
93+
"""
94+
Checks for changed code using inotify. After being called
95+
it blocks until a change event has been fired.
96+
"""
97+
wm = pyinotify.WatchManager()
98+
notifier = pyinotify.Notifier(wm)
99+
100+
def update_watch(sender=None, **kwargs):
101+
mask = (
102+
pyinotify.IN_MODIFY |
103+
pyinotify.IN_DELETE |
104+
pyinotify.IN_ATTRIB |
105+
pyinotify.IN_MOVED_FROM |
106+
pyinotify.IN_MOVED_TO |
107+
pyinotify.IN_CREATE
108+
)
109+
for path in gen_filenames():
110+
wm.add_watch(path, mask)
111+
112+
request_finished.connect(update_watch)
113+
update_watch()
114+
115+
# Block forever
116+
notifier.check_events(timeout=None)
117+
notifier.stop()
118+
119+
# If we are here the code must have changed.
120+
return True
121+
122+
def code_changed():
123+
global _mtimes, _win
124+
for filename in gen_filenames():
78125
stat = os.stat(filename)
79126
mtime = stat.st_mtime
80127
if _win:
@@ -129,11 +176,16 @@ def ensure_echo_on():
129176

130177
def reloader_thread():
131178
ensure_echo_on()
179+
if USE_INOTIFY:
180+
fn = inotify_code_changed
181+
else:
182+
fn = code_changed
132183
while RUN_RELOADER:
133-
if code_changed():
184+
if fn():
134185
sys.exit(3) # force reload
135186
time.sleep(1)
136187

188+
137189
def restart_with_reloader():
138190
while True:
139191
args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv

docs/ref/django-admin.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,18 @@ needed. You don't need to restart the server for code changes to take effect.
794794
However, some actions like adding files or compiling translation files don't
795795
trigger a restart, so you'll have to restart the server in these cases.
796796

797+
If you are using Linux and install `pyinotify`_, kernel signals will be used to
798+
autoreload the server (rather than polling file modification timestamps each
799+
second). This offers better scaling to large projects, reduction in response
800+
time to code modification, more robust change detection, and battery usage
801+
reduction.
802+
803+
.. _pyinotify: https://pypi.python.org/pypi/pyinotify/
804+
805+
.. versionadded:: 1.7
806+
807+
``pyinotify`` support was added.
808+
797809
When you start the server, and each time you change Python code while the
798810
server is running, the server will validate all of your installed models. (See
799811
the ``validate`` command below.) If the validator finds errors, it will print

docs/releases/1.7.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,9 @@ Management Commands
343343
Django takes this information from your settings file. If you have configured
344344
multiple caches or multiple databases, all cache tables are created.
345345

346+
* The :djadmin:`runserver` command now uses ``inotify`` Linux kernel signals
347+
for autoreloading if ``pyinotify`` is installed.
348+
346349
Models
347350
^^^^^^
348351

0 commit comments

Comments
 (0)