Skip to content

Commit c02e6cb

Browse files
authored
SNOW-469340 add _no_retry option when submitting queries (#879)
1 parent 36c0941 commit c02e6cb

File tree

4 files changed

+51
-18
lines changed

4 files changed

+51
-18
lines changed

src/snowflake/connector/connection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,7 @@ def cmd_query(
913913
describe_only: bool = False,
914914
_no_results: bool = False,
915915
_update_current_object: bool = True,
916+
_no_retry: bool = False,
916917
):
917918
"""Executes a query with a sequence counter."""
918919
logger.debug("_cmd_query")
@@ -953,6 +954,7 @@ def cmd_query(
953954
client=client,
954955
_no_results=_no_results,
955956
_include_retry_params=True,
957+
_no_retry=_no_retry,
956958
)
957959

958960
if ret is None:

src/snowflake/connector/cursor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ def _execute_helper(
408408
describe_only: bool = False,
409409
_no_results: bool = False,
410410
_is_put_get=None,
411+
_no_retry: bool = False,
411412
):
412413
del self.messages[:]
413414

@@ -512,6 +513,7 @@ def interrupt_handler(*_): # pragma: no cover
512513
is_internal=is_internal,
513514
describe_only=describe_only,
514515
_no_results=_no_results,
516+
_no_retry=_no_retry,
515517
)
516518
finally:
517519
try:
@@ -561,6 +563,7 @@ def execute(
561563
_bind_stage: str | None = None,
562564
timeout: int | None = None,
563565
_exec_async: bool = False,
566+
_no_retry: bool = False,
564567
_do_reset: bool = True,
565568
_put_callback: SnowflakeProgressPercentage = None,
566569
_put_azure_callback: SnowflakeProgressPercentage = None,
@@ -587,6 +590,7 @@ def execute(
587590
_bind_stage: Path in temporary stage where binding parameters are uploaded as CSV files.
588591
timeout: Number of seconds after which to abort the query.
589592
_exec_async: Whether to execute this query asynchronously.
593+
_no_retry: Whether or not to retry on known errors.
590594
_do_reset: Whether or not the result set needs to be reset before executing query.
591595
_put_callback: Function to which GET command should call back to.
592596
_put_azure_callback: Function to which an Azure GET command should call back to.
@@ -636,6 +640,7 @@ def execute(
636640
"describe_only": _describe_only,
637641
"_no_results": _no_results,
638642
"_is_put_get": _is_put_get,
643+
"_no_retry": _no_retry,
639644
}
640645

641646
if self._connection.is_pyformat:

src/snowflake/connector/network.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ def request(
431431
_no_results=False,
432432
timeout=None,
433433
_include_retry_params=False,
434+
_no_retry=False,
434435
):
435436
if body is None:
436437
body = {}
@@ -470,6 +471,7 @@ def request(
470471
_no_results=_no_results,
471472
timeout=timeout,
472473
_include_retry_params=_include_retry_params,
474+
no_retry=_no_retry,
473475
)
474476
else:
475477
return self._get_request(url, headers, token=self.token, timeout=timeout)
@@ -871,28 +873,16 @@ def _request_exec_wrapper(
871873
exception=str(e),
872874
stack_trace=traceback.format_exc(),
873875
)
874-
if no_retry:
875-
return {}
876876
cause = e.args[0]
877+
if no_retry:
878+
self.log_and_handle_http_error_with_cause(e, full_url, method, retry_ctx.total_timeout, retry_ctx.cnt,
879+
conn, timed_out=False)
880+
return {} # required for tests
877881
if retry_ctx.timeout is not None:
878882
retry_ctx.timeout -= int(time.time() - start_request_thread)
879883
if retry_ctx.timeout <= 0:
880-
logger.error(cause, exc_info=True)
881-
TelemetryService.get_instance().log_http_request_error(
882-
"HttpRequestRetryTimeout",
883-
full_url,
884-
method,
885-
SQLSTATE_IO_ERROR,
886-
ER_FAILED_TO_REQUEST,
887-
retry_timeout=retry_ctx.total_timeout,
888-
retry_count=retry_ctx.cnt,
889-
exception=str(e),
890-
stack_trace=traceback.format_exc(),
891-
)
892-
if isinstance(cause, Error):
893-
Error.errorhandler_wrapper_from_cause(conn, cause)
894-
else:
895-
self.handle_invalid_certificate_error(conn, full_url, cause)
884+
self.log_and_handle_http_error_with_cause(e, full_url, method, retry_ctx.total_timeout,
885+
retry_ctx.cnt, conn)
896886
return {} # required for tests
897887
sleeping_time = retry_ctx.next_sleep()
898888
logger.debug(
@@ -916,6 +906,34 @@ def _request_exec_wrapper(
916906
logger.debug("Ignored error", exc_info=True)
917907
return {}
918908

909+
def log_and_handle_http_error_with_cause(
910+
self,
911+
e: Exception,
912+
full_url: str,
913+
method: str,
914+
retry_timeout: int,
915+
retry_count: int,
916+
conn: SnowflakeConnection,
917+
timed_out: bool = True
918+
) -> None:
919+
cause = e.args[0]
920+
logger.error(cause, exc_info=True)
921+
TelemetryService.get_instance().log_http_request_error(
922+
"HttpRequestRetryTimeout" if timed_out else f"HttpRequestError: {cause}",
923+
full_url,
924+
method,
925+
SQLSTATE_IO_ERROR,
926+
ER_FAILED_TO_REQUEST,
927+
retry_timeout=retry_timeout,
928+
retry_count=retry_count,
929+
exception=str(e),
930+
stack_trace=traceback.format_exc(),
931+
)
932+
if isinstance(cause, Error):
933+
Error.errorhandler_wrapper_from_cause(conn, cause)
934+
else:
935+
self.handle_invalid_certificate_error(conn, full_url, cause)
936+
919937
def handle_invalid_certificate_error(self, conn, full_url, cause):
920938
# all other errors raise exception
921939
Error.errorhandler_wrapper(

test/unit/test_retry_network.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,11 @@ def fake_request_exec(**kwargs):
209209
cnt.set(NOT_RETRYABLE)
210210
with pytest.raises(NotRetryableException):
211211
rest.fetch(timeout=7, **default_parameters)
212+
213+
# first attempt fails and will not retry
214+
cnt.reset()
215+
default_parameters["no_retry"] = True
216+
ret = rest.fetch(timeout=10, **default_parameters)
217+
assert ret == {}
218+
assert cnt.c == 1 # failed on first call - did not retry
219+
assert rest._connection.errorhandler.called # error

0 commit comments

Comments
 (0)