mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
new HUP signal handling. Rather than reexecuting the app it mimic NGINX
behavior : Configuration reload Start the new worker processes with a new configuration Gracefully shutdown the old worker processes To reload gunicorn code use USR2 signal.
This commit is contained in:
parent
645938f2b9
commit
05b6281e8d
@ -18,33 +18,46 @@ class Application(object):
|
|||||||
An application interface for configuring and loading
|
An application interface for configuring and loading
|
||||||
the various necessities for any given web framework.
|
the various necessities for any given web framework.
|
||||||
"""
|
"""
|
||||||
|
LOG_LEVELS = {
|
||||||
|
"critical": logging.CRITICAL,
|
||||||
|
"error": logging.ERROR,
|
||||||
|
"warning": logging.WARNING,
|
||||||
|
"info": logging.INFO,
|
||||||
|
"debug": logging.DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, usage=None):
|
def __init__(self, usage=None):
|
||||||
self.log = logging.getLogger(__name__)
|
self.log = logging.getLogger(__name__)
|
||||||
self.cfg = Config(usage)
|
self.cfg = Config(usage)
|
||||||
self.callable = None
|
self.callable = None
|
||||||
|
self.logger = None
|
||||||
|
|
||||||
parser = self.cfg.parser()
|
self.cfgparser = self.cfg.parser()
|
||||||
opts, args = parser.parse_args()
|
self.opts, self.args = self.cfgparser.parse_args()
|
||||||
cfg = self.init(parser, opts, args)
|
|
||||||
|
self.load_config()
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
cfg = self.init(self.cfgparser, self.opts, self.args)
|
||||||
|
|
||||||
# Load up the any app specific configuration
|
# Load up the any app specific configuration
|
||||||
if cfg:
|
if cfg:
|
||||||
for k, v in list(cfg.items()):
|
for k, v in list(cfg.items()):
|
||||||
self.cfg.set(k.lower(), v)
|
self.cfg.set(k.lower(), v)
|
||||||
|
|
||||||
# Load up the config file if its found.
|
# Load up the config file if its found.
|
||||||
if opts.config and os.path.exists(opts.config):
|
if self.opts.config and os.path.exists(self.opts.config):
|
||||||
cfg = {
|
cfg = {
|
||||||
"__builtins__": __builtins__,
|
"__builtins__": __builtins__,
|
||||||
"__name__": "__config__",
|
"__name__": "__config__",
|
||||||
"__file__": opts.config,
|
"__file__": self.opts.config,
|
||||||
"__doc__": None,
|
"__doc__": None,
|
||||||
"__package__": None
|
"__package__": None
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
execfile(opts.config, cfg, cfg)
|
execfile(self.opts.config, cfg, cfg)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
print "Failed to read config file: %s" % opts.config
|
print "Failed to read config file: %s" % self.opts.config
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -60,16 +73,23 @@ class Application(object):
|
|||||||
|
|
||||||
# Lastly, update the configuration with any command line
|
# Lastly, update the configuration with any command line
|
||||||
# settings.
|
# settings.
|
||||||
for k, v in list(opts.__dict__.items()):
|
for k, v in list(self.opts.__dict__.items()):
|
||||||
if v is None:
|
if v is None:
|
||||||
continue
|
continue
|
||||||
self.cfg.set(k.lower(), v)
|
self.cfg.set(k.lower(), v)
|
||||||
|
|
||||||
def init(self, parser, opts, args):
|
def init(self, parser, opts, args):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
self.load_config()
|
||||||
|
if self.cfg.spew:
|
||||||
|
debug.spew()
|
||||||
|
loglevel = self.LOG_LEVELS.get(self.cfg.loglevel.lower(), logging.INFO)
|
||||||
|
self.logger.setLevel(loglevel)
|
||||||
|
|
||||||
def wsgi(self):
|
def wsgi(self):
|
||||||
if self.callable is None:
|
if self.callable is None:
|
||||||
@ -90,7 +110,7 @@ class Application(object):
|
|||||||
"""\
|
"""\
|
||||||
Set the log level and choose the destination for log output.
|
Set the log level and choose the destination for log output.
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger('gunicorn')
|
self.logger = logging.getLogger('gunicorn')
|
||||||
|
|
||||||
handlers = []
|
handlers = []
|
||||||
if self.cfg.logfile != "-":
|
if self.cfg.logfile != "-":
|
||||||
@ -98,21 +118,13 @@ class Application(object):
|
|||||||
else:
|
else:
|
||||||
handlers.append(logging.StreamHandler())
|
handlers.append(logging.StreamHandler())
|
||||||
|
|
||||||
levels = {
|
loglevel = self.LOG_LEVELS.get(self.cfg.loglevel.lower(), logging.INFO)
|
||||||
"critical": logging.CRITICAL,
|
self.logger.setLevel(loglevel)
|
||||||
"error": logging.ERROR,
|
|
||||||
"warning": logging.WARNING,
|
|
||||||
"info": logging.INFO,
|
|
||||||
"debug": logging.DEBUG
|
|
||||||
}
|
|
||||||
|
|
||||||
loglevel = levels.get(self.cfg.loglevel.lower(), logging.INFO)
|
|
||||||
logger.setLevel(loglevel)
|
|
||||||
|
|
||||||
format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s"
|
format = r"%(asctime)s [%(process)d] [%(levelname)s] %(message)s"
|
||||||
datefmt = r"%Y-%m-%d %H:%M:%S"
|
datefmt = r"%Y-%m-%d %H:%M:%S"
|
||||||
for h in handlers:
|
for h in handlers:
|
||||||
h.setFormatter(logging.Formatter(format, datefmt))
|
h.setFormatter(logging.Formatter(format, datefmt))
|
||||||
logger.addHandler(h)
|
self.logger.addHandler(h)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,10 @@ from gunicorn.pidfile import Pidfile
|
|||||||
from gunicorn.sock import create_socket
|
from gunicorn.sock import create_socket
|
||||||
from gunicorn import util
|
from gunicorn import util
|
||||||
|
|
||||||
|
|
||||||
|
class HUPSignal(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class Arbiter(object):
|
class Arbiter(object):
|
||||||
"""
|
"""
|
||||||
Arbiter maintain the workers processes alive. It launches or
|
Arbiter maintain the workers processes alive. It launches or
|
||||||
@ -48,21 +52,12 @@ class Arbiter(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.setup(app)
|
||||||
self.cfg = app.cfg
|
|
||||||
|
|
||||||
if self.cfg.preload_app:
|
|
||||||
self.app.wsgi()
|
|
||||||
|
|
||||||
self.log = logging.getLogger(__name__)
|
self.log = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.address = self.cfg.address
|
|
||||||
self.num_workers = self.cfg.workers
|
|
||||||
self.debug = self.cfg.debug
|
|
||||||
self.timeout = self.cfg.timeout
|
|
||||||
self.proc_name = self.cfg.proc_name
|
|
||||||
self.worker_class = self.cfg.worker_class
|
|
||||||
|
|
||||||
self.pidfile = None
|
self.pidfile = None
|
||||||
self.worker_age = 0
|
self.worker_age = 0
|
||||||
self.reexec_pid = 0
|
self.reexec_pid = 0
|
||||||
@ -88,6 +83,19 @@ class Arbiter(object):
|
|||||||
"cwd": cwd,
|
"cwd": cwd,
|
||||||
0: sys.executable
|
0: sys.executable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def setup(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.cfg = app.cfg
|
||||||
|
self.address = self.cfg.address
|
||||||
|
self.num_workers = self.cfg.workers
|
||||||
|
self.debug = self.cfg.debug
|
||||||
|
self.timeout = self.cfg.timeout
|
||||||
|
self.proc_name = self.cfg.proc_name
|
||||||
|
self.worker_class = self.cfg.worker_class
|
||||||
|
|
||||||
|
if self.cfg.preload_app:
|
||||||
|
self.app.wsgi()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""\
|
"""\
|
||||||
@ -95,7 +103,8 @@ class Arbiter(object):
|
|||||||
"""
|
"""
|
||||||
self.pid = os.getpid()
|
self.pid = os.getpid()
|
||||||
self.init_signals()
|
self.init_signals()
|
||||||
self.LISTENER = create_socket(self.cfg)
|
if not self.LISTENER:
|
||||||
|
self.LISTENER = create_socket(self.cfg)
|
||||||
|
|
||||||
if self.cfg.pidfile is not None:
|
if self.cfg.pidfile is not None:
|
||||||
self.pidfile = Pidfile(self.cfg.pidfile)
|
self.pidfile = Pidfile(self.cfg.pidfile)
|
||||||
@ -110,7 +119,7 @@ class Arbiter(object):
|
|||||||
are queued. Child signals only wake up the master.
|
are queued. Child signals only wake up the master.
|
||||||
"""
|
"""
|
||||||
if self.PIPE:
|
if self.PIPE:
|
||||||
map(lambda p: p.close(), self.PIPE)
|
map(lambda p: os.close(p), self.PIPE)
|
||||||
self.PIPE = pair = os.pipe()
|
self.PIPE = pair = os.pipe()
|
||||||
map(util.set_non_blocking, pair)
|
map(util.set_non_blocking, pair)
|
||||||
map(util.close_on_exec, pair)
|
map(util.close_on_exec, pair)
|
||||||
@ -150,11 +159,13 @@ class Arbiter(object):
|
|||||||
continue
|
continue
|
||||||
self.log.info("Handling signal: %s" % signame)
|
self.log.info("Handling signal: %s" % signame)
|
||||||
handler()
|
handler()
|
||||||
self.wakeup()
|
self.wakeup()
|
||||||
|
except HUPSignal:
|
||||||
|
return self.reload()
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
self.halt()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
break
|
self.halt()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.info("Unhandled exception in main loop:\n%s" %
|
self.log.info("Unhandled exception in main loop:\n%s" %
|
||||||
traceback.format_exc())
|
traceback.format_exc())
|
||||||
@ -163,12 +174,6 @@ class Arbiter(object):
|
|||||||
self.pidfile.unlink()
|
self.pidfile.unlink()
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
self.stop()
|
|
||||||
self.log.info("Shutting down: %s" % self.master_name)
|
|
||||||
if self.pidfile is not None:
|
|
||||||
self.pidfile.unlink()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def handle_chld(self, sig, frame):
|
def handle_chld(self, sig, frame):
|
||||||
"SIGCHLD handling"
|
"SIGCHLD handling"
|
||||||
self.wakeup()
|
self.wakeup()
|
||||||
@ -181,8 +186,7 @@ class Arbiter(object):
|
|||||||
restart the workers and rereading the configuration.
|
restart the workers and rereading the configuration.
|
||||||
"""
|
"""
|
||||||
self.log.info("Hang up: %s" % self.master_name)
|
self.log.info("Hang up: %s" % self.master_name)
|
||||||
self.reexec()
|
raise HUPSignal
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def handle_quit(self):
|
def handle_quit(self):
|
||||||
"SIGQUIT handling"
|
"SIGQUIT handling"
|
||||||
@ -250,6 +254,14 @@ class Arbiter(object):
|
|||||||
if e.errno not in [errno.EAGAIN, errno.EINTR]:
|
if e.errno not in [errno.EAGAIN, errno.EINTR]:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def halt(self):
|
||||||
|
""" halt arbiter """
|
||||||
|
self.stop()
|
||||||
|
self.log.info("Shutting down: %s" % self.master_name)
|
||||||
|
if self.pidfile is not None:
|
||||||
|
self.pidfile.unlink()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def sleep(self):
|
def sleep(self):
|
||||||
"""\
|
"""\
|
||||||
Sleep until PIPE is readable or we timeout.
|
Sleep until PIPE is readable or we timeout.
|
||||||
@ -269,6 +281,7 @@ class Arbiter(object):
|
|||||||
raise
|
raise
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
def stop(self, graceful=True):
|
def stop(self, graceful=True):
|
||||||
"""\
|
"""\
|
||||||
@ -304,7 +317,24 @@ class Arbiter(object):
|
|||||||
os.chdir(self.START_CTX['cwd'])
|
os.chdir(self.START_CTX['cwd'])
|
||||||
self.cfg.pre_exec(self)
|
self.cfg.pre_exec(self)
|
||||||
os.execvpe(self.START_CTX[0], self.START_CTX['args'], os.environ)
|
os.execvpe(self.START_CTX[0], self.START_CTX['args'], os.environ)
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
# reload conf
|
||||||
|
self.app.reload()
|
||||||
|
|
||||||
|
# spawn new workers
|
||||||
|
for i in range(self.app.cfg.workers):
|
||||||
|
self.spawn_worker()
|
||||||
|
|
||||||
|
# unlink pidfile
|
||||||
|
if self.pidfile is not None:
|
||||||
|
self.pidfile.unlink()
|
||||||
|
|
||||||
|
self.setup(self.app)
|
||||||
|
self.manage_workers()
|
||||||
|
|
||||||
|
return self.run()
|
||||||
|
|
||||||
def murder_workers(self):
|
def murder_workers(self):
|
||||||
"""\
|
"""\
|
||||||
Kill unused/idle workers
|
Kill unused/idle workers
|
||||||
@ -361,6 +391,40 @@ class Arbiter(object):
|
|||||||
pid, age = wpid, worker.age
|
pid, age = wpid, worker.age
|
||||||
self.kill_worker(pid, signal.SIGQUIT)
|
self.kill_worker(pid, signal.SIGQUIT)
|
||||||
|
|
||||||
|
def spawn_worker(self):
|
||||||
|
self.worker_age += 1
|
||||||
|
worker = self.worker_class(self.worker_age, self.pid, self.LISTENER,
|
||||||
|
self.app, self.timeout/2.0, self.cfg)
|
||||||
|
self.cfg.pre_fork(self, worker)
|
||||||
|
pid = os.fork()
|
||||||
|
if pid != 0:
|
||||||
|
self.WORKERS[pid] = worker
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process Child
|
||||||
|
worker_pid = os.getpid()
|
||||||
|
try:
|
||||||
|
util._setproctitle("worker [%s]" % self.proc_name)
|
||||||
|
self.log.debug("Booting worker: %s (age: %s)" % (
|
||||||
|
worker_pid, self.worker_age))
|
||||||
|
self.cfg.post_fork(self, worker)
|
||||||
|
worker.init_process()
|
||||||
|
sys.exit(0)
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
self.log.exception("Exception in worker process:")
|
||||||
|
if not worker.booted:
|
||||||
|
sys.exit(self.WORKER_BOOT_ERROR)
|
||||||
|
sys.exit(-1)
|
||||||
|
finally:
|
||||||
|
self.log.info("Worker exiting (pid: %s)" % worker_pid)
|
||||||
|
try:
|
||||||
|
worker.tmp.close()
|
||||||
|
os.unlink(worker.tmpname)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
def spawn_workers(self):
|
def spawn_workers(self):
|
||||||
"""\
|
"""\
|
||||||
Spawn new workers as needed.
|
Spawn new workers as needed.
|
||||||
@ -370,38 +434,7 @@ class Arbiter(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
for i in range(self.num_workers - len(self.WORKERS.keys())):
|
for i in range(self.num_workers - len(self.WORKERS.keys())):
|
||||||
self.worker_age += 1
|
self.spawn_worker()
|
||||||
worker = self.worker_class(self.worker_age, self.pid, self.LISTENER,
|
|
||||||
self.app, self.timeout/2.0, self.cfg)
|
|
||||||
self.cfg.pre_fork(self, worker)
|
|
||||||
pid = os.fork()
|
|
||||||
if pid != 0:
|
|
||||||
self.WORKERS[pid] = worker
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Process Child
|
|
||||||
worker_pid = os.getpid()
|
|
||||||
try:
|
|
||||||
util._setproctitle("worker [%s]" % self.proc_name)
|
|
||||||
self.log.debug("Booting worker: %s (age: %s)" % (
|
|
||||||
worker_pid, self.worker_age))
|
|
||||||
self.cfg.post_fork(self, worker)
|
|
||||||
worker.init_process()
|
|
||||||
sys.exit(0)
|
|
||||||
except SystemExit:
|
|
||||||
raise
|
|
||||||
except:
|
|
||||||
self.log.exception("Exception in worker process:")
|
|
||||||
if not worker.booted:
|
|
||||||
sys.exit(self.WORKER_BOOT_ERROR)
|
|
||||||
sys.exit(-1)
|
|
||||||
finally:
|
|
||||||
self.log.info("Worker exiting (pid: %s)" % worker_pid)
|
|
||||||
try:
|
|
||||||
worker.tmp.close()
|
|
||||||
os.unlink(worker.tmpname)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def kill_workers(self, sig):
|
def kill_workers(self, sig):
|
||||||
"""\
|
"""\
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user