Merge "Allow 'forever_retry_uncaught_exceptions' to take in different defaults"

This commit is contained in:
Jenkins 2015-11-20 19:27:40 +00:00 committed by Gerrit Code Review
commit 443740c657
1 changed files with 54 additions and 35 deletions

View File

@ -204,42 +204,61 @@ class save_and_reraise_exception(object):
six.reraise(self.type_, self.value, self.tb)
def forever_retry_uncaught_exceptions(infunc):
def forever_retry_uncaught_exceptions(*args, **kwargs):
"""Decorates provided function with infinite retry behavior.
If the original function fails it may trigger a logging output to
be written. The function retry delay is **always** one second.
The function retry delay is **always** one second unless
keyword argument ``retry_delay`` is passed that defines a value different
than 1.0 (less than zero values are automatically changed to be 0.0).
If repeated exceptions with the same message occur, logging will only
output/get triggered for those equivalent messages every 60.0
seconds, this can be altered by keyword argument ``same_log_delay`` to
be a value different than 60.0 seconds (exceptions that change the
message are always logged no matter what this delay is set to). As in
the ``retry_delay`` case if this is less than zero, it will be
automatically changed to be 0.0.
"""
def inner_func(*args, **kwargs):
last_exc_message = None
same_failure_count = 0
watch = timeutils.StopWatch(duration=60)
while True:
try:
return infunc(*args, **kwargs)
except Exception as exc:
this_exc_message = encodeutils.exception_to_unicode(exc)
if this_exc_message == last_exc_message:
same_failure_count += 1
else:
same_failure_count = 1
if this_exc_message != last_exc_message or watch.expired():
# The watch has expired or the exception message
# changed, so time to log it again...
logging.exception(
_LE('Unexpected exception occurred %d time(s)... '
'retrying.') % same_failure_count)
if not watch.has_started():
watch.start()
def decorator(infunc):
retry_delay = max(0.0, float(kwargs.get('retry_delay', 1.0)))
same_log_delay = max(0.0, float(kwargs.get('same_log_delay', 60.0)))
@six.wraps(infunc)
def wrapper(*args, **kwargs):
last_exc_message = None
same_failure_count = 0
watch = timeutils.StopWatch(duration=same_log_delay)
while True:
try:
return infunc(*args, **kwargs)
except Exception as exc:
this_exc_message = encodeutils.exception_to_unicode(exc)
if this_exc_message == last_exc_message:
same_failure_count += 1
else:
watch.restart()
same_failure_count = 0
last_exc_message = this_exc_message
# This should be a very rare event. In case it isn't, do
# a sleep.
#
# TODO(harlowja): why this is hard coded as one second
# really should be fixed (since I'm not really sure why
# one over some other value was chosen).
time.sleep(1)
return inner_func
same_failure_count = 1
if this_exc_message != last_exc_message or watch.expired():
# The watch has expired or the exception message
# changed, so time to log it again...
logging.exception(
_LE('Unexpected exception occurred %d time(s)... '
'retrying.') % same_failure_count)
if not watch.has_started():
watch.start()
else:
watch.restart()
same_failure_count = 0
last_exc_message = this_exc_message
time.sleep(retry_delay)
return wrapper
# This is needed to handle when the decorator has args or the decorator
# doesn't have args, python is rather weird here...
if kwargs or not args:
return decorator
else:
if len(args) == 1:
return decorator(args[0])
else:
return decorator