backoff

repository·master·Indexed 25 days ago

https://github.com/litl/backoff

A Python library providing function decorators for implementing retry logic and backoff strategies to handle intermittent failures when accessing unreliable resources. It includes support for various wait strategies such as constant, exponential (expo), Fibonacci (fibo), decay, and runtime-based delays, as well as jitter algorithms like full_jitter and random_jitter. The library supports both synchronous functions and asyncio coroutines via @backoff.on_exception and @backoff.on_predicate.

Tokens
2.5K
Snippets
8
Records
23
Agent score
83%

What's inside backoff

  1. Use backoff with asyncio coroutines

    master

    Backoff supports asynchronous execution. Simply apply @backoff.on_exception or @backoff.on_predicate to async def coroutines. Event handlers (on_success, on_backoff, on_giveup) can also be coroutines.

    @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60)
    async def get_url(url):
        async with aiohttp.ClientSession(raise_for_status=True) as session:
            async with session.get(url) as response:
                return await response.text()
  2. Configure logging for backoff

    master

    By default, backoff logs retry attempts to a logger named 'backoff' at the INFO level. Because it uses a NullHandler by default, you must configure a handler to see output.

    To see retry events, set the level to INFO. To see only when a retry attempt is exhausted (a 'giveup' event), set the level to ERROR.

  3. Configure backoff using runtime callables

    master

    If configuration values (like max_time) are only available at runtime, pass a callable instead of a constant. The decorator will execute the callable at runtime to obtain the value.

    def lookup_max_time():
        return app.config["BACKOFF_MAX_TIME"]
    
    @backoff.on_exception(backoff.expo,
                          ValueError,
                          max_time=lookup_max_time)
  4. Combine multiple backoff decorators

    master

    You can stack multiple decorators to handle different retry scenarios (e.g., retrying on specific exceptions with one policy and specific return values with another).

    @backoff.on_predicate(backoff.fibo, max_value=13)
    @backoff.on_exception(backoff.expo,
                          requests.exceptions.HTTPError,
                          max_time=60)
    @backoff.on_exception(backoff.expo,
                          requests.exceptions.Timeout,
                          max_time=300)
    def poll_for_message(queue):
        return queue.get()
  5. Retry on return values with @backoff.on_predicate

    master

    Use the @backoff.on_predicate decorator to retry a function when its return value meets a specific condition. This is useful for polling resources.

    • predicate: A callable that accepts the return value and returns True if a retry is needed.
    • If predicate is omitted, it defaults to a falsey test (retries if the return value is falsey).
    • max_value: An extra keyword argument passed to the wait generator (e.g., to limit Fibonacci sequences).
    @backoff.on_predicate(backoff.fibo, lambda x: x == [], max_value=13)
    def poll_for_messages(queue):
        return queue.get()
  6. Use event handlers for logging and statistics

    master

    Both on_exception and on_predicate accept on_success, on_backoff, and on_giveup handlers. Handlers must be callables that accept a single dictionary argument containing:

    • target: The function/method being invoked.
    • args: Positional arguments passed to the function.
    • kwargs: Keyword arguments passed to the function.
    • tries: Number of invocation tries so far.
    • elapsed: Elapsed time in seconds so far.
    • wait: Seconds to wait (available in on_backoff only).
    • value: The value that triggered the backoff (available in on_predicate only).
    • exception: The exception instance (available in on_exception handlers).

    You can provide a single callable or an iterable of callables.

    def backoff_hdlr(details):
        print ("Backing off {wait:0.1f} seconds after {tries} tries "
               "calling function {target} with args {args} and kwargs "
               "{kwargs}".format(**details))
    
    @backoff.on_exception(backoff.expo,
                          requests.exceptions.RequestException,
                          on_backoff=backoff_hdlr)
    def get_url(url):
        return requests.get(url)
  7. Use @backoff.runtime to dynamically adjust backoff values

    master

    The backoff.runtime generator allows you to use the return value or an exception from the decorated method to determine the next wait interval. This is useful for respecting Retry-After headers in HTTP responses.

    @backoff.on_predicate(
        backoff.runtime,
        predicate=lambda r: r.status_code == 429,
        value=lambda r: int(r.headers.get("Retry-After")),
        jitter=None,
    )
    def get_url():
        return requests.get(url)
  8. Apply jitter to backoff intervals

    master

    The jitter keyword argument accepts a function that takes the original unadulterated backoff value and returns a jittered version.

    • backoff.full_jitter: The default (since v1.2), implements the 'Full Jitter' algorithm.
    • backoff.random_jitter: Adds a random number of milliseconds (up to 1s) to the raw sleep value.
  9. Use a custom logger with backoff decorators

    master
    You can redirect backoff logs to a specific logger using the logger keyword argument in decorators. You can provide either a string (the logger name) or a logging.Logger / logging.LoggerAdapter object.
  10. Retry on exceptions with @backoff.on_exception

    master

    Use the @backoff.on_exception decorator to retry a function when specific exceptions are raised. You can pass a single exception class or a tuple of exception classes.

    Supported wait generators include backoff.expo (exponential backoff) and backoff.fibo (Fibonacci backoff).

    @backoff.on_exception(backoff.expo,
                          requests.exceptions.RequestException)
    def get_url(url):
        return requests.get(url)
  11. Configure give up conditions for @backoff.on_exception

    master

    You can control when the backoff mechanism stops retrying using several keyword arguments:

    • max_time: Maximum total time in seconds to elapse before giving up.
    • max_tries: Maximum number of calls to make before giving up.
    • giveup: A callable that accepts the exception and returns a truthy value if the exception should not be retried (e.g., a fatal error code).
    • raise_on_giveup: If set to False, the exception is not re-raised when the give-up condition is met; instead, the decorated function returns None.
    def fatal_code(e):
        return 400 <= e.response.status_code < 500
    
    @backoff.on_exception(backoff.expo,
                          requests.exceptions.RequestException,
                          max_time=300,
                          raise_on_giveup=False,
                          giveup=fatal_code)
    def get_url(url):
        return requests.get(url)