Add support for `before_run` callbacks.

This commit is contained in:
Dave Shawley 2016-01-27 18:24:02 -05:00
parent d8253fcef2
commit a1ba8874f6
5 changed files with 60 additions and 13 deletions

View file

@ -10,9 +10,22 @@ Application Callbacks
Starting with version 0.4.0, :func:`sprockets.http.run` augments the
:class:`tornado.web.Application` instance with a new attribute named
``runner_callbacks`` which is a dictionary of lists of functions to
call when specific events occur. The only supported event is
**shutdown**. When the application receives a stop signal, it will
run each of the callbacks before terminating the application instance.
call when specific events occur. The following events are supported:
:before_run:
This set of callbacks is invoked after Tornado forks sub-processes
(based on the ``number_of_procs`` setting) and before
:meth:`~tornado.ioloop.IOLoop.start` is called. Callbacks can
safely access the :class:`~tornado.ioloop.IOLoop` without causing
the :meth:`~tornado.ioloop.IOLoop.start` method to explode.
If any callback raises an exception, then the application is
terminated **before** the IOLoop is started.
:shutdown:
When the application receives a stop signal, it will run each of the
callbacks before terminating the application instance. Exceptions
raised by the callbacks are simply logged.
See :func:`sprockets.http.run` for a detailed description of how to
install the runner callbacks.

View file

@ -3,6 +3,10 @@
Release History
===============
`Next Release`_
---------------
- Add support for the ``before_run`` callback set.
`1.0.2`_ (10 Dec 2015)
----------------------
- Add ``log_config`` parameter to ``sprockets.http.run``

View file

@ -48,17 +48,20 @@ def run(create_application, settings=None, log_config=None):
key, then the application will be configured to run this many processes
unless in *debug* mode. This is passed to ``HTTPServer.start``.
.. rubric:: application.runner_callbacks['shutdown']
.. rubric:: application.runner_callbacks
The ``runner_callbacks`` attribute is a :class:`dict` of lists
of functions to call when an event occurs. The *shutdown* key
contains functions that are invoked when a stop signal is
received *before* the IOLoop is stopped.
of functions to call when an event occurs. This attribute will be
created **AFTER** `create_application` is called and **BEFORE** this
function returns. If the attribute exists on the instance returned
from `create_application` , then it will be used as-is.
This attribute will be created **AFTER** `create_application` is
called and **BEFORE** this function returns. If the attribute
exists on the instance returned from `create_application` , then
it will be used as-is.
The *before_run* key contains functions that are invoked after
sub-processes are forked (if necessary) and before the IOLoop is
started.
The *shutdown* key contains functions that are invoked when a stop
signal is received *before* the IOLoop is stopped.
"""
from . import runner

View file

@ -47,8 +47,12 @@ class Runner(object):
self.shutdown_limit = 5
try:
self.application.runner_callbacks.setdefault('shutdown', [])
self.application.runner_callbacks.setdefault('before_run', [])
except AttributeError:
setattr(self.application, 'runner_callbacks', {'shutdown': []})
setattr(self.application, 'runner_callbacks', {
'shutdown': [],
'before_run': [],
})
def start_server(self, port_number, number_of_procs=0):
"""
@ -90,8 +94,18 @@ class Runner(object):
tornado decide how many sub-processes to spawn.
"""
iol = ioloop.IOLoop.instance()
self.start_server(port_number, number_of_procs)
ioloop.IOLoop.instance().start()
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()
return
iol.start()
def _on_signal(self, signo, frame):
self.logger.info('signal %s received, stopping', signo)

View file

@ -293,10 +293,12 @@ class CallbackTests(unittest.TestCase):
super(CallbackTests, self).setUp()
self.application = mock.Mock()
self.shutdown_callback = mock.Mock()
self.before_run_callback = mock.Mock()
def make_application(self, **settings):
self.application.settings = settings.copy()
self.application.runner_callbacks = {
'before_run': [self.before_run_callback],
'shutdown': [self.shutdown_callback],
}
return self.application
@ -311,3 +313,14 @@ class CallbackTests(unittest.TestCase):
runner.run(8080)
runner._shutdown()
self.shutdown_callback.assert_called_once_with(self.application)
def test_that_before_run_callback_invoked(self):
iol = mock.Mock(_callbacks=[], _timeouts=[])
iol.time.side_effect = time.time
with mock.patch('sprockets.http.runner.ioloop') as ioloop:
ioloop.IOLoop.instance.return_value = iol
with mock.patch('sprockets.http.runner.httpserver'):
runner = sprockets.http.runner.Runner(self.make_application())
runner.run(8080)
self.before_run_callback.assert_called_once_with(
self.application, iol)