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:
parent
e9cb333bc3
commit
15f82c7011
3
AUTHORS
3
AUTHORS
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue