You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
147 lines
4.9 KiB
147 lines
4.9 KiB
7 years ago
|
# -*- coding: utf-8 -
|
||
|
#
|
||
|
# This file is part of gunicorn released under the MIT license.
|
||
|
# See the NOTICE for more information.
|
||
|
|
||
|
import copy
|
||
|
import os
|
||
|
import sys
|
||
|
|
||
|
try:
|
||
|
import tornado
|
||
|
except ImportError:
|
||
|
raise RuntimeError("You need tornado installed to use this worker.")
|
||
|
import tornado.web
|
||
|
import tornado.httpserver
|
||
|
from tornado.ioloop import IOLoop, PeriodicCallback
|
||
|
from tornado.wsgi import WSGIContainer
|
||
|
from gunicorn.workers.base import Worker
|
||
|
from gunicorn import __version__ as gversion
|
||
|
|
||
|
|
||
|
# `io_loop` arguments to many Tornado functions have been removed in Tornado 5.0
|
||
|
# <http://www.tornadoweb.org/en/stable/releases/v5.0.0.html#backwards-compatibility-notes>
|
||
|
IOLOOP_PARAMETER_REMOVED = tornado.version_info >= (5, 0, 0)
|
||
|
|
||
|
|
||
|
class TornadoWorker(Worker):
|
||
|
|
||
|
@classmethod
|
||
|
def setup(cls):
|
||
|
web = sys.modules.pop("tornado.web")
|
||
|
old_clear = web.RequestHandler.clear
|
||
|
|
||
|
def clear(self):
|
||
|
old_clear(self)
|
||
|
if "Gunicorn" not in self._headers["Server"]:
|
||
|
self._headers["Server"] += " (Gunicorn/%s)" % gversion
|
||
|
web.RequestHandler.clear = clear
|
||
|
sys.modules["tornado.web"] = web
|
||
|
|
||
|
def handle_exit(self, sig, frame):
|
||
|
if self.alive:
|
||
|
super(TornadoWorker, self).handle_exit(sig, frame)
|
||
|
|
||
|
def handle_request(self):
|
||
|
self.nr += 1
|
||
|
if self.alive and self.nr >= self.max_requests:
|
||
|
self.log.info("Autorestarting worker after current request.")
|
||
|
self.alive = False
|
||
|
|
||
|
def watchdog(self):
|
||
|
if self.alive:
|
||
|
self.notify()
|
||
|
|
||
|
if self.ppid != os.getppid():
|
||
|
self.log.info("Parent changed, shutting down: %s", self)
|
||
|
self.alive = False
|
||
|
|
||
|
def heartbeat(self):
|
||
|
if not self.alive:
|
||
|
if self.server_alive:
|
||
|
if hasattr(self, 'server'):
|
||
|
try:
|
||
|
self.server.stop()
|
||
|
except Exception:
|
||
|
pass
|
||
|
self.server_alive = False
|
||
|
else:
|
||
|
if not self.ioloop._callbacks:
|
||
|
self.ioloop.stop()
|
||
|
|
||
|
def run(self):
|
||
|
self.ioloop = IOLoop.instance()
|
||
|
self.alive = True
|
||
|
self.server_alive = False
|
||
|
if IOLOOP_PARAMETER_REMOVED:
|
||
|
PeriodicCallback(self.watchdog, 1000).start()
|
||
|
PeriodicCallback(self.heartbeat, 1000).start()
|
||
|
else:
|
||
|
PeriodicCallback(self.watchdog, 1000, io_loop=self.ioloop).start()
|
||
|
PeriodicCallback(self.heartbeat, 1000, io_loop=self.ioloop).start()
|
||
|
|
||
|
# Assume the app is a WSGI callable if its not an
|
||
|
# instance of tornado.web.Application or is an
|
||
|
# instance of tornado.wsgi.WSGIApplication
|
||
|
app = self.wsgi
|
||
|
if not isinstance(app, tornado.web.Application) or \
|
||
|
isinstance(app, tornado.wsgi.WSGIApplication):
|
||
|
app = WSGIContainer(app)
|
||
|
|
||
|
# Monkey-patching HTTPConnection.finish to count the
|
||
|
# number of requests being handled by Tornado. This
|
||
|
# will help gunicorn shutdown the worker if max_requests
|
||
|
# is exceeded.
|
||
|
httpserver = sys.modules["tornado.httpserver"]
|
||
|
if hasattr(httpserver, 'HTTPConnection'):
|
||
|
old_connection_finish = httpserver.HTTPConnection.finish
|
||
|
|
||
|
def finish(other):
|
||
|
self.handle_request()
|
||
|
old_connection_finish(other)
|
||
|
httpserver.HTTPConnection.finish = finish
|
||
|
sys.modules["tornado.httpserver"] = httpserver
|
||
|
|
||
|
server_class = tornado.httpserver.HTTPServer
|
||
|
else:
|
||
|
|
||
|
class _HTTPServer(tornado.httpserver.HTTPServer):
|
||
|
|
||
|
def on_close(instance, server_conn):
|
||
|
self.handle_request()
|
||
|
super(_HTTPServer, instance).on_close(server_conn)
|
||
|
|
||
|
server_class = _HTTPServer
|
||
|
|
||
|
if self.cfg.is_ssl:
|
||
|
_ssl_opt = copy.deepcopy(self.cfg.ssl_options)
|
||
|
# tornado refuses initialization if ssl_options contains following
|
||
|
# options
|
||
|
del _ssl_opt["do_handshake_on_connect"]
|
||
|
del _ssl_opt["suppress_ragged_eofs"]
|
||
|
if IOLOOP_PARAMETER_REMOVED:
|
||
|
server = server_class(app, ssl_options=_ssl_opt)
|
||
|
else:
|
||
|
server = server_class(app, io_loop=self.ioloop,
|
||
|
ssl_options=_ssl_opt)
|
||
|
else:
|
||
|
if IOLOOP_PARAMETER_REMOVED:
|
||
|
server = server_class(app)
|
||
|
else:
|
||
|
server = server_class(app, io_loop=self.ioloop)
|
||
|
|
||
|
self.server = server
|
||
|
self.server_alive = True
|
||
|
|
||
|
for s in self.sockets:
|
||
|
s.setblocking(0)
|
||
|
if hasattr(server, "add_socket"): # tornado > 2.0
|
||
|
server.add_socket(s)
|
||
|
elif hasattr(server, "_sockets"): # tornado 2.0
|
||
|
server._sockets[s.fileno()] = s
|
||
|
|
||
|
server.no_keep_alive = self.cfg.keepalive <= 0
|
||
|
server.start(num_processes=1)
|
||
|
|
||
|
self.ioloop.start()
|