From d9b7e32d340e3ee00736e7925318af7d0b8d5910 Mon Sep 17 00:00:00 2001 From: benoitc Date: Sun, 1 Jun 2014 11:19:32 +0200 Subject: [PATCH] try to log what happened in the worker after a timeout. fix #518 --- examples/example_config.py | 3 +++ examples/timeout.py | 21 +++++++++++++++++++++ gunicorn/arbiter.py | 8 ++++++-- gunicorn/config.py | 20 ++++++++++++++++++++ gunicorn/workers/base.py | 10 +++++++++- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 examples/timeout.py diff --git a/examples/example_config.py b/examples/example_config.py index b3901a09..c939b851 100644 --- a/examples/example_config.py +++ b/examples/example_config.py @@ -217,3 +217,6 @@ def worker_int(worker): if line: code.append(" %s" % (line.strip())) worker.log.debug("\n".join(code)) + +def worker_abort(worker): + worker.log.info("worker received SIGABRT signal") diff --git a/examples/timeout.py b/examples/timeout.py new file mode 100644 index 00000000..cf268c35 --- /dev/null +++ b/examples/timeout.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 - +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +import sys +import time + + +def app(environ, start_response): + """Application which cooperatively pauses 10 seconds before responding""" + data = b'Hello, World!\n' + status = '200 OK' + response_headers = [ + ('Content-type','text/plain'), + ('Content-Length', str(len(data))) ] + sys.stdout.write('request will timeout') + sys.stdout.flush() + time.sleep(35) + start_response(status, response_headers) + return iter([data]) diff --git a/gunicorn/arbiter.py b/gunicorn/arbiter.py index 3b4b05ab..77002653 100644 --- a/gunicorn/arbiter.py +++ b/gunicorn/arbiter.py @@ -433,8 +433,12 @@ class Arbiter(object): except ValueError: continue - self.log.critical("WORKER TIMEOUT (pid:%s)", pid) - self.kill_worker(pid, signal.SIGKILL) + if not worker.aborted: + self.log.critical("WORKER TIMEOUT (pid:%s)", pid) + worker.aborted = True + self.kill_worker(pid, signal.SIGABRT) + else: + self.kill_worker(pid, signal.SIGKILL) def reap_workers(self): """\ diff --git a/gunicorn/config.py b/gunicorn/config.py index 895049db..17fef369 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -1336,6 +1336,26 @@ class WorkerInt(Setting): """ +class WorkerAbort(Setting): + name = "worker_abort" + section = "Server Hooks" + validator = validate_callable(1) + type = six.callable + + def worker_abort(worker): + pass + + default = staticmethod(worker_abort) + desc = """\ + Called when a worker received the SIGABRT signal. + + This call generally happen on timeout. + + The callable needs to accept one instance variable for the initialized + Worker. + """ + + class PreExec(Setting): name = "pre_exec" section = "Server Hooks" diff --git a/gunicorn/workers/base.py b/gunicorn/workers/base.py index 5be83efd..99c68c8f 100644 --- a/gunicorn/workers/base.py +++ b/gunicorn/workers/base.py @@ -23,7 +23,7 @@ from gunicorn.six import MAXSIZE class Worker(object): SIGNALS = [getattr(signal, "SIG%s" % x) \ - for x in "HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()] + for x in "ABRT HUP QUIT INT TERM USR1 USR2 WINCH CHLD".split()] PIPE = [] @@ -40,6 +40,7 @@ class Worker(object): self.timeout = timeout self.cfg = cfg self.booted = False + self.aborted = False self.nr = 0 self.max_requests = cfg.max_requests or MAXSIZE @@ -127,6 +128,8 @@ class Worker(object): signal.signal(signal.SIGINT, self.handle_quit) signal.signal(signal.SIGWINCH, self.handle_winch) signal.signal(signal.SIGUSR1, self.handle_usr1) + signal.signal(signal.SIGABRT, self.handle_abort) + # Don't let SIGQUIT and SIGUSR1 disturb active requests # by interrupting system calls if hasattr(signal, 'siginterrupt'): # python >= 2.6 @@ -145,6 +148,11 @@ class Worker(object): self.alive = False sys.exit(0) + def handle_abort(self, sig, frame): + self.alive = False + self.cfg.worker_abort(self) + sys.exit(1) + def handle_error(self, req, client, addr, exc): request_start = datetime.now() addr = addr or ('', -1) # unix socket case