Cduplar's picture
Initial public release: Multi-Agent MCP Delegation Server
8b02e7c
Raw
History Blame Contribute Delete
2.8 kB
"""Retry logic with exponential backoff."""
import asyncio
import logging
from typing import TypeVar, Callable, Any
from functools import wraps
logger = logging.getLogger(__name__)
T = TypeVar("T")
async def retry_with_backoff(
func: Callable[..., Any],
*args: Any,
max_retries: int = 3,
initial_delay: float = 1.0,
backoff_factor: float = 2.0,
exceptions: tuple = (Exception,),
**kwargs: Any,
) -> Any:
"""
Retry an async function with exponential backoff.
Args:
func: Async function to retry
*args: Positional arguments for func
max_retries: Maximum number of retry attempts
initial_delay: Initial delay in seconds
backoff_factor: Multiplier for delay on each retry
exceptions: Tuple of exceptions to catch and retry
**kwargs: Keyword arguments for func
Returns:
Result of successful function call
Raises:
Last exception if all retries fail
"""
delay = initial_delay
last_exception = None
for attempt in range(max_retries + 1):
try:
return await func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt == max_retries:
logger.error(
f"Failed after {max_retries} retries: {str(e)}"
)
raise
logger.warning(
f"Attempt {attempt + 1}/{max_retries + 1} failed: {str(e)}. "
f"Retrying in {delay:.1f}s..."
)
await asyncio.sleep(delay)
delay *= backoff_factor
# This should never be reached, but just in case
if last_exception:
raise last_exception
def with_retry(
max_retries: int = 3,
initial_delay: float = 1.0,
backoff_factor: float = 2.0,
exceptions: tuple = (Exception,),
):
"""
Decorator for adding retry logic to async functions.
Args:
max_retries: Maximum number of retry attempts
initial_delay: Initial delay in seconds
backoff_factor: Multiplier for delay on each retry
exceptions: Tuple of exceptions to catch and retry
Example:
@with_retry(max_retries=3, initial_delay=1.0)
async def fetch_data():
return await api.get('/data')
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
return await retry_with_backoff(
func,
*args,
max_retries=max_retries,
initial_delay=initial_delay,
backoff_factor=backoff_factor,
exceptions=exceptions,
**kwargs,
)
return wrapper
return decorator