Skip to content

Commit f064e6e

Browse files
committed
Changed the retry logic to be idempotency based
1 parent 2f982bc commit f064e6e

File tree

1 file changed

+44
-42
lines changed

1 file changed

+44
-42
lines changed

src/databricks/sql/auth/retry.py

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,40 @@ def get(cls, value: str):
4848
return cls.OTHER
4949

5050

51+
class CommandIdempotency(Enum):
52+
IDEMPOTENT = "idempotent"
53+
NON_IDEMPOTENT = "non_idempotent"
54+
55+
56+
# Mapping of CommandType to CommandIdempotency
57+
# Only EXECUTE_STATEMENT is non-idempotent, all others are idempotent
58+
COMMAND_IDEMPOTENCY_MAP = {
59+
CommandType.EXECUTE_STATEMENT: CommandIdempotency.NON_IDEMPOTENT,
60+
CommandType.CLOSE_SESSION: CommandIdempotency.IDEMPOTENT,
61+
CommandType.CLOSE_OPERATION: CommandIdempotency.IDEMPOTENT,
62+
CommandType.GET_OPERATION_STATUS: CommandIdempotency.IDEMPOTENT,
63+
CommandType.OTHER: CommandIdempotency.IDEMPOTENT,
64+
}
65+
66+
# HTTP status codes that should never be retried, even for idempotent requests
67+
# These are client error codes that indicate permanent issues
68+
NON_RETRYABLE_STATUS_CODES = {
69+
400, # Bad Request
70+
401, # Unauthorized
71+
403, # Forbidden
72+
404, # Not Found
73+
405, # Method Not Allowed
74+
409, # Conflict
75+
410, # Gone
76+
411, # Length Required
77+
412, # Precondition Failed
78+
413, # Payload Too Large
79+
414, # URI Too Long
80+
415, # Unsupported Media Type
81+
416, # Range Not Satisfiable
82+
}
83+
84+
5185
class DatabricksRetryPolicy(Retry):
5286
"""
5387
Implements our v3 retry policy by extending urllib3's robust default retry behaviour.
@@ -358,21 +392,13 @@ def should_retry(self, method: str, status_code: int) -> Tuple[bool, str]:
358392
if status_code // 100 <= 3:
359393
return False, "2xx/3xx codes are not retried"
360394

361-
if status_code == 400:
395+
# Check for non-retryable status codes that should never be retried
396+
if status_code in NON_RETRYABLE_STATUS_CODES:
362397
return (
363398
False,
364-
"Received 400 - BAD_REQUEST. Please check the request parameters.",
399+
f"{self.command_type.value if self.command_type else 'Unknown'} received {status_code} - Client error codes are not retried",
365400
)
366401

367-
if status_code == 401:
368-
return (
369-
False,
370-
"Received 401 - UNAUTHORIZED. Confirm your authentication credentials.",
371-
)
372-
373-
if status_code == 403:
374-
return False, "403 codes are not retried"
375-
376402
# Request failed and server said NotImplemented. This isn't recoverable. Don't retry.
377403
if status_code == 501:
378404
return False, "Received code 501 from server."
@@ -381,50 +407,26 @@ def should_retry(self, method: str, status_code: int) -> Tuple[bool, str]:
381407
if not self._is_method_retryable(method):
382408
return False, "Only POST requests are retried"
383409

384-
# Request failed with 404 and was a GetOperationStatus. This is not recoverable. Don't retry.
385-
if status_code == 404 and self.command_type == CommandType.GET_OPERATION_STATUS:
386-
return (
387-
False,
388-
"GetOperationStatus received 404 code from Databricks. Operation was canceled.",
389-
)
390-
391-
# Request failed with 404 because CloseSession returns 404 if you repeat the request.
392-
if (
393-
status_code == 404
394-
and self.command_type == CommandType.CLOSE_SESSION
395-
and len(self.history) > 0
396-
):
397-
raise SessionAlreadyClosedError(
398-
"CloseSession received 404 code from Databricks. Session is already closed."
399-
)
400-
401-
# Request failed with 404 because CloseOperation returns 404 if you repeat the request.
402-
if (
403-
status_code == 404
404-
and self.command_type == CommandType.CLOSE_OPERATION
405-
and len(self.history) > 0
406-
):
407-
raise CursorAlreadyClosedError(
408-
"CloseOperation received 404 code from Databricks. Cursor is already closed."
409-
)
410+
# Get command idempotency for use in multiple conditions below
411+
command_idempotency = COMMAND_IDEMPOTENCY_MAP.get(self.command_type, CommandIdempotency.IDEMPOTENT)
410412

411-
# Request failed, was an ExecuteStatement and the command may have reached the server
413+
# Request failed, was a non-idempotent command and the command may have reached the server
412414
if (
413-
self.command_type == CommandType.EXECUTE_STATEMENT
415+
command_idempotency == CommandIdempotency.NON_IDEMPOTENT
414416
and status_code not in self.status_forcelist
415417
and status_code not in self.force_dangerous_codes
416418
):
417419
return (
418420
False,
419-
"ExecuteStatement command can only be retried for codes 429 and 503",
421+
f"{self.command_type.value} command can only be retried for codes 429 and 503",
420422
)
421423

422-
# Request failed with a dangerous code, was an ExecuteStatement, but user forced retries for this
424+
# Request failed with a dangerous code, was a non-idempotent command, but user forced retries for this
423425
# dangerous code. Note that these lines _are not required_ to make these requests retry. They would
424426
# retry automatically. This code is included only so that we can log the exact reason for the retry.
425427
# This gives users signal that their _retry_dangerous_codes setting actually did something.
426428
if (
427-
self.command_type == CommandType.EXECUTE_STATEMENT
429+
command_idempotency == CommandIdempotency.NON_IDEMPOTENT
428430
and status_code in self.force_dangerous_codes
429431
):
430432
return (

0 commit comments

Comments
 (0)