diff --git a/libcloud/test/test_connection.py b/libcloud/test/test_connection.py index d23fa7a829..aa9bf43bf7 100644 --- a/libcloud/test/test_connection.py +++ b/libcloud/test/test_connection.py @@ -29,7 +29,12 @@ from libcloud.test import unittest, no_internet from libcloud.utils.py3 import assertRaisesRegex from libcloud.common.base import Response, Connection, CertificateConnection -from libcloud.utils.retry import RETRY_EXCEPTIONS, Retry, RetryForeverOnRateLimitError +from libcloud.utils.retry import ( + RETRY_EXCEPTIONS, + Retry, + MinimalRetry, + RetryForeverOnRateLimitError, +) from libcloud.common.exceptions import RateLimitReachedError @@ -558,6 +563,32 @@ def responseCls(connection, response) -> mock.MagicMock: def _raise_socket_error(self): raise socket.gaierror("") + def test_retry_with_nonpositive_timeout_returns_first_result(self): + for retry_class in (MinimalRetry, Retry): + for timeout in (0, -1): + with self.subTest(retry_class=retry_class, timeout=timeout): + request = Mock(return_value="success") + result = retry_class(timeout=timeout)(request)("/", method="GET") + self.assertEqual(result, "success") + request.assert_called_once_with("/", method="GET") + + @patch("libcloud.utils.retry.time.sleep") + def test_retry_with_nonpositive_timeout_preserves_first_error(self, sleep): + for retry_class in (MinimalRetry, Retry): + for timeout in (0, -1): + for error in ( + socket.gaierror("unavailable"), + RateLimitReachedError(), + ValueError(), + ): + with self.subTest(retry_class=retry_class, timeout=timeout, error=error): + request = Mock(side_effect=error) + with self.assertRaises(type(error)) as exc: + retry_class(timeout=timeout)(request)() + self.assertIs(exc.exception, error) + request.assert_called_once_with() + sleep.assert_not_called() + @patch("libcloud.common.base.Connection.request") def test_retry_with_sleep(self, mock_connect): con = Connection() diff --git a/libcloud/utils/retry.py b/libcloud/utils/retry.py index fabb1133b3..def57aa613 100644 --- a/libcloud/utils/retry.py +++ b/libcloud/utils/retry.py @@ -107,12 +107,15 @@ def retry_loop(*args, **kwargs): end = datetime.now() + timedelta(seconds=self.timeout) last_exc = None - while datetime.now() < end: + while last_exc is None or datetime.now() < end: try: return transform_ssl_error(func, *args, **kwargs) except Exception as exc: last_exc = exc + if datetime.now() >= end: + raise + if isinstance(exc, RateLimitReachedError): _logger.debug("You are being rate limited, backing " "off...")