d
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
import time
|
||||
|
||||
import psycopg2
|
||||
from psycopg2 import errors
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
@@ -18,3 +21,39 @@ def get_db():
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
_RETRYABLE_READ_ERRORS = (
|
||||
errors.DeadlockDetected,
|
||||
errors.SerializationFailure,
|
||||
errors.LockNotAvailable,
|
||||
)
|
||||
|
||||
|
||||
def is_retryable_read_error(exc: Exception) -> bool:
|
||||
return isinstance(exc, _RETRYABLE_READ_ERRORS)
|
||||
|
||||
|
||||
def is_retryable_db_error(exc: Exception) -> bool:
|
||||
return isinstance(exc, _RETRYABLE_READ_ERRORS)
|
||||
|
||||
|
||||
def db_read_retry(*, attempts: int = 3, initial_delay_seconds: float = 0.05, backoff: float = 2.0):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
delay = max(0.0, float(initial_delay_seconds or 0.0))
|
||||
for attempt in range(1, max(1, int(attempts or 1)) + 1):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as exc:
|
||||
if not is_retryable_read_error(exc) or attempt >= max(1, int(attempts or 1)):
|
||||
raise
|
||||
if delay > 0:
|
||||
time.sleep(delay)
|
||||
delay *= max(1.0, float(backoff or 1.0))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
Reference in New Issue
Block a user