Skip to content

Commit 426f3e0

Browse files
juliuszsompolskisusodapop
authored andcommitted
Retries for 429 and 503 HTTP errors
Add retry support to clients during HTTP 429/503 scenarios, with sane defaults.
1 parent 37de4c8 commit 426f3e0

File tree

4 files changed

+19
-14
lines changed

4 files changed

+19
-14
lines changed

cmdexec/clients/python/src/databricks/sql/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def __init__(self,
5757
# Set client SSL certificate.
5858
# _session_id
5959
# Specify the session id of the connection. For Redash use only.
60-
# _max_number_of_retries
61-
# The maximum number of times we should retry retriable requests (defaults to 25)
60+
# _retry_stop_after_attempts_count
61+
# The maximum number of attempts during a request retry sequence (defaults to 24)
6262

6363
self.host = server_hostname
6464
self.port = kwargs.get("_port", 443)
@@ -91,7 +91,7 @@ def __init__(self,
9191
_tls_client_cert_key_file=kwargs.get("_tls_client_cert_key_file"),
9292
_tls_client_cert_key_password=kwargs.get("_tls_client_cert_key_password"),
9393
_connection_uri=kwargs.get("_connection_uri"),
94-
_max_number_of_retries=kwargs.get("_max_number_of_retries", 25))
94+
_retry_stop_after_attempts_count=kwargs.get("_retry_stop_after_attempts_count", 24))
9595

9696
self._session_handle = self.thrift_backend.open_session(
9797
session_id=kwargs.get("_session_id"))

cmdexec/clients/python/src/databricks/sql/thrift_backend.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ def __init__(self, server_hostname: str, port, http_path: str, http_headers, **k
4343
# See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain
4444
# _connection_uri
4545
# Overrides server_hostname and http_path.
46-
# _max_number_of_retries
47-
# The maximum number of times we should retry retryable requests (defaults to 25)
46+
# _retry_stop_after_attempts_count
47+
# The maximum number of times we should retry retryable requests (defaults to 24)
4848

4949
port = port or 443
5050
if kwargs.get("_connection_uri"):
@@ -55,7 +55,7 @@ def __init__(self, server_hostname: str, port, http_path: str, http_headers, **k
5555
else:
5656
raise ValueError("No valid connection settings.")
5757

58-
self._max_number_of_retries = kwargs.get("_max_number_of_retries", 25)
58+
self._retry_stop_after_attempts_count = kwargs.get("_retry_stop_after_attempts_count", 24)
5959

6060
# Configure tls context
6161
ssl_context = create_default_context(cafile=kwargs.get("_tls_trusted_ca_file"))
@@ -101,6 +101,9 @@ def _check_response_for_error(response):
101101
[ttypes.TStatusCode.ERROR_STATUS, ttypes.TStatusCode.INVALID_HANDLE_STATUS]:
102102
raise DatabaseError(response.status.errorMessage)
103103

104+
# FUTURE: Consider moving to https://github.com/litl/backoff or
105+
# https://github.com/jd/tenacity for retry logic. Otherwise, copy from
106+
# v1 client.
104107
def make_request(self, method, request, attempt_number=1):
105108
try:
106109
# We have a lock here because .cancel can be called from a separate thread.
@@ -118,7 +121,7 @@ def make_request(self, method, request, attempt_number=1):
118121
# We only retry if a Retry-After header is set
119122
if code_and_headers_is_set and self._transport.code in [503, 429] and \
120123
"Retry-After" in self._transport.headers and \
121-
attempt_number <= self._max_number_of_retries:
124+
attempt_number <= self._retry_stop_after_attempts_count - 1:
122125
retry_time_seconds = int(self._transport.headers["Retry-After"])
123126
if self.ERROR_MSG_HEADER in self._transport.headers:
124127
error_message = self._transport.headers[self.ERROR_MSG_HEADER]

cmdexec/clients/python/tests/test_thrift_backend.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -808,22 +808,23 @@ def test_make_request_wont_retry_if_error_code_not_429_or_503(self, t_transport_
808808
self.assertIn("This method fails", str(cm.exception))
809809

810810
@patch("thrift.transport.THttpClient.THttpClient")
811-
def test_make_request_will_retry_max_number_of_retries_times_if_retryable(
811+
def test_make_request_will_retry_stop_after_attempts_count_if_retryable(
812812
self, t_transport_class):
813813
t_transport_instance = t_transport_class.return_value
814814
t_transport_instance.code = 429
815815
t_transport_instance.headers = {"Retry-After": "0"}
816816
mock_method = Mock()
817817
mock_method.side_effect = Exception("This method fails")
818818

819-
thrift_backend = ThriftBackend("foobar", 443, "path", [], _max_number_of_retries=13)
819+
thrift_backend = ThriftBackend(
820+
"foobar", 443, "path", [], _retry_stop_after_attempts_count=14)
820821

821822
with self.assertRaises(OperationalError) as cm:
822823
thrift_backend.make_request(mock_method, Mock())
823824

824825
self.assertIn("This method fails", str(cm.exception))
825826

826-
self.assertEqual(mock_method.call_count, 13 + 1)
827+
self.assertEqual(mock_method.call_count, 14)
827828

828829
@patch("thrift.transport.THttpClient.THttpClient")
829830
def test_make_request_will_read_X_Thriftserver_Error_Message_if_set(self, t_transport_class):
@@ -836,14 +837,15 @@ def test_make_request_will_read_X_Thriftserver_Error_Message_if_set(self, t_tran
836837
mock_method = Mock()
837838
mock_method.side_effect = Exception("This method fails")
838839

839-
thrift_backend = ThriftBackend("foobar", 443, "path", [], _max_number_of_retries=13)
840+
thrift_backend = ThriftBackend(
841+
"foobar", 443, "path", [], _retry_stop_after_attempts_count=14)
840842

841843
with self.assertRaises(OperationalError) as cm:
842844
thrift_backend.make_request(mock_method, Mock())
843845

844846
self.assertIn("message2", str(cm.exception))
845847

846-
self.assertEqual(mock_method.call_count, 13 + 1)
848+
self.assertEqual(mock_method.call_count, 14)
847849

848850
@staticmethod
849851
def make_table_and_desc(height, n_decimal_cols, width, precision, scale, int_constant,

cmdexec/clients/python/tests/tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,9 @@ def test_cancel_command_will_issue_warning_for_cancel_with_no_executing_command(
327327

328328
@patch("%s.client.ThriftBackend" % PACKAGE_NAME)
329329
def test_max_number_of_retries_passthrough(self, mock_client_class):
330-
databricks.sql.connect(_max_number_of_retries=53, **self.DUMMY_CONNECTION_ARGS)
330+
databricks.sql.connect(_retry_stop_after_attempts_count=54, **self.DUMMY_CONNECTION_ARGS)
331331

332-
self.assertEqual(mock_client_class.call_args[1]["_max_number_of_retries"], 53)
332+
self.assertEqual(mock_client_class.call_args[1]["_retry_stop_after_attempts_count"], 54)
333333

334334

335335
if __name__ == '__main__':

0 commit comments

Comments
 (0)