Index: recipe_engine/util.py |
diff --git a/recipe_engine/util.py b/recipe_engine/util.py |
index cb3257b37f1f98027aae40c9b4b699c15d88c26a..91c5192c8c7ccad7d5da3e1b04b327b3a796e8df 100644 |
--- a/recipe_engine/util.py |
+++ b/recipe_engine/util.py |
@@ -3,10 +3,13 @@ |
# that can be found in the LICENSE file. |
import contextlib |
+import datetime |
import functools |
+import logging |
import os |
import sys |
import traceback |
+import time |
import urllib |
from cStringIO import StringIO |
@@ -190,3 +193,38 @@ class StringListIO(object): |
def close(self): |
if not isinstance(self.lines[-1], basestring): |
self.lines[-1] = self.lines[-1].getvalue() |
+ |
+ |
+class exponential_retry(object): |
+ """Decorator which retries the function if an exception is encountered.""" |
+ |
+ def __init__(self, retries=None, delay=None, condition=None): |
+ """Creates a new exponential retry decorator. |
+ |
+ Args: |
+ retries (int): Maximum number of retries before giving up. |
+ delay (datetime.timedelta): Amount of time to wait before retrying. This |
+ will double every retry attempt (exponential). |
+ condition (func): If not None, a function that will be passed the |
+ exception as its one argument. Retries will only happen if this |
+ function returns True. If None, retries will always happen. |
+ """ |
+ self.retries = retries or 5 |
+ self.delay = delay or datetime.timedelta(seconds=1) |
+ self.condition = condition or (lambda e: True) |
+ |
+ def __call__(self, f): |
+ @functools.wraps(f) |
+ def wrapper(*args, **kwargs): |
+ retry_delay = self.delay |
+ for i in xrange(self.retries): |
+ try: |
+ return f(*args, **kwargs) |
+ except Exception as e: |
+ if (i+1) >= self.retries or not self.condition(e): |
+ raise |
+ logging.exception('Exception encountered, retrying in %s', |
+ retry_delay) |
+ time.sleep(retry_delay.total_seconds()) |
+ retry_delay *= 2 |
+ return wrapper |