
This code is currently shared by tooz and taskflow and it seems like a good candiate to be used and shared by others as well (and excutils seems like an obvious home for it). Change-Id: I7e7c094085d6d5b5e32ec40201e260f032d0fdef
151 lines
5.9 KiB
Python
151 lines
5.9 KiB
Python
# Copyright 2011 OpenStack Foundation.
|
|
# Copyright 2012, Red Hat, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Exception related utilities.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
import six
|
|
|
|
from oslo_utils._i18n import _LE
|
|
|
|
|
|
def raise_with_cause(exc_cls, message, *args, **kwargs):
|
|
"""Helper to raise + chain exceptions (when able) and associate a *cause*.
|
|
|
|
NOTE(harlowja): Since in py3.x exceptions can be chained (due to
|
|
:pep:`3134`) we should try to raise the desired exception with the given
|
|
*cause* (or extract a *cause* from the current stack if able) so that the
|
|
exception formats nicely in old and new versions of python. Since py2.x
|
|
does **not** support exception chaining (or formatting) the exception
|
|
class provided should take a ``cause`` keyword argument (which it may
|
|
discard if it wants) to its constructor which can then be
|
|
inspected/retained on py2.x to get *similar* information as would be
|
|
automatically included/obtainable in py3.x.
|
|
|
|
:param exc_cls: the exception class to raise.
|
|
:param message: the text/str message that will be passed to
|
|
the exceptions constructor as its first positional
|
|
argument.
|
|
:param args: any additional positional arguments to pass to the
|
|
exceptions constructor.
|
|
:param kwargs: any additional keyword arguments to pass to the
|
|
exceptions constructor.
|
|
"""
|
|
if 'cause' not in kwargs:
|
|
exc_type, exc, exc_tb = sys.exc_info()
|
|
try:
|
|
if exc is not None:
|
|
kwargs['cause'] = exc
|
|
finally:
|
|
# Leave no references around (especially with regards to
|
|
# tracebacks and any variables that it retains internally).
|
|
del(exc_type, exc, exc_tb)
|
|
six.raise_from(exc_cls(message, *args, **kwargs), kwargs.get('cause'))
|
|
|
|
|
|
class save_and_reraise_exception(object):
|
|
"""Save current exception, run some code and then re-raise.
|
|
|
|
In some cases the exception context can be cleared, resulting in None
|
|
being attempted to be re-raised after an exception handler is run. This
|
|
can happen when eventlet switches greenthreads or when running an
|
|
exception handler, code raises and catches an exception. In both
|
|
cases the exception context will be cleared.
|
|
|
|
To work around this, we save the exception state, run handler code, and
|
|
then re-raise the original exception. If another exception occurs, the
|
|
saved exception is logged and the new exception is re-raised.
|
|
|
|
In some cases the caller may not want to re-raise the exception, and
|
|
for those circumstances this context provides a reraise flag that
|
|
can be used to suppress the exception. For example::
|
|
|
|
except Exception:
|
|
with save_and_reraise_exception() as ctxt:
|
|
decide_if_need_reraise()
|
|
if not should_be_reraised:
|
|
ctxt.reraise = False
|
|
|
|
If another exception occurs and reraise flag is False,
|
|
the saved exception will not be logged.
|
|
|
|
If the caller wants to raise new exception during exception handling
|
|
he/she sets reraise to False initially with an ability to set it back to
|
|
True if needed::
|
|
|
|
except Exception:
|
|
with save_and_reraise_exception(reraise=False) as ctxt:
|
|
[if statements to determine whether to raise a new exception]
|
|
# Not raising a new exception, so reraise
|
|
ctxt.reraise = True
|
|
"""
|
|
def __init__(self, reraise=True, logger=None):
|
|
self.reraise = reraise
|
|
if logger is None:
|
|
logger = logging.getLogger()
|
|
self.logger = logger
|
|
|
|
def __enter__(self):
|
|
self.type_, self.value, self.tb, = sys.exc_info()
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
if exc_type is not None:
|
|
if self.reraise:
|
|
self.logger.error(_LE('Original exception being dropped: %s'),
|
|
traceback.format_exception(self.type_,
|
|
self.value,
|
|
self.tb))
|
|
return False
|
|
if self.reraise:
|
|
six.reraise(self.type_, self.value, self.tb)
|
|
|
|
|
|
def forever_retry_uncaught_exceptions(infunc):
|
|
def inner_func(*args, **kwargs):
|
|
last_log_time = 0
|
|
last_exc_message = None
|
|
exc_count = 0
|
|
while True:
|
|
try:
|
|
return infunc(*args, **kwargs)
|
|
except Exception as exc:
|
|
this_exc_message = six.u(str(exc))
|
|
if this_exc_message == last_exc_message:
|
|
exc_count += 1
|
|
else:
|
|
exc_count = 1
|
|
# Do not log any more frequently than once a minute unless
|
|
# the exception message changes
|
|
cur_time = int(time.time())
|
|
if (cur_time - last_log_time > 60 or
|
|
this_exc_message != last_exc_message):
|
|
logging.exception(
|
|
_LE('Unexpected exception occurred %d time(s)... '
|
|
'retrying.') % exc_count)
|
|
last_log_time = cur_time
|
|
last_exc_message = this_exc_message
|
|
exc_count = 0
|
|
# This should be a very rare event. In case it isn't, do
|
|
# a sleep.
|
|
time.sleep(1)
|
|
return inner_func
|