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)
try:
while self._running:
no_error = self.f(*self.args, **self.kw)
if not self._running:
break
random_jitter = random.gauss(jitter, 0.1) random_jitter = random.gauss(jitter, 0.1)
if no_error: if success:
# Reset error state # Reset error state now that it didn't error...
error_time = 0 self._interval = starting_interval
interval = starting_interval self._error_time = 0
idle = interval * random_jitter return self._interval * random_jitter
else: else:
# Backoff # Perform backoff
interval = min(interval * 2 * random_jitter, self._interval = idle = min(
max_interval) self._interval * 2 * random_jitter, max_interval)
idle = interval
# Don't go over timeout, end early if necessary. If # Don't go over timeout, end early if necessary. If
# timeout is 0, keep going. # timeout is 0, keep going.
if timeout > 0 and error_time + idle > timeout: if timeout > 0 and self._error_time + idle > timeout:
raise LoopingCallTimeOut( raise LoopingCallTimeOut(
'Looping call timed out after %.02f seconds' 'Looping call timed out after %.02f seconds'
% error_time) % self._error_time)
error_time += idle self._error_time += idle
return idle
LOG.debug('Dynamic looping call sleeping for %.02f ' return self._start(_idle_for, initial_delay=initial_delay)
'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:
done.send(True)
self.done = done
greenthread.spawn(_inner)
return self.done