diff --git a/gunicorn/config.py b/gunicorn/config.py index 6c24aaa1..37e022ca 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -23,6 +23,7 @@ import shlex from gunicorn import __version__ from gunicorn import _compat from gunicorn.errors import ConfigError +from gunicorn.reloader import reloader_engines from gunicorn import six from gunicorn import util @@ -496,6 +497,13 @@ def validate_hostport(val): 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(): config_path = os.path.join(os.path.abspath(os.getcwd()), '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): name = "spew" section = "Debugging" diff --git a/gunicorn/reloader.py b/gunicorn/reloader.py index 86f8f88f..e8a00675 100644 --- a/gunicorn/reloader.py +++ b/gunicorn/reloader.py @@ -115,3 +115,9 @@ if has_inotify: preferred_reloader = InotifyReloader if has_inotify else Reloader + +reloader_engines = { + 'auto': preferred_reloader, + 'poll': Reloader, + 'inotify': InotifyReloader, +} diff --git a/gunicorn/workers/base.py b/gunicorn/workers/base.py index d3bdf742..92df19d4 100644 --- a/gunicorn/workers/base.py +++ b/gunicorn/workers/base.py @@ -15,7 +15,7 @@ import traceback from gunicorn import six from gunicorn import util from gunicorn.workers.workertmp import WorkerTmp -from gunicorn.reloader import preferred_reloader +from gunicorn.reloader import reloader_engines from gunicorn.http.errors import ( InvalidHeader, InvalidHeaderName, InvalidRequestLine, InvalidRequestMethod, InvalidHTTPVersion, LimitRequestLine, LimitRequestHeaders, @@ -119,7 +119,8 @@ class Worker(object): time.sleep(0.1) 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.load_wsgi() diff --git a/tests/test_config.py b/tests/test_config.py index 28554184..75f4962b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,6 +10,7 @@ import pytest from gunicorn import config from gunicorn.app.base import Application +from gunicorn.errors import ConfigError from gunicorn.workers.sync import SyncWorker from gunicorn import glogging from gunicorn.instrument import statsd @@ -152,6 +153,17 @@ def test_callable_validation(): 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(): from os.path import isdir as testfunc assert config.validate_callable(-1)("os.path.isdir") == testfunc