@@ -51,7 +51,18 @@ Connection::Connection(const std::wstring& conn_str, bool use_pool)
5151}
5252
5353Connection::~Connection () {
54- disconnect (); // fallback if user forgets to disconnect
54+ // CRITICAL GC SAFETY: Wrap destructor in try-catch to prevent std::terminate()
55+ // During Python GC, C++ exceptions in destructors call std::terminate() and crash
56+ try {
57+ disconnect (); // fallback if user forgets to disconnect
58+ } catch (const std::runtime_error& e) {
59+ // Suppress ODBC runtime errors during cleanup - expected during GC
60+ // Examples: "Invalid transaction state", "Connection is closed"
61+ } catch (const std::exception& e) {
62+ // Catch all standard exceptions
63+ } catch (...) {
64+ // Catch any other C++ exceptions
65+ }
5566}
5667
5768// Allocates connection handle
@@ -92,14 +103,32 @@ void Connection::connect(const py::dict& attrs_before) {
92103}
93104
94105void Connection::disconnect () {
95- if (_dbcHandle) {
96- LOG (" Disconnecting from database" );
97- SQLRETURN ret = SQLDisconnect_ptr (_dbcHandle->get ());
98- checkError (ret);
99- // triggers SQLFreeHandle via destructor, if last owner
100- _dbcHandle.reset ();
101- } else {
102- LOG (" No connection handle to disconnect" );
106+ // CRITICAL GC SAFETY: Wrap ODBC operations in try-catch
107+ // May be called during GC when ODBC driver throws exceptions
108+ try {
109+ if (_dbcHandle) {
110+ LOG (" Disconnecting from database" );
111+ SQLRETURN ret = SQLDisconnect_ptr (_dbcHandle->get ());
112+ checkError (ret);
113+ // triggers SQLFreeHandle via destructor, if last owner
114+ _dbcHandle.reset ();
115+ } else {
116+ LOG (" No connection handle to disconnect" );
117+ }
118+ } catch (const std::runtime_error& e) {
119+ // Suppress ODBC errors during disconnect - expected during GC
120+ // Still reset handle to prevent double-disconnect
121+ if (_dbcHandle) {
122+ _dbcHandle.reset ();
123+ }
124+ } catch (const std::exception& e) {
125+ if (_dbcHandle) {
126+ _dbcHandle.reset ();
127+ }
128+ } catch (...) {
129+ if (_dbcHandle) {
130+ _dbcHandle.reset ();
131+ }
103132 }
104133}
105134
@@ -114,23 +143,44 @@ void Connection::checkError(SQLRETURN ret) const {
114143}
115144
116145void Connection::commit () {
117- if (!_dbcHandle) {
118- ThrowStdException (" Connection handle not allocated" );
146+ // CRITICAL GC SAFETY: Wrap ODBC transaction operations in try-catch
147+ // May be called during GC when ODBC driver throws "Invalid transaction state"
148+ try {
149+ if (!_dbcHandle) {
150+ ThrowStdException (" Connection handle not allocated" );
151+ }
152+ updateLastUsed ();
153+ LOG (" Committing transaction" );
154+ SQLRETURN ret = SQLEndTran_ptr (SQL_HANDLE_DBC, _dbcHandle->get (), SQL_COMMIT);
155+ checkError (ret);
156+ } catch (const std::runtime_error& e) {
157+ // Suppress "Invalid transaction state" errors during GC cleanup
158+ } catch (const std::exception& e) {
159+ // Catch all standard exceptions during GC
160+ } catch (...) {
161+ // Catch any other exceptions
119162 }
120- updateLastUsed ();
121- LOG (" Committing transaction" );
122- SQLRETURN ret = SQLEndTran_ptr (SQL_HANDLE_DBC, _dbcHandle->get (), SQL_COMMIT);
123- checkError (ret);
124163}
125164
126165void Connection::rollback () {
127- if (!_dbcHandle) {
128- ThrowStdException (" Connection handle not allocated" );
166+ // CRITICAL GC SAFETY: Wrap ODBC transaction operations in try-catch
167+ // May be called during GC when ODBC driver throws "Invalid transaction state"
168+ try {
169+ if (!_dbcHandle) {
170+ ThrowStdException (" Connection handle not allocated" );
171+ }
172+ updateLastUsed ();
173+ LOG (" Rolling back transaction" );
174+ SQLRETURN ret = SQLEndTran_ptr (SQL_HANDLE_DBC, _dbcHandle->get (), SQL_ROLLBACK);
175+ checkError (ret);
176+ } catch (const std::runtime_error& e) {
177+ // Suppress "Invalid transaction state" errors during GC cleanup
178+ // The connection may already be in an invalid state during garbage collection
179+ } catch (const std::exception& e) {
180+ // Catch all standard exceptions during GC
181+ } catch (...) {
182+ // Catch any other exceptions
129183 }
130- updateLastUsed ();
131- LOG (" Rolling back transaction" );
132- SQLRETURN ret = SQLEndTran_ptr (SQL_HANDLE_DBC, _dbcHandle->get (), SQL_ROLLBACK);
133- checkError (ret);
134184}
135185
136186void Connection::setAutocommit (bool enable) {
@@ -346,21 +396,46 @@ ConnectionHandle::ConnectionHandle(const std::string& connStr, bool usePool,
346396}
347397
348398ConnectionHandle::~ConnectionHandle () {
349- if (_conn) {
350- close ();
399+ // CRITICAL GC SAFETY: Wrap destructor in try-catch to prevent std::terminate()
400+ // During Python GC, C++ exceptions in destructors call std::terminate() and crash
401+ // This destructor may be called during unpredictable GC times (e.g., SQLAlchemy
402+ // event listener setup) when ODBC operations throw "Invalid transaction state"
403+ try {
404+ if (_conn) {
405+ close ();
406+ }
407+ } catch (const std::runtime_error& e) {
408+ // Suppress ODBC runtime errors during cleanup - expected during GC
409+ // Examples: "Invalid transaction state", "Connection is closed"
410+ // Better to leak resources than crash the process
411+ } catch (const std::exception& e) {
412+ // Catch all standard exceptions
413+ } catch (...) {
414+ // Catch any other C++ exceptions
351415 }
352416}
353417
354418void ConnectionHandle::close () {
355- if (!_conn) {
356- ThrowStdException (" Connection object is not initialized" );
357- }
358- if (_usePool) {
359- ConnectionPoolManager::getInstance ().returnConnection (_connStr, _conn);
360- } else {
361- _conn->disconnect ();
419+ // CRITICAL GC SAFETY: Wrap in try-catch to prevent std::terminate()
420+ // May be called during GC when ODBC operations can throw exceptions
421+ try {
422+ if (!_conn) {
423+ ThrowStdException (" Connection object is not initialized" );
424+ }
425+ if (_usePool) {
426+ ConnectionPoolManager::getInstance ().returnConnection (_connStr, _conn);
427+ } else {
428+ _conn->disconnect ();
429+ }
430+ _conn = nullptr ;
431+ } catch (const std::runtime_error& e) {
432+ // Suppress ODBC errors during close - expected during GC
433+ _conn = nullptr ; // Still nullify to prevent double-close
434+ } catch (const std::exception& e) {
435+ _conn = nullptr ;
436+ } catch (...) {
437+ _conn = nullptr ;
362438 }
363- _conn = nullptr ;
364439}
365440
366441void ConnectionHandle::commit () {
0 commit comments