Skip to content

Commit

Permalink
SNOW-802436: improve error message for sql execution cancellation due…
Browse files Browse the repository at this point in the history
… to timeout (#2073)
  • Loading branch information
sfc-gh-aling authored Oct 21, 2024
1 parent b55531b commit 8e2b757
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 4 deletions.
3 changes: 3 additions & 0 deletions DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne

# Release Notes

- v3.12.3(TBD)
- Improved error message for SQL execution cancellations caused by timeout.

- v3.12.2(September 11,2024)
- Improved error handling for asynchronous queries, providing more detailed and informative error messages when an async query fails.
- Improved inference of top-level domains for accounts specifying a region in China, now defaulting to snowflakecomputing.cn.
Expand Down
11 changes: 11 additions & 0 deletions src/snowflake/connector/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import string
from enum import Enum
from random import choice
from threading import Timer


class TempObjectType(Enum):
Expand Down Expand Up @@ -43,3 +44,13 @@ def random_name_for_temp_object(object_type: TempObjectType) -> str:

def get_temp_type_for_object(use_scoped_temp_objects: bool) -> str:
return SCOPED_TEMPORARY_STRING if use_scoped_temp_objects else TEMPORARY_STRING


class _TrackedQueryCancellationTimer(Timer):
def __init__(self, interval, function, args=None, kwargs=None):
super().__init__(interval, function, args, kwargs)
self.executed = False

def run(self):
super().run()
self.executed = True
16 changes: 13 additions & 3 deletions src/snowflake/connector/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import warnings
from enum import Enum
from logging import getLogger
from threading import Lock, Timer
from threading import Lock
from types import TracebackType
from typing import (
IO,
Expand All @@ -39,6 +39,7 @@

from . import compat
from ._sql_util import get_file_transfer_type
from ._utils import _TrackedQueryCancellationTimer
from .bind_upload_agent import BindUploadAgent, BindUploadError
from .constants import (
FIELD_NAME_TO_ID,
Expand Down Expand Up @@ -392,7 +393,9 @@ def __init__(
self.messages: list[
tuple[type[Error] | type[Exception], dict[str, str | bool]]
] = []
self._timebomb: Timer | None = None # must be here for abort_exit method
self._timebomb: _TrackedQueryCancellationTimer | None = (
None # must be here for abort_exit method
)
self._description: list[ResultMetadataV2] | None = None
self._sfqid: str | None = None
self._sqlstate = None
Expand Down Expand Up @@ -654,7 +657,9 @@ def _execute_helper(
)

if real_timeout is not None:
self._timebomb = Timer(real_timeout, self.__cancel_query, [query])
self._timebomb = _TrackedQueryCancellationTimer(
real_timeout, self.__cancel_query, [query]
)
self._timebomb.start()
logger.debug("started timebomb in %ss", real_timeout)
else:
Expand Down Expand Up @@ -1071,6 +1076,11 @@ def execute(
logger.debug(ret)
err = ret["message"]
code = ret.get("code", -1)
if self._timebomb and self._timebomb.executed:
err = (
f"SQL execution was cancelled by the client due to a timeout. "
f"Error message received from the server: {err}"
)
if "data" in ret:
err += ret["data"].get("errorMessage", "")
errvalue = {
Expand Down
6 changes: 5 additions & 1 deletion test/integ/test_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,11 @@ def test_timeout_query(conn_cnx):
"select seq8() as c1 from table(generator(timeLimit => 60))",
timeout=5,
)
assert err.value.errno == 604, "Invalid error code"
assert err.value.errno == 604, (
"Invalid error code"
and "SQL execution was cancelled by the client due to a timeout"
in err.value.msg
)


def test_executemany(conn, db_parameters):
Expand Down
17 changes: 17 additions & 0 deletions test/unit/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
#

from snowflake.connector._utils import _TrackedQueryCancellationTimer


def test_timer():
timer = _TrackedQueryCancellationTimer(1, lambda: None)
timer.start()
timer.join()
assert timer.executed

timer = _TrackedQueryCancellationTimer(1, lambda: None)
timer.start()
timer.cancel()
assert not timer.executed

0 comments on commit 8e2b757

Please sign in to comment.