From bc20bea7d9a86ebf2bd1cf62766ecc299b940d77 Mon Sep 17 00:00:00 2001 From: Mark Adams Date: Tue, 14 Feb 2017 10:35:35 -0600 Subject: [PATCH] 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 --- gunicorn/config.py | 28 ++++++++++++++++++++++++++++ gunicorn/reloader.py | 6 ++++++ gunicorn/workers/base.py | 5 +++-- tests/test_config.py | 12 ++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) 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