Skip to content

Commit 72c799b

Browse files
CJStuartsusodapop
authored andcommitted
Add connection finalizer
Adds a finalizer to the PySQL Connection class. This helps mitigate common application errors causing the session to not be closed correctly. Added tests to ensure the finalizer is called when there are no more references to the connection.
1 parent 70f0f15 commit 72c799b

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

cmdexec/clients/python/src/databricks/sql/client.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ def __enter__(self):
110110
def __exit__(self, exc_type, exc_value, traceback):
111111
self.close()
112112

113+
def __del__(self):
114+
if self.open:
115+
logger.debug("Closing unclosed connection for session "
116+
"{}".format(self.get_session_id()))
117+
self._close(close_cursors=False)
118+
113119
def get_session_id(self):
114120
return self.thrift_backend.handle_to_id(self._session_handle)
115121

@@ -134,11 +140,15 @@ def cursor(self,
134140

135141
def close(self) -> None:
136142
"""Close the underlying session and mark all associated cursors as closed."""
143+
self._close()
144+
145+
def _close(self, close_cursors=True) -> None:
137146
self.thrift_backend.close_session(self._session_handle)
138147
self.open = False
139148

140-
for cursor in self._cursors:
141-
cursor.close()
149+
if close_cursors:
150+
for cursor in self._cursors:
151+
cursor.close()
142152

143153
def commit(self):
144154
"""No-op because Databricks does not support transactions"""

cmdexec/clients/python/tests/tests.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import gc
12
import re
23
import sys
34
import unittest
@@ -507,6 +508,34 @@ def test_column_name_api(self):
507508
"third_col": expected[2]
508509
})
509510

511+
@patch("%s.client.ThriftBackend" % PACKAGE_NAME)
512+
def test_finalizer_closes_abandoned_connection(self, mock_client_class):
513+
instance = mock_client_class.return_value
514+
instance.open_session.return_value = b'\x22'
515+
516+
databricks.sql.connect(**self.DUMMY_CONNECTION_ARGS)
517+
518+
# not strictly necessary as the refcount is 0, but just to be sure
519+
gc.collect()
520+
521+
# Check the close session request has an id of x22
522+
close_session_id = instance.close_session.call_args[0][0]
523+
self.assertEqual(close_session_id, b'\x22')
524+
525+
@patch("%s.client.ThriftBackend" % PACKAGE_NAME)
526+
def test_cursor_keeps_connection_alive(self, mock_client_class):
527+
instance = mock_client_class.return_value
528+
instance.open_session.return_value = b'\x22'
529+
530+
connection = databricks.sql.connect(**self.DUMMY_CONNECTION_ARGS)
531+
cursor = connection.cursor()
532+
del connection
533+
534+
gc.collect()
535+
536+
self.assertEqual(instance.close_session.call_count, 0)
537+
cursor.close()
538+
510539

511540
if __name__ == '__main__':
512541
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])

0 commit comments

Comments
 (0)