图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
5.5 KiB

  1. import logging
  2. import random
  3. import time
  4. from kazoo.exceptions import (
  5. ConnectionClosedError,
  6. ConnectionLoss,
  7. KazooException,
  8. OperationTimeoutError,
  9. SessionExpiredError,
  10. )
  11. log = logging.getLogger(__name__)
  12. class ForceRetryError(Exception):
  13. """Raised when some recipe logic wants to force a retry."""
  14. class RetryFailedError(KazooException):
  15. """Raised when retrying an operation ultimately failed, after
  16. retrying the maximum number of attempts.
  17. """
  18. class InterruptedError(RetryFailedError):
  19. """Raised when the retry is forcibly interrupted by the interrupt
  20. function"""
  21. class KazooRetry(object):
  22. """Helper for retrying a method in the face of retry-able
  23. exceptions"""
  24. RETRY_EXCEPTIONS = (ConnectionLoss, OperationTimeoutError, ForceRetryError)
  25. EXPIRED_EXCEPTIONS = (SessionExpiredError,)
  26. def __init__(
  27. self,
  28. max_tries=1,
  29. delay=0.1,
  30. backoff=2,
  31. max_jitter=0.4,
  32. max_delay=60.0,
  33. ignore_expire=True,
  34. sleep_func=time.sleep,
  35. deadline=None,
  36. interrupt=None,
  37. ):
  38. """Create a :class:`KazooRetry` instance for retrying function
  39. calls.
  40. :param max_tries: How many times to retry the command. -1 means
  41. infinite tries.
  42. :param delay: Initial delay between retry attempts.
  43. :param backoff: Backoff multiplier between retry attempts.
  44. Defaults to 2 for exponential backoff.
  45. :param max_jitter: Percentage of jitter to apply to each retry's delay
  46. to ensure all clients to do not hammer the server
  47. at the same time. Between 0.0 and 1.0.
  48. :param max_delay: Maximum delay in seconds, regardless of other
  49. backoff settings. Defaults to one minute.
  50. :param ignore_expire:
  51. Whether a session expiration should be ignored and treated
  52. as a retry-able command.
  53. :param interrupt:
  54. Function that will be called with no args that may return
  55. True if the retry should be ceased immediately. This will
  56. be called no more than every 0.1 seconds during a wait
  57. between retries.
  58. """
  59. self.max_tries = max_tries
  60. self.delay = delay
  61. self.backoff = backoff
  62. # Ensure max_jitter is in (0, 1)
  63. self.max_jitter = max(min(max_jitter, 1.0), 0.0)
  64. self.max_delay = float(max_delay)
  65. self._attempts = 0
  66. self._cur_delay = delay
  67. self.deadline = deadline
  68. self._cur_stoptime = None
  69. self.sleep_func = sleep_func
  70. self.retry_exceptions = self.RETRY_EXCEPTIONS
  71. self.interrupt = interrupt
  72. if ignore_expire:
  73. self.retry_exceptions += self.EXPIRED_EXCEPTIONS
  74. def reset(self):
  75. """Reset the attempt counter"""
  76. self._attempts = 0
  77. self._cur_delay = self.delay
  78. self._cur_stoptime = None
  79. def copy(self):
  80. """Return a clone of this retry manager"""
  81. obj = KazooRetry(
  82. max_tries=self.max_tries,
  83. delay=self.delay,
  84. backoff=self.backoff,
  85. max_jitter=self.max_jitter,
  86. max_delay=self.max_delay,
  87. sleep_func=self.sleep_func,
  88. deadline=self.deadline,
  89. interrupt=self.interrupt,
  90. )
  91. obj.retry_exceptions = self.retry_exceptions
  92. return obj
  93. def __call__(self, func, *args, **kwargs):
  94. """Call a function with arguments until it completes without
  95. throwing a Kazoo exception
  96. :param func: Function to call
  97. :param args: Positional arguments to call the function with
  98. :params kwargs: Keyword arguments to call the function with
  99. The function will be called until it doesn't throw one of the
  100. retryable exceptions (ConnectionLoss, OperationTimeout, or
  101. ForceRetryError), and optionally retrying on session
  102. expiration.
  103. """
  104. self.reset()
  105. while True:
  106. try:
  107. if self.deadline is not None and self._cur_stoptime is None:
  108. self._cur_stoptime = time.time() + self.deadline
  109. return func(*args, **kwargs)
  110. except ConnectionClosedError:
  111. raise
  112. except self.retry_exceptions:
  113. # Note: max_tries == -1 means infinite tries.
  114. if self._attempts == self.max_tries:
  115. raise RetryFailedError("Too many retry attempts")
  116. self._attempts += 1
  117. jitter = random.uniform(
  118. 1.0 - self.max_jitter, 1.0 + self.max_jitter
  119. )
  120. sleeptime = self._cur_delay * jitter
  121. if (
  122. self._cur_stoptime is not None
  123. and time.time() + sleeptime >= self._cur_stoptime
  124. ):
  125. raise RetryFailedError("Exceeded retry deadline")
  126. if self.interrupt:
  127. remain_time = sleeptime
  128. while remain_time > 0:
  129. # Break the time period down and sleep for no
  130. # longer than 0.1 before calling the interrupt
  131. self.sleep_func(min(0.1, remain_time))
  132. remain_time -= 0.1
  133. if self.interrupt():
  134. raise InterruptedError()
  135. else:
  136. self.sleep_func(sleeptime)
  137. self._cur_delay = min(sleeptime * self.backoff, self.max_delay)