Skip to content

Commit 8e2b757

Browse files
authored
SNOW-802436: improve error message for sql execution cancellation due to timeout (#2073)
1 parent b55531b commit 8e2b757

File tree

5 files changed

+49
-4
lines changed

5 files changed

+49
-4
lines changed

DESCRIPTION.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
88

99
# Release Notes
1010

11+
- v3.12.3(TBD)
12+
- Improved error message for SQL execution cancellations caused by timeout.
13+
1114
- v3.12.2(September 11,2024)
1215
- Improved error handling for asynchronous queries, providing more detailed and informative error messages when an async query fails.
1316
- Improved inference of top-level domains for accounts specifying a region in China, now defaulting to snowflakecomputing.cn.

src/snowflake/connector/_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import string
88
from enum import Enum
99
from random import choice
10+
from threading import Timer
1011

1112

1213
class TempObjectType(Enum):
@@ -43,3 +44,13 @@ def random_name_for_temp_object(object_type: TempObjectType) -> str:
4344

4445
def get_temp_type_for_object(use_scoped_temp_objects: bool) -> str:
4546
return SCOPED_TEMPORARY_STRING if use_scoped_temp_objects else TEMPORARY_STRING
47+
48+
49+
class _TrackedQueryCancellationTimer(Timer):
50+
def __init__(self, interval, function, args=None, kwargs=None):
51+
super().__init__(interval, function, args, kwargs)
52+
self.executed = False
53+
54+
def run(self):
55+
super().run()
56+
self.executed = True

src/snowflake/connector/cursor.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import warnings
1717
from enum import Enum
1818
from logging import getLogger
19-
from threading import Lock, Timer
19+
from threading import Lock
2020
from types import TracebackType
2121
from typing import (
2222
IO,
@@ -39,6 +39,7 @@
3939

4040
from . import compat
4141
from ._sql_util import get_file_transfer_type
42+
from ._utils import _TrackedQueryCancellationTimer
4243
from .bind_upload_agent import BindUploadAgent, BindUploadError
4344
from .constants import (
4445
FIELD_NAME_TO_ID,
@@ -392,7 +393,9 @@ def __init__(
392393
self.messages: list[
393394
tuple[type[Error] | type[Exception], dict[str, str | bool]]
394395
] = []
395-
self._timebomb: Timer | None = None # must be here for abort_exit method
396+
self._timebomb: _TrackedQueryCancellationTimer | None = (
397+
None # must be here for abort_exit method
398+
)
396399
self._description: list[ResultMetadataV2] | None = None
397400
self._sfqid: str | None = None
398401
self._sqlstate = None
@@ -654,7 +657,9 @@ def _execute_helper(
654657
)
655658

656659
if real_timeout is not None:
657-
self._timebomb = Timer(real_timeout, self.__cancel_query, [query])
660+
self._timebomb = _TrackedQueryCancellationTimer(
661+
real_timeout, self.__cancel_query, [query]
662+
)
658663
self._timebomb.start()
659664
logger.debug("started timebomb in %ss", real_timeout)
660665
else:
@@ -1071,6 +1076,11 @@ def execute(
10711076
logger.debug(ret)
10721077
err = ret["message"]
10731078
code = ret.get("code", -1)
1079+
if self._timebomb and self._timebomb.executed:
1080+
err = (
1081+
f"SQL execution was cancelled by the client due to a timeout. "
1082+
f"Error message received from the server: {err}"
1083+
)
10741084
if "data" in ret:
10751085
err += ret["data"].get("errorMessage", "")
10761086
errvalue = {

test/integ/test_cursor.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,11 @@ def test_timeout_query(conn_cnx):
767767
"select seq8() as c1 from table(generator(timeLimit => 60))",
768768
timeout=5,
769769
)
770-
assert err.value.errno == 604, "Invalid error code"
770+
assert err.value.errno == 604, (
771+
"Invalid error code"
772+
and "SQL execution was cancelled by the client due to a timeout"
773+
in err.value.msg
774+
)
771775

772776

773777
def test_executemany(conn, db_parameters):

test/unit/test_util.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#
2+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
3+
#
4+
5+
from snowflake.connector._utils import _TrackedQueryCancellationTimer
6+
7+
8+
def test_timer():
9+
timer = _TrackedQueryCancellationTimer(1, lambda: None)
10+
timer.start()
11+
timer.join()
12+
assert timer.executed
13+
14+
timer = _TrackedQueryCancellationTimer(1, lambda: None)
15+
timer.start()
16+
timer.cancel()
17+
assert not timer.executed

0 commit comments

Comments
 (0)