Add support for --reload-engine

Currently, gunicorn automatically uses the preferred reloader (inotify
if present with fallback to polling). However, it would be useful in
some scenarios if users could force polling.

The solution for this is to add a new configuration option called
'reload_engine' which takes one of three options: ['auto', 'poll',
'inotify'].

Fixes #1459
This commit is contained in:
Mark Adams 2017-02-14 10:35:35 -06:00 committed by Berker Peksag
parent 34a624ff59
commit bc20bea7d9
4 changed files with 49 additions and 2 deletions

View File

@ -23,6 +23,7 @@ import shlex
from gunicorn import __version__ from gunicorn import __version__
from gunicorn import _compat from gunicorn import _compat
from gunicorn.errors import ConfigError from gunicorn.errors import ConfigError
from gunicorn.reloader import reloader_engines
from gunicorn import six from gunicorn import six
from gunicorn import util from gunicorn import util
@ -496,6 +497,13 @@ def validate_hostport(val):
raise TypeError("Value must consist of: hostname:port") raise TypeError("Value must consist of: hostname:port")
def validate_reload_engine(val):
if val not in reloader_engines:
raise ConfigError("Invalid reload_engine: %r" % val)
return val
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')
@ -838,6 +846,26 @@ class Reload(Setting):
''' '''
class ReloadEngine(Setting):
name = "reload_engine"
section = "Debugging"
cli = ["--reload-engine"]
meta = "STRING"
validator = validate_reload_engine
default = "auto"
desc = """\
The implementation that should be used to power :ref:`reload`.
Valid engines are:
* 'auto'
* 'poll'
* 'inotify' (requires inotify)
.. versionadded:: 19.7
"""
class Spew(Setting): class Spew(Setting):
name = "spew" name = "spew"
section = "Debugging" section = "Debugging"

View File

@ -115,3 +115,9 @@ if has_inotify:
preferred_reloader = InotifyReloader if has_inotify else Reloader preferred_reloader = InotifyReloader if has_inotify else Reloader
reloader_engines = {
'auto': preferred_reloader,
'poll': Reloader,
'inotify': InotifyReloader,
}

View File

@ -15,7 +15,7 @@ import traceback
from gunicorn import six from gunicorn import six
from gunicorn import util from gunicorn import util
from gunicorn.workers.workertmp import WorkerTmp from gunicorn.workers.workertmp import WorkerTmp
from gunicorn.reloader import preferred_reloader from gunicorn.reloader import reloader_engines
from gunicorn.http.errors import ( from gunicorn.http.errors import (
InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod, InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod,
InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders, InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders,
@ -119,7 +119,8 @@ class Worker(object):
time.sleep(0.1) time.sleep(0.1)
sys.exit(0) sys.exit(0)
self.reloader = preferred_reloader(callback=changed) reloader_cls = reloader_engines[self.cfg.reload_engine]
self.reloader = reloader_cls(callback=changed)
self.reloader.start() self.reloader.start()
self.load_wsgi() self.load_wsgi()

View File

@ -10,6 +10,7 @@ import pytest
from gunicorn import config from gunicorn import config
from gunicorn.app.base import Application from gunicorn.app.base import Application
from gunicorn.errors import ConfigError
from gunicorn.workers.sync import SyncWorker from gunicorn.workers.sync import SyncWorker
from gunicorn import glogging from gunicorn import glogging
from gunicorn.instrument import statsd from gunicorn.instrument import statsd
@ -152,6 +153,17 @@ def test_callable_validation():
pytest.raises(TypeError, c.set, "pre_fork", lambda x: True) pytest.raises(TypeError, c.set, "pre_fork", lambda x: True)
def test_reload_engine_validation():
c = config.Config()
assert c.reload_engine == "auto"
c.set('reload_engine', 'poll')
assert c.reload_engine == 'poll'
pytest.raises(ConfigError, c.set, "reload_engine", "invalid")
def test_callable_validation_for_string(): def test_callable_validation_for_string():
from os.path import isdir as testfunc from os.path import isdir as testfunc
assert config.validate_callable(-1)("os.path.isdir") == testfunc assert config.validate_callable(-1)("os.path.isdir") == testfunc