2015-07-22 18:04:26 +00:00
|
|
|
"""
|
|
|
|
Run a Tornado HTTP service.
|
|
|
|
|
|
|
|
- :func:`.run`: calls a ``make_app`` *callable*, configures the
|
|
|
|
environment intelligently, and runs the application.
|
|
|
|
- :class:`.Runner`: encapsulates the running of the application
|
|
|
|
|
|
|
|
"""
|
2015-12-10 15:44:52 +00:00
|
|
|
import logging
|
2015-07-22 18:04:26 +00:00
|
|
|
import signal
|
2016-01-28 12:09:32 +00:00
|
|
|
import sys
|
2015-07-22 18:04:26 +00:00
|
|
|
|
|
|
|
from tornado import httpserver, ioloop
|
|
|
|
|
2015-08-28 14:57:06 +00:00
|
|
|
import sprockets.logging
|
|
|
|
|
2015-07-22 18:04:26 +00:00
|
|
|
|
|
|
|
class Runner(object):
|
|
|
|
"""
|
|
|
|
HTTP service runner.
|
|
|
|
|
|
|
|
:param tornado.web.Application application: the application to serve
|
|
|
|
|
|
|
|
This class implements the logic necessary to safely run a
|
|
|
|
Tornado HTTP service inside of a docker container.
|
|
|
|
|
|
|
|
.. rubric:: Usage Example
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
def make_app():
|
|
|
|
return web.Application(...)
|
|
|
|
|
|
|
|
def run():
|
|
|
|
server = runner.Runner(make_app())
|
|
|
|
server.start_server()
|
|
|
|
ioloop.IOLoop.instance().start()
|
|
|
|
|
|
|
|
The :meth:`.start_server` method sets up the necessary signal handling
|
|
|
|
to ensure that we have a clean shutdown in the face of signals.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, application):
|
|
|
|
self.application = application
|
|
|
|
self.logger = logging.getLogger('Runner')
|
|
|
|
self.server = None
|
|
|
|
self.shutdown_limit = 5
|
2015-09-24 18:55:13 +00:00
|
|
|
try:
|
|
|
|
self.application.runner_callbacks.setdefault('shutdown', [])
|
2016-01-27 23:24:02 +00:00
|
|
|
self.application.runner_callbacks.setdefault('before_run', [])
|
2015-09-24 18:55:13 +00:00
|
|
|
except AttributeError:
|
2016-01-27 23:24:02 +00:00
|
|
|
setattr(self.application, 'runner_callbacks', {
|
|
|
|
'shutdown': [],
|
|
|
|
'before_run': [],
|
|
|
|
})
|
2015-07-22 18:04:26 +00:00
|
|
|
|
2015-09-24 18:57:03 +00:00
|
|
|
def start_server(self, port_number, number_of_procs=0):
|
2015-07-22 18:04:26 +00:00
|
|
|
"""
|
|
|
|
Create a HTTP server and start it.
|
|
|
|
|
|
|
|
:param int port_number: the port number to bind the server to
|
2015-09-24 18:57:03 +00:00
|
|
|
:param int number_of_procs: number of processes to pass to
|
|
|
|
Tornado's ``httpserver.HTTPServer.start``.
|
2015-07-22 18:04:26 +00:00
|
|
|
|
|
|
|
If the application's ``debug`` setting is ``True``, then we are
|
|
|
|
going to run in a single-process mode; otherwise, we'll let
|
|
|
|
tornado decide how many sub-processes to spawn.
|
|
|
|
|
|
|
|
"""
|
|
|
|
signal.signal(signal.SIGTERM, self._on_signal)
|
|
|
|
signal.signal(signal.SIGINT, self._on_signal)
|
2016-02-23 16:39:52 +00:00
|
|
|
xheaders = self.application.settings.get('xheaders', False)
|
2015-07-22 18:04:26 +00:00
|
|
|
|
2016-02-23 16:39:52 +00:00
|
|
|
self.server = httpserver.HTTPServer(self.application, xheaders=xheaders)
|
2015-07-22 18:04:26 +00:00
|
|
|
if self.application.settings.get('debug', False):
|
|
|
|
self.logger.info('starting 1 process on port %d', port_number)
|
|
|
|
self.server.listen(port_number)
|
|
|
|
else:
|
2015-08-28 14:57:06 +00:00
|
|
|
self.application.settings.setdefault(
|
|
|
|
'log_function', sprockets.logging.tornado_log_function)
|
2015-07-22 18:04:26 +00:00
|
|
|
self.logger.info('starting processes on port %d', port_number)
|
|
|
|
self.server.bind(port_number)
|
2015-09-24 18:57:03 +00:00
|
|
|
self.server.start(number_of_procs)
|
2015-07-22 18:04:26 +00:00
|
|
|
|
2015-09-24 18:57:03 +00:00
|
|
|
def run(self, port_number, number_of_procs=0):
|
2015-07-22 18:04:26 +00:00
|
|
|
"""
|
|
|
|
Create the server and run the IOLoop.
|
|
|
|
|
|
|
|
:param int port_number: the port number to bind the server to
|
2015-09-24 18:57:03 +00:00
|
|
|
:param int number_of_procs: number of processes to pass to
|
|
|
|
Tornado's ``httpserver.HTTPServer.start``.
|
2015-07-22 18:04:26 +00:00
|
|
|
|
|
|
|
If the application's ``debug`` setting is ``True``, then we are
|
|
|
|
going to run in a single-process mode; otherwise, we'll let
|
2016-01-28 12:09:32 +00:00
|
|
|
tornado decide how many sub-processes to spawn. In any case, the
|
|
|
|
applications *before_run* callbacks are invoked. If a callback
|
|
|
|
raises an exception, then the application is terminated by calling
|
|
|
|
:func:`sys.exit`.
|
2015-07-22 18:04:26 +00:00
|
|
|
|
|
|
|
"""
|
2015-09-24 18:57:03 +00:00
|
|
|
self.start_server(port_number, number_of_procs)
|
2016-02-15 18:01:16 +00:00
|
|
|
iol = ioloop.IOLoop.instance()
|
2016-01-27 23:24:02 +00:00
|
|
|
for callback in self.application.runner_callbacks['before_run']:
|
|
|
|
try:
|
|
|
|
callback(self.application, iol)
|
|
|
|
except Exception:
|
|
|
|
self.logger.error('before_run callback %r cancelled start',
|
|
|
|
callback, exc_info=1)
|
|
|
|
self._shutdown()
|
2016-01-28 12:09:32 +00:00
|
|
|
sys.exit(70)
|
2016-01-27 23:24:02 +00:00
|
|
|
|
|
|
|
iol.start()
|
2015-07-22 18:04:26 +00:00
|
|
|
|
|
|
|
def _on_signal(self, signo, frame):
|
|
|
|
self.logger.info('signal %s received, stopping', signo)
|
|
|
|
ioloop.IOLoop.instance().add_callback_from_signal(self._shutdown)
|
|
|
|
|
|
|
|
def _shutdown(self):
|
2015-09-24 18:55:13 +00:00
|
|
|
for callback in self.application.runner_callbacks['shutdown']:
|
|
|
|
try:
|
|
|
|
callback(self.application)
|
|
|
|
except Exception:
|
|
|
|
self.logger.warning('shutdown callback %r raised an exception',
|
|
|
|
callback, exc_info=1)
|
2015-07-22 18:04:26 +00:00
|
|
|
|
2015-09-24 18:55:13 +00:00
|
|
|
self.server.stop()
|
2015-07-22 18:04:26 +00:00
|
|
|
iol = ioloop.IOLoop.instance()
|
|
|
|
deadline = iol.time() + self.shutdown_limit
|
|
|
|
|
|
|
|
def maybe_stop():
|
|
|
|
now = iol.time()
|
|
|
|
if now < deadline and (iol._callbacks or iol._timeouts):
|
|
|
|
return iol.add_timeout(now + 1, maybe_stop)
|
|
|
|
iol.stop()
|
|
|
|
self.logger.info('stopped')
|
|
|
|
|
|
|
|
self.logger.info('stopping within %s seconds', self.shutdown_limit)
|
|
|
|
maybe_stop()
|