Bug 1118774 - Import python redo library; r=gps
This commit is contained in:
@@ -24,3 +24,4 @@ objdir:build
|
|||||||
gyp.pth:media/webrtc/trunk/tools/gyp/pylib
|
gyp.pth:media/webrtc/trunk/tools/gyp/pylib
|
||||||
pyasn1.pth:python/pyasn1
|
pyasn1.pth:python/pyasn1
|
||||||
bitstring.pth:python/bitstring
|
bitstring.pth:python/bitstring
|
||||||
|
redo.pth:python/redo
|
||||||
|
|||||||
10
python/redo/PKG-INFO
Normal file
10
python/redo/PKG-INFO
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Metadata-Version: 1.0
|
||||||
|
Name: redo
|
||||||
|
Version: 1.4
|
||||||
|
Summary: Utilities to retry Python callables.
|
||||||
|
Home-page: https://github.com/bhearsum/redo
|
||||||
|
Author: Ben Hearsum
|
||||||
|
Author-email: ben@hearsum.ca
|
||||||
|
License: UNKNOWN
|
||||||
|
Description: UNKNOWN
|
||||||
|
Platform: UNKNOWN
|
||||||
4
python/redo/README
Normal file
4
python/redo/README
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Redo - Utilities to retry Python callables
|
||||||
|
******************************************
|
||||||
|
|
||||||
|
Redo provides various means to add seamless retriability to any Python callable. Redo includes a plain function (redo.retry), a decorator (redo.retriable), and a context manager (redo.retrying) to enable you to integrate it in the best possible way for your project. As a bonus, a standalone interface is also included ("retry"). For details and sample invocations have a look at the docstrings in redo/__init__.py.
|
||||||
10
python/redo/redo.egg-info/PKG-INFO
Normal file
10
python/redo/redo.egg-info/PKG-INFO
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Metadata-Version: 1.0
|
||||||
|
Name: redo
|
||||||
|
Version: 1.4
|
||||||
|
Summary: Utilities to retry Python callables.
|
||||||
|
Home-page: https://github.com/bhearsum/redo
|
||||||
|
Author: Ben Hearsum
|
||||||
|
Author-email: ben@hearsum.ca
|
||||||
|
License: UNKNOWN
|
||||||
|
Description: UNKNOWN
|
||||||
|
Platform: UNKNOWN
|
||||||
9
python/redo/redo.egg-info/SOURCES.txt
Normal file
9
python/redo/redo.egg-info/SOURCES.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
README
|
||||||
|
setup.py
|
||||||
|
redo/__init__.py
|
||||||
|
redo/cmd.py
|
||||||
|
redo.egg-info/PKG-INFO
|
||||||
|
redo.egg-info/SOURCES.txt
|
||||||
|
redo.egg-info/dependency_links.txt
|
||||||
|
redo.egg-info/entry_points.txt
|
||||||
|
redo.egg-info/top_level.txt
|
||||||
1
python/redo/redo.egg-info/dependency_links.txt
Normal file
1
python/redo/redo.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
3
python/redo/redo.egg-info/entry_points.txt
Normal file
3
python/redo/redo.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[console_scripts]
|
||||||
|
retry = redo.cmd:main
|
||||||
|
|
||||||
1
python/redo/redo.egg-info/top_level.txt
Normal file
1
python/redo/redo.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
redo
|
||||||
218
python/redo/redo/__init__.py
Normal file
218
python/redo/redo/__init__.py
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
# ***** END LICENSE BLOCK *****
|
||||||
|
|
||||||
|
import time
|
||||||
|
from functools import wraps
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def retrier(attempts=5, sleeptime=10, max_sleeptime=300, sleepscale=1.5, jitter=1):
|
||||||
|
"""
|
||||||
|
A generator function that sleeps between retries, handles exponential
|
||||||
|
backoff and jitter. The action you are retrying is meant to run after
|
||||||
|
retrier yields.
|
||||||
|
|
||||||
|
At each iteration, we sleep for sleeptime + random.randint(-jitter, jitter).
|
||||||
|
Afterwards sleeptime is multiplied by sleepscale for the next iteration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
attempts (int): maximum number of times to try; defaults to 5
|
||||||
|
sleeptime (float): how many seconds to sleep between tries; defaults to
|
||||||
|
60s (one minute)
|
||||||
|
max_sleeptime (float): the longest we'll sleep, in seconds; defaults to
|
||||||
|
300s (five minutes)
|
||||||
|
sleepscale (float): how much to multiply the sleep time by each
|
||||||
|
iteration; defaults to 1.5
|
||||||
|
jitter (int): random jitter to introduce to sleep time each iteration.
|
||||||
|
the amount is chosen at random between [-jitter, +jitter]
|
||||||
|
defaults to 1
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
None, a maximum of `attempts` number of times
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> n = 0
|
||||||
|
>>> for _ in retrier(sleeptime=0, jitter=0):
|
||||||
|
... if n == 3:
|
||||||
|
... # We did the thing!
|
||||||
|
... break
|
||||||
|
... n += 1
|
||||||
|
>>> n
|
||||||
|
3
|
||||||
|
|
||||||
|
>>> n = 0
|
||||||
|
>>> for _ in retrier(sleeptime=0, jitter=0):
|
||||||
|
... if n == 6:
|
||||||
|
... # We did the thing!
|
||||||
|
... break
|
||||||
|
... n += 1
|
||||||
|
... else:
|
||||||
|
... print "max tries hit"
|
||||||
|
max tries hit
|
||||||
|
"""
|
||||||
|
for _ in range(attempts):
|
||||||
|
log.debug("attempt %i/%i", _ + 1, attempts)
|
||||||
|
yield
|
||||||
|
if jitter:
|
||||||
|
sleeptime += random.randint(-jitter, jitter)
|
||||||
|
sleeptime = max(sleeptime, 0)
|
||||||
|
|
||||||
|
if _ == attempts - 1:
|
||||||
|
# Don't need to sleep the last time
|
||||||
|
break
|
||||||
|
log.debug("sleeping for %.2fs (attempt %i/%i)", sleeptime, _ + 1, attempts)
|
||||||
|
time.sleep(sleeptime)
|
||||||
|
sleeptime *= sleepscale
|
||||||
|
if sleeptime > max_sleeptime:
|
||||||
|
sleeptime = max_sleeptime
|
||||||
|
|
||||||
|
|
||||||
|
def retry(action, attempts=5, sleeptime=60, max_sleeptime=5 * 60,
|
||||||
|
sleepscale=1.5, jitter=1, retry_exceptions=(Exception,),
|
||||||
|
cleanup=None, args=(), kwargs={}):
|
||||||
|
"""
|
||||||
|
Calls an action function until it succeeds, or we give up.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action (callable): the function to retry
|
||||||
|
attempts (int): maximum number of times to try; defaults to 5
|
||||||
|
sleeptime (float): how many seconds to sleep between tries; defaults to
|
||||||
|
60s (one minute)
|
||||||
|
max_sleeptime (float): the longest we'll sleep, in seconds; defaults to
|
||||||
|
300s (five minutes)
|
||||||
|
sleepscale (float): how much to multiply the sleep time by each
|
||||||
|
iteration; defaults to 1.5
|
||||||
|
jitter (int): random jitter to introduce to sleep time each iteration.
|
||||||
|
the amount is chosen at random between [-jitter, +jitter]
|
||||||
|
defaults to 1
|
||||||
|
retry_exceptions (tuple): tuple of exceptions to be caught. If other
|
||||||
|
exceptions are raised by action(), then these
|
||||||
|
are immediately re-raised to the caller.
|
||||||
|
cleanup (callable): optional; called if one of `retry_exceptions` is
|
||||||
|
caught. No arguments are passed to the cleanup
|
||||||
|
function; if your cleanup requires arguments,
|
||||||
|
consider using functools.partial or a lambda
|
||||||
|
function.
|
||||||
|
args (tuple): positional arguments to call `action` with
|
||||||
|
hwargs (dict): keyword arguments to call `action` with
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whatever action(*args, **kwargs) returns
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Whatever action(*args, **kwargs) raises. `retry_exceptions` are caught
|
||||||
|
up until the last attempt, in which case they are re-raised.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> count = 0
|
||||||
|
>>> def foo():
|
||||||
|
... global count
|
||||||
|
... count += 1
|
||||||
|
... print count
|
||||||
|
... if count < 3:
|
||||||
|
... raise ValueError("count is too small!")
|
||||||
|
... return "success!"
|
||||||
|
>>> retry(foo, sleeptime=0, jitter=0)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
'success!'
|
||||||
|
"""
|
||||||
|
assert callable(action)
|
||||||
|
assert not cleanup or callable(cleanup)
|
||||||
|
if max_sleeptime < sleeptime:
|
||||||
|
log.debug("max_sleeptime %d less than sleeptime %d" % (
|
||||||
|
max_sleeptime, sleeptime))
|
||||||
|
|
||||||
|
n = 1
|
||||||
|
for _ in retrier(attempts=attempts, sleeptime=sleeptime,
|
||||||
|
max_sleeptime=max_sleeptime, sleepscale=sleepscale,
|
||||||
|
jitter=jitter):
|
||||||
|
try:
|
||||||
|
log.info("retry: Calling %s with args: %s, kwargs: %s, "
|
||||||
|
"attempt #%d" % (action, str(args), str(kwargs), n))
|
||||||
|
return action(*args, **kwargs)
|
||||||
|
except retry_exceptions:
|
||||||
|
log.debug("retry: Caught exception: ", exc_info=True)
|
||||||
|
if cleanup:
|
||||||
|
cleanup()
|
||||||
|
if n == attempts:
|
||||||
|
log.info("retry: Giving up on %s" % action)
|
||||||
|
raise
|
||||||
|
continue
|
||||||
|
finally:
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
|
||||||
|
def retriable(*retry_args, **retry_kwargs):
|
||||||
|
"""
|
||||||
|
A decorator factory for retry(). Wrap your function in @retriable(...) to
|
||||||
|
give it retry powers!
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
Same as for `retry`, with the exception of `action`, `args`, and `kwargs`,
|
||||||
|
which are left to the normal function definition.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A function decorator
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> count = 0
|
||||||
|
>>> @retriable(sleeptime=0, jitter=0)
|
||||||
|
... def foo():
|
||||||
|
... global count
|
||||||
|
... count += 1
|
||||||
|
... print count
|
||||||
|
... if count < 3:
|
||||||
|
... raise ValueError("count too small")
|
||||||
|
... return "success!"
|
||||||
|
>>> foo()
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
'success!'
|
||||||
|
"""
|
||||||
|
def _retriable_factory(func):
|
||||||
|
@wraps(func)
|
||||||
|
def _retriable_wrapper(*args, **kwargs):
|
||||||
|
return retry(func, args=args, kwargs=kwargs, *retry_args,
|
||||||
|
**retry_kwargs)
|
||||||
|
return _retriable_wrapper
|
||||||
|
return _retriable_factory
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def retrying(func, *retry_args, **retry_kwargs):
|
||||||
|
"""
|
||||||
|
A context manager for wrapping functions with retry functionality.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
func (callable): the function to wrap
|
||||||
|
other arguments as per `retry`
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A context manager that returns retriable(func) on __enter__
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> count = 0
|
||||||
|
>>> def foo():
|
||||||
|
... global count
|
||||||
|
... count += 1
|
||||||
|
... print count
|
||||||
|
... if count < 3:
|
||||||
|
... raise ValueError("count too small")
|
||||||
|
... return "success!"
|
||||||
|
>>> with retrying(foo, sleeptime=0, jitter=0) as f:
|
||||||
|
... f()
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
'success!'
|
||||||
|
"""
|
||||||
|
yield retriable(*retry_args, **retry_kwargs)(func)
|
||||||
53
python/redo/redo/cmd.py
Normal file
53
python/redo/redo/cmd.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
# ***** END LICENSE BLOCK *****
|
||||||
|
import logging
|
||||||
|
from subprocess import check_call, CalledProcessError
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from redo import retrying
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-a", "--attempts", type=int, default=5,
|
||||||
|
help="How many times to retry.")
|
||||||
|
parser.add_argument(
|
||||||
|
"-s", "--sleeptime", type=int, default=60,
|
||||||
|
help="How long to sleep between attempts. Sleeptime doubles after each attempt.")
|
||||||
|
parser.add_argument(
|
||||||
|
"-m", "--max-sleeptime", type=int, default=5*60,
|
||||||
|
help="Maximum length of time to sleep between attempts (limits backoff length).")
|
||||||
|
parser.add_argument("-v", "--verbose", action="store_true", default=False)
|
||||||
|
parser.add_argument("cmd", nargs="+", help="Command to run. Eg: wget http://blah")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logging.getLogger("retry").setLevel(logging.INFO)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=logging.ERROR)
|
||||||
|
logging.getLogger("retry").setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with retrying(check_call, attempts=args.attempts, sleeptime=args.sleeptime,
|
||||||
|
max_sleeptime=args.max_sleeptime,
|
||||||
|
retry_exceptions=(CalledProcessError,)) as r_check_call:
|
||||||
|
r_check_call(args.cmd)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(-1)
|
||||||
|
except Exception as e:
|
||||||
|
log.error("Unable to run command after %d attempts" % args.attempts, exc_info=True)
|
||||||
|
rc = getattr(e, "returncode", -2)
|
||||||
|
sys.exit(rc)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
5
python/redo/setup.cfg
Normal file
5
python/redo/setup.cfg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[egg_info]
|
||||||
|
tag_build =
|
||||||
|
tag_date = 0
|
||||||
|
tag_svn_revision = 0
|
||||||
|
|
||||||
14
python/redo/setup.py
Normal file
14
python/redo/setup.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="redo",
|
||||||
|
version="1.4",
|
||||||
|
description="Utilities to retry Python callables.",
|
||||||
|
author="Ben Hearsum",
|
||||||
|
author_email="ben@hearsum.ca",
|
||||||
|
packages=["redo"],
|
||||||
|
entry_points={
|
||||||
|
"console_scripts": ["retry = redo.cmd:main"],
|
||||||
|
},
|
||||||
|
url="https://github.com/bhearsum/redo",
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user