Skip to content

Commit 8d70f6c

Browse files
author
Jesse
authored
Don't raise exception when closing a stale Thrift session (databricks#159)
Signed-off-by: Jesse Whitehouse <[email protected]>
1 parent fecfa88 commit 8d70f6c

File tree

7 files changed

+55
-3
lines changed

7 files changed

+55
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 2.6.x (Unreleased)
44

5+
- Fix: connector raised exception when calling close() on a closed Thrift session
56
- Improve e2e test development ergonomics
67
- Redact logged thrift responses by default
78
- Add support for OAuth on Databricks Azure

CONTRIBUTING.md

+2
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ access_token="dapi***"
118118
staging_ingestion_user="***@example.com"
119119
```
120120

121+
To see logging output from pytest while running tests, set `log_cli = "true"` under `tool.pytest.ini_options` in `pyproject.toml`. You can also set `log_cli_level` to any of the default Python log levels: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
122+
121123
There are several e2e test suites available:
122124
- `PySQLCoreTestSuite`
123125
- `PySQLLargeQueriesSuite`

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ exclude = '/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck
5454

5555
[tool.pytest.ini_options]
5656
minversion = "6.0"
57+
log_cli = "false"
58+
log_cli_level = "INFO"
5759
testpaths = [
5860
"tests"
5961
]

src/databricks/sql/client.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def read(self) -> Optional[OAuthToken]:
190190
session_configuration, catalog, schema
191191
)
192192
self.open = True
193-
logger.info("Successfully opened session " + str(self.get_session_id()))
193+
logger.info("Successfully opened session " + str(self.get_session_id_hex()))
194194
self._cursors = [] # type: List[Cursor]
195195

196196
def __enter__(self):
@@ -214,6 +214,9 @@ def __del__(self):
214214
def get_session_id(self):
215215
return self.thrift_backend.handle_to_id(self._session_handle)
216216

217+
def get_session_id_hex(self):
218+
return self.thrift_backend.handle_to_hex_id(self._session_handle)
219+
217220
def cursor(
218221
self,
219222
arraysize: int = DEFAULT_ARRAY_SIZE,
@@ -244,7 +247,25 @@ def _close(self, close_cursors=True) -> None:
244247
if close_cursors:
245248
for cursor in self._cursors:
246249
cursor.close()
247-
self.thrift_backend.close_session(self._session_handle)
250+
251+
logger.info(f"Closing session {self.get_session_id_hex()}")
252+
if not self.open:
253+
logger.debug("Session appears to have been closed already")
254+
255+
try:
256+
self.thrift_backend.close_session(self._session_handle)
257+
except DatabaseError as e:
258+
if "Invalid SessionHandle" in str(e):
259+
logger.warning(
260+
f"Attempted to close session that was already closed: {e}"
261+
)
262+
else:
263+
logger.warning(
264+
f"Attempt to close session raised an exception at the server: {e}"
265+
)
266+
except Exception as e:
267+
logger.error(f"Attempt to close session raised a local exception: {e}")
268+
248269
self.open = False
249270

250271
def commit(self):

src/databricks/sql/thrift_backend.py

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import math
55
import time
6+
import uuid
67
import threading
78
import lz4.frame
89
from ssl import CERT_NONE, CERT_REQUIRED, create_default_context
@@ -1021,3 +1022,8 @@ def cancel_command(self, active_op_handle):
10211022
@staticmethod
10221023
def handle_to_id(session_handle):
10231024
return session_handle.sessionId.guid
1025+
1026+
@staticmethod
1027+
def handle_to_hex_id(session_handle: TCLIService.TSessionHandle):
1028+
this_uuid = uuid.UUID(bytes=session_handle.sessionId.guid)
1029+
return str(this_uuid)

test.env.example

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ http_path=""
44
access_token=""
55

66
# Only required to run the PySQLStagingIngestionTestSuite
7-
staging_ingestion_user=""
7+
staging_ingestion_user=""
8+
9+
# Only required to run SQLAlchemy tests
10+
catalog=""
11+
schema=""

tests/e2e/test_driver.py

+16
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,22 @@ def test_close_connection_closes_cursors(self):
616616
assert "RESOURCE_DOES_NOT_EXIST" in cm.exception.message
617617

618618

619+
def test_closing_a_closed_connection_doesnt_fail(self):
620+
621+
with self.assertLogs("databricks.sql", level="DEBUG",) as cm:
622+
# Second .close() call is when this context manager exits
623+
with self.connection() as conn:
624+
# First .close() call is explicit here
625+
conn.close()
626+
627+
expected_message_was_found = False
628+
for log in cm.output:
629+
if expected_message_was_found:
630+
break
631+
target = "Session appears to have been closed already"
632+
expected_message_was_found = target in log
633+
634+
self.assertTrue(expected_message_was_found, "Did not find expected log messages")
619635

620636

621637
# use a RetrySuite to encapsulate these tests which we'll typically want to run together; however keep

0 commit comments

Comments
 (0)