certbot/letsencrypt/error_handler.py

100 lines
3.7 KiB
Python
Raw Normal View History

"""Registers functions to be called if an exception or signal occurs."""
import logging
2015-09-10 22:35:44 -04:00
import os
import signal
import traceback
2015-09-10 22:35:44 -04:00
logger = logging.getLogger(__name__)
# _SIGNALS stores the signals that will be handled by the ErrorHandler. These
# signals were chosen as their default handler terminates the process and could
# potentially occur from inside Python. Signals such as SIGILL were not
# included as they could be a sign of something devious and we should terminate
# immediately.
2015-09-28 15:25:37 -04:00
_SIGNALS = ([signal.SIGTERM] if os.name == "nt" else
[signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT,
signal.SIGXCPU, signal.SIGXFSZ])
2015-09-10 22:35:44 -04:00
2015-09-22 21:24:22 -04:00
class ErrorHandler(object):
"""Registers functions to be called if an exception or signal occurs.
This class allows you to register functions that will be called when
2015-10-01 18:39:55 -04:00
an exception (excluding SystemExit) or signal is encountered. The
class works best as a context manager. For example:
with ErrorHandler(cleanup_func):
do_something()
If an exception is raised out of do_something, cleanup_func will be
called. The exception is not caught by the ErrorHandler. Similarly,
if a signal is encountered, cleanup_func is called followed by the
previously registered signal handler.
Every registered function is attempted to be run to completion
exactly once. If a registered function raises an exception, it is
logged and the next function is called. If a (different) handled
signal occurs while calling a registered function, it is attempted
to be called again by the next signal handler.
"""
2015-09-10 22:35:44 -04:00
def __init__(self, func=None):
2015-09-23 18:02:20 -04:00
self.funcs = []
2015-09-22 21:24:22 -04:00
self.prev_handlers = {}
if func is not None:
2015-09-23 18:02:20 -04:00
self.register(func)
2015-09-10 22:35:44 -04:00
def __enter__(self):
self.set_signal_handlers()
def __exit__(self, exec_type, exec_value, trace):
2015-10-01 18:39:55 -04:00
# SystemExit is ignored to properly handle forks that don't exec
2015-09-30 16:00:10 -04:00
if exec_type not in (None, SystemExit):
logger.debug("Encountered exception:\n%s", "".join(
traceback.format_exception(exec_type, exec_value, trace)))
2015-09-22 21:24:22 -04:00
self.call_registered()
2015-09-10 22:35:44 -04:00
self.reset_signal_handlers()
def register(self, func):
"""Registers func to be called if an error occurs."""
self.funcs.append(func)
2015-09-22 21:24:22 -04:00
def call_registered(self):
"""Calls all registered functions"""
logger.debug("Calling registered functions")
while self.funcs:
try:
self.funcs[-1]()
except Exception as error: # pylint: disable=broad-except
logger.error("Encountered exception during recovery")
logger.exception(error)
self.funcs.pop()
2015-09-10 22:35:44 -04:00
def set_signal_handlers(self):
2015-09-22 21:24:22 -04:00
"""Sets signal handlers for signals in _SIGNALS."""
for signum in _SIGNALS:
prev_handler = signal.getsignal(signum)
# If prev_handler is None, the handler was set outside of Python
if prev_handler is not None:
self.prev_handlers[signum] = prev_handler
signal.signal(signum, self._signal_handler)
2015-09-10 22:35:44 -04:00
def reset_signal_handlers(self):
2015-09-22 21:24:22 -04:00
"""Resets signal handlers for signals in _SIGNALS."""
for signum in self.prev_handlers:
signal.signal(signum, self.prev_handlers[signum])
self.prev_handlers.clear()
def _signal_handler(self, signum, unused_frame):
2015-09-22 21:24:22 -04:00
"""Calls registered functions and the previous signal handler.
:param int signum: number of current signal
2015-09-10 22:35:44 -04:00
2015-09-22 21:24:22 -04:00
"""
logger.debug("Singal %s encountered", signum)
2015-09-22 21:24:22 -04:00
self.call_registered()
signal.signal(signum, self.prev_handlers[signum])
2015-09-10 22:35:44 -04:00
os.kill(os.getpid(), signum)