Skip to content

Commit 0206a1c

Browse files
Snow 1747564 econnreset error should be retried (#2547)
1 parent 84fb3ed commit 0206a1c

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

DESCRIPTION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1111
- Added the `workload_identity_impersonation_path` parameter to support service account impersonation for Workload Identity Federation on GCP and AWS workloads only
1212
- Fixed `get_results_from_sfqid` when using `DictCursor` and executing multiple statements at once
1313
- Added the `oauth_credentials_in_body` parameter supporting an option to send the oauth client credentials in the request body
14+
- Fix retry behavior for `ECONNRESET` error
1415

1516
- v3.17.4(September 22,2025)
1617
- Added support for intermediate certificates as roots when they are stored in the trust store

src/snowflake/connector/network.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ def is_login_request(url: str) -> bool:
238238
return "login-request" in parse_url(url).path
239239

240240

241+
def is_econnreset_exception(e: Exception) -> bool:
242+
return "ECONNRESET" in repr(e)
243+
244+
241245
class RetryRequest(Exception):
242246
"""Signal to retry request."""
243247

@@ -965,7 +969,7 @@ def _request_exec_wrapper(
965969
)
966970
retry_ctx.retry_reason = reason
967971

968-
if "Connection aborted" in repr(e) and "ECONNRESET" in repr(e):
972+
if is_econnreset_exception(e):
969973
# connection is reset by the server, the underlying connection is broken and can not be reused
970974
# we need a new urllib3 http(s) connection in this case.
971975
# We need to first close the old one so that urllib3 pool manager can create a new connection
@@ -1146,6 +1150,8 @@ def _request_exec(
11461150
finally:
11471151
raw_ret.close() # ensure response is closed
11481152
except SSLError as se:
1153+
if is_econnreset_exception(se):
1154+
raise RetryRequest(se)
11491155
msg = f"Hit non-retryable SSL error, {str(se)}.\n{_CONNECTIVITY_ERR_MSG}"
11501156
logger.debug(msg)
11511157
# the following code is for backward compatibility with old versions of python connector which calls

test/unit/test_retry_network.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@
5151
try:
5252
import snowflake.connector.vendored.urllib3.contrib.pyopenssl
5353
from snowflake.connector.vendored import requests, urllib3
54+
from snowflake.connector.vendored.requests.exceptions import SSLError
5455
except ImportError: # pragma: no cover
5556
import requests
5657
import urllib3
58+
from requests.exceptions import SSLError
5759

5860
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
5961

@@ -477,3 +479,58 @@ def test_retry_request_timeout(mockSessionRequest, next_action_result):
477479
# 13 seconds should be enough for authenticator to attempt thrice
478480
# however, loosen restrictions to avoid thread scheduling causing failure
479481
assert 1 < mockSessionRequest.call_count < 5
482+
483+
484+
def test_sslerror_with_econnreset_retries():
485+
"""Test that SSLError with ECONNRESET raises RetryRequest."""
486+
connection = mock_connection()
487+
connection.errorhandler = Error.default_errorhandler
488+
rest = SnowflakeRestful(
489+
host="testaccount.snowflakecomputing.com",
490+
port=443,
491+
connection=connection,
492+
)
493+
494+
default_parameters = {
495+
"method": "POST",
496+
"full_url": "https://testaccount.snowflakecomputing.com/",
497+
"headers": {},
498+
"data": '{"code": 12345}',
499+
"token": None,
500+
}
501+
502+
# Test SSLError with ECONNRESET in the message
503+
econnreset_ssl_error = SSLError("Connection broken: ECONNRESET")
504+
session = MagicMock()
505+
session.request = Mock(side_effect=econnreset_ssl_error)
506+
507+
with pytest.raises(RetryRequest, match="Connection broken: ECONNRESET"):
508+
rest._request_exec(session=session, **default_parameters)
509+
510+
511+
def test_sslerror_without_econnreset_does_not_retry():
512+
"""Test that SSLError without ECONNRESET does not retry but raises OperationalError."""
513+
connection = mock_connection()
514+
connection.errorhandler = Error.default_errorhandler
515+
rest = SnowflakeRestful(
516+
host="testaccount.snowflakecomputing.com",
517+
port=443,
518+
connection=connection,
519+
)
520+
521+
default_parameters = {
522+
"method": "POST",
523+
"full_url": "https://testaccount.snowflakecomputing.com/",
524+
"headers": {},
525+
"data": '{"code": 12345}',
526+
"token": None,
527+
}
528+
529+
# Test SSLError without ECONNRESET in the message
530+
regular_ssl_error = SSLError("SSL handshake failed")
531+
session = MagicMock()
532+
session.request = Mock(side_effect=regular_ssl_error)
533+
534+
# This should raise OperationalError, not RetryRequest
535+
with pytest.raises(OperationalError):
536+
rest._request_exec(session=session, **default_parameters)

0 commit comments

Comments
 (0)