mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 15:26:47 +00:00
further work on Issue #77
This commit is contained in:
parent
15eec25240
commit
53817efffb
@ -34,7 +34,9 @@ Feature Enhancements:
|
||||
* :issue:`76` - Add app.close() functionality including a
|
||||
cement_on_close_hook() allowing plugins/extensions/etc to be able to
|
||||
cleanup on application exit.
|
||||
* :issue:`77` - Add signal handler for SIGINT/SIGTERM/Etc
|
||||
* :issue:`77` - Added default signal handler for SIGINT/SIGTERM as well as
|
||||
the cement_signal_hook which is called when any catch_signals are
|
||||
encountered.
|
||||
|
||||
Incompatible Changes:
|
||||
|
||||
|
||||
@ -87,8 +87,7 @@ Cement2 Core Modules
|
||||
-----------------------
|
||||
|
||||
.. automodule:: cement2.core.log
|
||||
:members:
|
||||
|
||||
:members:
|
||||
|
||||
.. _cement2.core.output:
|
||||
|
||||
@ -98,7 +97,6 @@ Cement2 Core Modules
|
||||
.. automodule:: cement2.core.output
|
||||
:members:
|
||||
|
||||
|
||||
.. _cement2.core.plugin:
|
||||
|
||||
:mod:`cement2.core.plugin`
|
||||
|
||||
@ -18,4 +18,5 @@ them in your applications.
|
||||
dev/controllers
|
||||
dev/hooks
|
||||
dev/plugins
|
||||
dev/signal_handling
|
||||
dev/cleanup
|
||||
|
||||
@ -201,8 +201,8 @@ Cement has a number of hooks that tie into the framework.
|
||||
cement_pre_setup_hook
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Run before CementApp.setup() is called. The application object object is
|
||||
passed as an argument.
|
||||
Run before CementApp.setup() is called. The application object is
|
||||
passed as an argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -261,10 +261,36 @@ passed as an argument.
|
||||
@hook.register(name='cement_post_run_hook')
|
||||
def my_post_run_hook(app):
|
||||
# Do something after application run() is called.
|
||||
pass
|
||||
return
|
||||
|
||||
cement_on_close_hook
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Run when app.close() is called. This hook should be used by plugins and
|
||||
extensions to do any 'cleanup' at the end of program execution.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement2.core import hook
|
||||
|
||||
@hook.register(name='cement_on_close_hook')
|
||||
def my_cleanup_hook(app):
|
||||
# Do something when the application close() is called.
|
||||
return
|
||||
|
||||
cement_signal_hook
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Run when signal handling is enabled, and the defined signal handler callback
|
||||
is executed. This hook should be used by the application, plugins, and
|
||||
extensions to perform any actions when a specific signal is caught.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from cement2.core import hook
|
||||
|
||||
@hook.register(name='cement_signal_hook')
|
||||
def my_signal_hook(signum, frame):
|
||||
# do something with signum/frame
|
||||
return
|
||||
|
||||
184
doc/source/dev/signal_handling.rst
Normal file
184
doc/source/dev/signal_handling.rst
Normal file
@ -0,0 +1,184 @@
|
||||
Signal Handling
|
||||
===============
|
||||
|
||||
Python provides the `Signal <http://docs.python.org/library/signal.html>`_
|
||||
library allowing developers to catch Unix signals and set handlers for
|
||||
asynchronous events. For example, the 'SIGTERM' (Terminate) signal is
|
||||
received when issuing a 'kill' command for a given Unix process. Via the
|
||||
signal library, we can set a handler (function) callback that will be executed
|
||||
when that signal is received. Some signals however can not be handled/caught,
|
||||
such as the SIGKILL signal (kill -9). Please refer to the
|
||||
`Signal <http://docs.python.org/library/signal.html>`_ library documentation
|
||||
for a full understanding of its use and capabilities.
|
||||
|
||||
A caveat when setting a signal handler is that only one handler can be defined
|
||||
for a given signal. Therefore, all handling must be done from a single
|
||||
callback function. This is a slight roadblock for applications built on
|
||||
Cement in that many pieces of the framework are broken out into independent
|
||||
extensions as well as applications that have 3rd party plugins. The trouble
|
||||
happens when the application, plugins, and framework extensions all need to
|
||||
perform some action when a signal is caught. This section outlines the
|
||||
recommended way of handling signals with Cement versus manually setting signal
|
||||
handlers that may.
|
||||
|
||||
*Important Note*
|
||||
|
||||
It is important to note that it is not necessary to use the Cement mechanisms
|
||||
for signal handling, what-so-ever. That said, the primary concern of the
|
||||
framework is that app.close() is called no matter what the situation.
|
||||
Therefore, if you decide to disable signal handling all together you *must*
|
||||
ensure that you at the very least catch signal.SIGTERM and signal.SIGINT with
|
||||
the ability to call app.close(). You will likely find that it is more
|
||||
complex than you might think. The reason we put these mechanisms in place is
|
||||
primarily that we found it was the best way to a) handle a signal, and b) have
|
||||
access to our 'app' object in order to be able to call 'app.close()' when a
|
||||
process is terminated.
|
||||
|
||||
Signals Caught by Default
|
||||
-------------------------
|
||||
|
||||
By default Cement catches the signals SIGTERM and SIGINT. When these signals
|
||||
are caught, Cement raises the exception 'CementSignalError(signum, frame)'
|
||||
where 'signum' and 'frame' are the parameters passed to the signal handler.
|
||||
By raising an exception, we are able to pass runtime back to our applications
|
||||
main process (within a try/except block) and maintain the ability to access
|
||||
our 'application' object without using global objects.
|
||||
|
||||
A basic application using default handling might look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement2.core import foundation, exc
|
||||
|
||||
app = foundation.lay_cement('myapp')
|
||||
app.setup()
|
||||
|
||||
try:
|
||||
app.run()
|
||||
except exc.CementSignalError as e:
|
||||
# do something with e.signum or e.frame (passed from signal library)
|
||||
|
||||
if e.signum == signal.SIGTERM:
|
||||
print("Caught signal SIGTERM...")
|
||||
# do something to handle signal here
|
||||
elif e.signum == signal.SIGINT:
|
||||
print("Caught signal SIGINT...")
|
||||
# do something to handle signal here
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
As you can see, this provides a very simple means of handling the most common
|
||||
signals allowing us to still call app.close() after handling the signal. This
|
||||
is extremely important as 'app.close()' is where the 'cement_on_close_hook' is
|
||||
called, allowing the framework/application/extensions/plugins to all perform
|
||||
any cleanup actions they may need.
|
||||
|
||||
Using The Signal Hook
|
||||
---------------------
|
||||
|
||||
An alternative way of adding multiple callbacks to a signal handler is by
|
||||
using the cement_signal_hook. This hook is called anytime a handled signal
|
||||
is encountered.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement2.core import foundation, exc, hook
|
||||
|
||||
app = foundation.lay_cement('myapp')
|
||||
app.setup()
|
||||
|
||||
@hook.register(name='cement_signal_hook')
|
||||
def my_signal_handler(signum, frame):
|
||||
# do something with signum/frame
|
||||
|
||||
if signum == signal.SIGTERM:
|
||||
print("Caught signal SIGTERM...")
|
||||
# do something to handle signal here
|
||||
elif signum == signal.SIGINT:
|
||||
print("Caught signal SIGINT...")
|
||||
# do something to handle signal here
|
||||
|
||||
try:
|
||||
app.run()
|
||||
except exc.CementSignalError as e:
|
||||
pass
|
||||
finally:
|
||||
app.close()
|
||||
|
||||
|
||||
The key thing to note here is that the main application itself handles the
|
||||
exc.CementSignalError exception, where as using the cement_signal_hook is
|
||||
useful for plugins and extensions to be able to tie into the signal handling
|
||||
outside of the main application. Both serve the same purpose however the
|
||||
application object is not available (passed to) the cement_signal_hook which
|
||||
limits what can be done within the callback function. For this reason
|
||||
any extensions or plugins should use the cement_on_close_hook as much as
|
||||
possible as it is always run when app.close() is called and receives the
|
||||
app object as one of its parameters.
|
||||
|
||||
|
||||
Configuring Which Signals To Catch
|
||||
----------------------------------
|
||||
|
||||
You can define other signals to catch by passing a list of 'catch_signals' to
|
||||
foundation.lay_cement():
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement2.core import foundation, exc
|
||||
|
||||
SIGNALS = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]
|
||||
|
||||
app = foundation.lay_cement('myapp', catch_signals=SIGNALS)
|
||||
...
|
||||
|
||||
What happens is, Cement iterates over the catch_signals list and adds a
|
||||
generic handler function (the same) for each signal. Because the handler
|
||||
calls the cement_signal_hook, and then raises an exception which both pass the
|
||||
'signum' and 'frame' parameters, you are able to handle the logic elsewhere
|
||||
rather than assigning a unique callback function for every signal.
|
||||
|
||||
What If I Don't Like Your Signal Handler Callback?
|
||||
--------------------------------------------------
|
||||
|
||||
If you want more control over what happens when a signal is caught, you are
|
||||
more than welcome to override the default signal handler callback. That said,
|
||||
please be kind and be sure to atleast run the cement_signal_hook within your
|
||||
callback.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement2.core import foundation, exc, hook
|
||||
|
||||
SIGNALS = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]
|
||||
|
||||
def my_signal_handler(signum, frame):
|
||||
# do something with signum/frame
|
||||
print 'Caught signal %s' % signum
|
||||
|
||||
# execute the cement_signal_hook
|
||||
for res in hook.run('cement_signal_hook', signum, frame):
|
||||
pass
|
||||
|
||||
app = foundation.lay_cement('myapp',
|
||||
catch_signals=SIGNALS,
|
||||
signal_handler=my_signal_handler)
|
||||
...
|
||||
|
||||
|
||||
This Is Stupid, and UnPythonic - How Do I Disable It?
|
||||
-----------------------------------------------------
|
||||
|
||||
To each their own. If you simply do not want any kind of signal handling
|
||||
performed, just set 'catch_signals=None'.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import signal
|
||||
from cement2.core import foundation, exc
|
||||
|
||||
app = foundation.lay_cement('myapp', catch_signals=None)
|
||||
@ -20,8 +20,12 @@ def cement_signal_handler(signum, frame):
|
||||
|
||||
"""
|
||||
Log.debug('Caught signal %s' % signum)
|
||||
raise exc.CementSignalError(signum, frame)
|
||||
|
||||
for res in hook.run('cement_signal_hook', signum, frame):
|
||||
pass
|
||||
|
||||
raise exc.CementSignalError(signum, frame)
|
||||
|
||||
class CementApp(object):
|
||||
"""
|
||||
The CementApp is the primary application class used and returned by
|
||||
@ -44,6 +48,10 @@ class CementApp(object):
|
||||
List of signals to catch, and raise exc.CementSignalError for.
|
||||
Default: [signals.SIGTERM, signals.SIGINT]
|
||||
|
||||
signal_handler
|
||||
Func to handle any caught signals.
|
||||
Default: cement.core.foundation.cement_signal_handler
|
||||
|
||||
config_handler
|
||||
An instantiated config handler object.
|
||||
|
||||
@ -73,7 +81,8 @@ class CementApp(object):
|
||||
self.argv = kw.get('argv', sys.argv[1:])
|
||||
self.catch_signals = kw.get('catch_signals',
|
||||
[signal.SIGTERM, signal.SIGINT])
|
||||
|
||||
self.signal_handler = kw.get('signal_handler', cement_signal_handler)
|
||||
|
||||
# default all handlers to None
|
||||
self.ext = None
|
||||
self.config = None
|
||||
@ -267,9 +276,13 @@ class CementApp(object):
|
||||
self._setup_output_handler()
|
||||
|
||||
def _setup_signals(self):
|
||||
if not self.catch_signals:
|
||||
Log.debug("catch_signals=None... not handling any signals")
|
||||
return
|
||||
|
||||
for signum in self.catch_signals:
|
||||
Log.debug("adding signal handler for signal %s" % signum)
|
||||
signal.signal(signum, cement_signal_handler)
|
||||
signal.signal(signum, self.signal_handler)
|
||||
|
||||
def _setup_extension_handler(self):
|
||||
Log.debug("setting up %s.extension handler" % self.name)
|
||||
@ -382,15 +395,15 @@ class CementApp(object):
|
||||
|
||||
def lay_cement(name, klass=CementApp, *args, **kw):
|
||||
"""
|
||||
Initialize the framework. All *args, and **kwargs are passed to the
|
||||
Initialize the framework. All args, and kwargs are passed to the
|
||||
klass() object.
|
||||
|
||||
Required Arguments:
|
||||
|
||||
name
|
||||
The name of the application.
|
||||
|
||||
|
||||
|
||||
|
||||
Optional Keyword Arguments:
|
||||
|
||||
klass
|
||||
@ -439,6 +452,7 @@ def lay_cement(name, klass=CementApp, *args, **kw):
|
||||
hook.define('cement_pre_run_hook')
|
||||
hook.define('cement_post_run_hook')
|
||||
hook.define('cement_on_close_hook')
|
||||
hook.define('cement_signal_hook')
|
||||
|
||||
# define and register handlers
|
||||
handler.define(extension.IExtension)
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
"""Cement core log module."""
|
||||
"""
|
||||
Cement core log module.
|
||||
|
||||
"""
|
||||
|
||||
from cement2.core import exc, backend, interface
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user