Modify 'reload' config to be more consistent with existing API

--reload = Runs the reloader with inotify if available and falls back on
           FS polling.
--reload=inotify = Forces the reloader to run with inotify
--reload=poll = Forces the reloader to use FS polling
This commit is contained in:
Mark Adams 2016-10-24 09:08:02 -05:00 committed by Mark Adams
parent 64b26ef766
commit 92d48256e4
3 changed files with 43 additions and 30 deletions

View File

@ -489,6 +489,19 @@ def validate_hostport(val):
else: else:
raise TypeError("Value must consist of: hostname:port") raise TypeError("Value must consist of: hostname:port")
def validate_reloader(val):
if val is None:
val = 'default'
choices = ['poll', 'inotify', 'default']
if val not in choices:
raise ConfigError(
'Invalid reloader type. Must be one of: %s' % choices
)
def get_default_config_file(): def get_default_config_file():
config_path = os.path.join(os.path.abspath(os.getcwd()), config_path = os.path.join(os.path.abspath(os.getcwd()),
'gunicorn.conf.py') 'gunicorn.conf.py')
@ -806,9 +819,11 @@ class Reload(Setting):
name = "reload" name = "reload"
section = 'Debugging' section = 'Debugging'
cli = ['--reload'] cli = ['--reload']
validator = validate_bool validator = validate_reloader
action = 'store_true' nargs = '?'
default = False const = 'default'
default = None
desc = '''\ desc = '''\
Restart workers when code changes. Restart workers when code changes.
@ -818,24 +833,14 @@ class Reload(Setting):
The reloader is incompatible with application preloading. When using a The reloader is incompatible with application preloading. When using a
paste configuration be sure that the server block does not import any paste configuration be sure that the server block does not import any
application code or the reload will not work as designed. application code or the reload will not work as designed.
When using this option, you can optionally specify whether you would
like to use file system polling or the kernel's inotify API to watch
for changes. Generally, inotify should be preferred if available
because it consumes less system resources. If no preference is given,
inotify will attempted with a fallback to FS polling.'
''' '''
class ReloadInotify(Setting):
name = 'inotify'
section = 'Debugging'
cli = ['--use-inotify']
validator = validate_bool
action = "store_true"
default = False
desc = '''\
When using the 'reload' option, use the kernel's inotify APIs to watch
files instead of polling the filesystem. On many systems this could result
in a performance improvement when using 'reload'.
This setting must be used in conjunction with 'reload' and requires the
'inotify' package be installed from PyPI.
'''
class Spew(Setting): class Spew(Setting):
name = "spew" name = "spew"

View File

@ -60,18 +60,20 @@ try:
except ImportError: except ImportError:
has_inotify = False has_inotify = False
class InotifyReloader(): class InotifyReloader():
def __init__(self, callback=None): def __init__(self, callback=None):
raise ImportError('You must have the inotify module installed to use the INotify reloader') raise ImportError('You must have the inotify module installed to use '
'the inotify reloader')
if has_inotify: if has_inotify:
class InotifyReloader(threading.Thread): class InotifyReloader(threading.Thread):
event_mask = (inotify.constants.IN_CREATE | inotify.constants.IN_DELETE event_mask = (inotify.constants.IN_CREATE | inotify.constants.IN_DELETE
| inotify.constants.IN_DELETE_SELF | inotify.constants.IN_MODIFY | inotify.constants.IN_DELETE_SELF | inotify.constants.IN_MODIFY
| inotify.constants.IN_MOVE_SELF | inotify.constants.IN_MOVED_FROM | inotify.constants.IN_MOVE_SELF | inotify.constants.IN_MOVED_FROM
| inotify.constants.IN_MOVED_TO) | inotify.constants.IN_MOVED_TO)
def __init__(self, extra_files=None, callback=None): def __init__(self, extra_files=None, callback=None):
super(InotifyReloader, self).__init__() super(InotifyReloader, self).__init__()
self.setDaemon(True) self.setDaemon(True)
@ -102,14 +104,14 @@ if has_inotify:
for dirname in self._dirs: for dirname in self._dirs:
self._watcher.add_watch(dirname, mask=self.event_mask) self._watcher.add_watch(dirname, mask=self.event_mask)
for event in self._watcher.event_gen(): for event in self._watcher.event_gen():
if event is None: if event is None:
continue continue
types = event[1]
filename = event[3] filename = event[3]
self._callback(filename) self._callback(filename)
preferred_reloader = InotifyReloader if has_inotify else Reloader

View File

@ -14,7 +14,7 @@ import traceback
from gunicorn import util from gunicorn import util
from gunicorn.workers.workertmp import WorkerTmp from gunicorn.workers.workertmp import WorkerTmp
from gunicorn.reloader import Reloader, InotifyReloader, has_inotify from gunicorn.reloader import preferred_reloader, Reloader, InotifyReloader
from gunicorn.http.errors import ( from gunicorn.http.errors import (
InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod, InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod,
InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders, InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders,
@ -122,8 +122,14 @@ class Worker(object):
self.cfg.worker_int(self) self.cfg.worker_int(self)
time.sleep(0.1) time.sleep(0.1)
sys.exit(0) sys.exit(0)
reloader_cls = Reloader if not self.cfg.inotify else InotifyReloader if self.cfg.reload == 'poll':
reloader_cls = Reloader
elif self.cfg.reload == 'inotify':
reloader_cls = InotifyReloader
else:
reloader_cls = preferred_reloader
self.reloader = reloader_cls(callback=changed) self.reloader = reloader_cls(callback=changed)
self.reloader.start() self.reloader.start()