• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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