1# Copyright 2020 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""This contains common retrying helpers (retryers). 15 16We use tenacity as a general-purpose retrying library. 17 18> It [tenacity] originates from a fork of retrying which is sadly no 19> longer maintained. Tenacity isn’t api compatible with retrying but > 20> adds significant new functionality and fixes a number of longstanding bugs. 21> - https://tenacity.readthedocs.io/en/latest/index.html 22""" 23import datetime 24import logging 25from typing import Any, List, Optional 26 27import tenacity 28 29retryers_logger = logging.getLogger(__name__) 30# Type aliases 31timedelta = datetime.timedelta 32Retrying = tenacity.Retrying 33RetryError = tenacity.RetryError 34_after_log = tenacity.after_log 35_before_sleep_log = tenacity.before_sleep_log 36_retry_if_exception_type = tenacity.retry_if_exception_type 37_stop_after_attempt = tenacity.stop_after_attempt 38_stop_after_delay = tenacity.stop_after_delay 39_stop_any = tenacity.stop_any 40_wait_exponential = tenacity.wait_exponential 41_wait_fixed = tenacity.wait_fixed 42 43 44def _retry_on_exceptions(retry_on_exceptions: Optional[List[Any]] = None): 45 # Retry on all exceptions by default 46 if retry_on_exceptions is None: 47 retry_on_exceptions = (Exception,) 48 return _retry_if_exception_type(retry_on_exceptions) 49 50 51def exponential_retryer_with_timeout( 52 *, 53 wait_min: timedelta, 54 wait_max: timedelta, 55 timeout: timedelta, 56 retry_on_exceptions: Optional[List[Any]] = None, 57 logger: Optional[logging.Logger] = None, 58 log_level: Optional[int] = logging.DEBUG) -> Retrying: 59 if logger is None: 60 logger = retryers_logger 61 if log_level is None: 62 log_level = logging.DEBUG 63 return Retrying(retry=_retry_on_exceptions(retry_on_exceptions), 64 wait=_wait_exponential(min=wait_min.total_seconds(), 65 max=wait_max.total_seconds()), 66 stop=_stop_after_delay(timeout.total_seconds()), 67 before_sleep=_before_sleep_log(logger, log_level)) 68 69 70def constant_retryer(*, 71 wait_fixed: timedelta, 72 attempts: int = 0, 73 timeout: timedelta = None, 74 retry_on_exceptions: Optional[List[Any]] = None, 75 logger: Optional[logging.Logger] = None, 76 log_level: Optional[int] = logging.DEBUG) -> Retrying: 77 if logger is None: 78 logger = retryers_logger 79 if log_level is None: 80 log_level = logging.DEBUG 81 if attempts < 1 and timeout is None: 82 raise ValueError('The number of attempts or the timeout must be set') 83 stops = [] 84 if attempts > 0: 85 stops.append(_stop_after_attempt(attempts)) 86 if timeout is not None: 87 stops.append(_stop_after_delay.total_seconds()) 88 89 return Retrying(retry=_retry_on_exceptions(retry_on_exceptions), 90 wait=_wait_fixed(wait_fixed.total_seconds()), 91 stop=_stop_any(*stops), 92 before_sleep=_before_sleep_log(logger, log_level)) 93