Refactor backoff looping call

This codebase can now be updated to use the refactored
base class to avoid duplicating alot of the same code.

Eventually we should also just move this to oslo if
when/this review is accepted since that seems like a better
home for this code in general.

Change-Id: I387d60667f427824a64d52544638792429887ebf
This commit is contained in:
Joshua Harlow 2015-09-02 14:59:17 -07:00
parent b30efc1c7d
commit 04582bc219

View File

@ -13,10 +13,7 @@
# under the License. # under the License.
import random import random
import sys
from eventlet import event
from eventlet import greenthread
from oslo_log import log from oslo_log import log
from oslo_service import loopingcall from oslo_service import loopingcall
@ -78,56 +75,42 @@ class BackOffLoopingCall(loopingcall.LoopingCallBase):
timeout. timeout.
""" """
_KIND = 'Dynamic backoff interval looping call'
_RUN_ONLY_ONE_MESSAGE = ("A dynamic backoff interval looping call can"
" only run one function at a time")
def __init__(self, f=None, *args, **kw):
super(BackOffLoopingCall, self).__init__(f=f, *args, **kw)
self._error_time = 0
self._interval = 1
def start(self, initial_delay=None, starting_interval=1, timeout=300, def start(self, initial_delay=None, starting_interval=1, timeout=300,
max_interval=300, jitter=0.75): max_interval=300, jitter=0.75):
self._running = True if self._thread is not None:
done = event.Event() raise RuntimeError(self._RUN_ONLY_ONE_MESSAGE)
def _inner(): # Reset any prior state.
interval = starting_interval self._error_time = 0
error_time = 0 self._interval = starting_interval
if initial_delay: def _idle_for(success, _elapsed):
greenthread.sleep(initial_delay) random_jitter = random.gauss(jitter, 0.1)
if success:
try: # Reset error state now that it didn't error...
while self._running: self._interval = starting_interval
no_error = self.f(*self.args, **self.kw) self._error_time = 0
if not self._running: return self._interval * random_jitter
break
random_jitter = random.gauss(jitter, 0.1)
if no_error:
# Reset error state
error_time = 0
interval = starting_interval
idle = interval * random_jitter
else:
# Backoff
interval = min(interval * 2 * random_jitter,
max_interval)
idle = interval
# Don't go over timeout, end early if necessary. If
# timeout is 0, keep going.
if timeout > 0 and error_time + idle > timeout:
raise LoopingCallTimeOut(
'Looping call timed out after %.02f seconds'
% error_time)
error_time += idle
LOG.debug('Dynamic looping call sleeping for %.02f '
'seconds', idle)
greenthread.sleep(idle)
except loopingcall.LoopingCallDone as e:
self.stop()
done.send(e.retvalue)
except Exception:
LOG.exception('in dynamic looping call')
done.send_exception(*sys.exc_info())
return
else: else:
done.send(True) # Perform backoff
self._interval = idle = min(
self._interval * 2 * random_jitter, max_interval)
# Don't go over timeout, end early if necessary. If
# timeout is 0, keep going.
if timeout > 0 and self._error_time + idle > timeout:
raise LoopingCallTimeOut(
'Looping call timed out after %.02f seconds'
% self._error_time)
self._error_time += idle
return idle
self.done = done return self._start(_idle_for, initial_delay=initial_delay)
greenthread.spawn(_inner)
return self.done