@@ -16,6 +16,7 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
1616 const py::dict& attrs_before) {
1717 std::vector<std::shared_ptr<Connection>> to_disconnect;
1818 std::shared_ptr<Connection> valid_conn = nullptr ;
19+ bool needs_connect = false ;
1920 {
2021 std::lock_guard<std::mutex> lock (_mutex);
2122 auto now = std::chrono::steady_clock::now ();
@@ -57,16 +58,33 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
5758 }
5859 }
5960
60- // Create new connection if none reusable
61+ // Reserve a slot for a new connection if none reusable.
62+ // The actual connect() call happens outside the mutex to avoid
63+ // holding the mutex during the blocking ODBC call (which releases
64+ // the GIL and could otherwise cause a mutex/GIL deadlock).
6165 if (!valid_conn && _current_size < _max_size) {
6266 valid_conn = std::make_shared<Connection>(connStr, true );
63- valid_conn->connect (attrs_before);
6467 ++_current_size;
68+ needs_connect = true ;
6569 } else if (!valid_conn) {
6670 throw std::runtime_error (" ConnectionPool::acquire: pool size limit reached" );
6771 }
6872 }
6973
74+ // Phase 2.5: Connect the new connection outside the mutex.
75+ if (needs_connect) {
76+ try {
77+ valid_conn->connect (attrs_before);
78+ } catch (...) {
79+ // Connect failed — release the reserved slot
80+ {
81+ std::lock_guard<std::mutex> lock (_mutex);
82+ if (_current_size > 0 ) --_current_size;
83+ }
84+ throw ;
85+ }
86+ }
87+
7088 // Phase 3: Disconnect expired/bad connections outside lock
7189 for (auto & conn : to_disconnect) {
7290 try {
@@ -79,12 +97,25 @@ std::shared_ptr<Connection> ConnectionPool::acquire(const std::wstring& connStr,
7997}
8098
8199void ConnectionPool::release (std::shared_ptr<Connection> conn) {
82- std::lock_guard<std::mutex> lock (_mutex);
83- if (_pool.size () < _max_size) {
84- conn->updateLastUsed ();
85- _pool.push_back (conn);
86- } else {
87- conn->disconnect ();
100+ bool should_disconnect = false ;
101+ {
102+ std::lock_guard<std::mutex> lock (_mutex);
103+ if (_pool.size () < _max_size) {
104+ conn->updateLastUsed ();
105+ _pool.push_back (conn);
106+ } else {
107+ should_disconnect = true ;
108+ }
109+ }
110+ // Disconnect outside the mutex to avoid holding it during the
111+ // blocking ODBC call (which releases the GIL).
112+ if (should_disconnect) {
113+ try {
114+ conn->disconnect ();
115+ } catch (const std::exception& ex) {
116+ LOG (" ConnectionPool::release: disconnect failed: %s" , ex.what ());
117+ }
118+ std::lock_guard<std::mutex> lock (_mutex);
88119 if (_current_size > 0 )
89120 --_current_size;
90121 }
@@ -116,21 +147,35 @@ ConnectionPoolManager& ConnectionPoolManager::getInstance() {
116147
117148std::shared_ptr<Connection> ConnectionPoolManager::acquireConnection (const std::wstring& connStr,
118149 const py::dict& attrs_before) {
119- std::lock_guard<std::mutex> lock (_manager_mutex);
120-
121- auto & pool = _pools[connStr];
122- if (!pool) {
123- LOG (" Creating new connection pool" );
124- pool = std::make_shared<ConnectionPool>(_default_max_size, _default_idle_secs);
150+ std::shared_ptr<ConnectionPool> pool;
151+ {
152+ std::lock_guard<std::mutex> lock (_manager_mutex);
153+ auto & pool_ref = _pools[connStr];
154+ if (!pool_ref) {
155+ LOG (" Creating new connection pool" );
156+ pool_ref = std::make_shared<ConnectionPool>(_default_max_size, _default_idle_secs);
157+ }
158+ pool = pool_ref;
125159 }
160+ // Call acquire() outside _manager_mutex. acquire() may release the GIL
161+ // during the ODBC connect call; holding _manager_mutex across that would
162+ // create a mutex/GIL lock-ordering deadlock.
126163 return pool->acquire (connStr, attrs_before);
127164}
128165
129166void ConnectionPoolManager::returnConnection (const std::wstring& conn_str,
130167 const std::shared_ptr<Connection> conn) {
131- std::lock_guard<std::mutex> lock (_manager_mutex);
132- if (_pools.find (conn_str) != _pools.end ()) {
133- _pools[conn_str]->release ((conn));
168+ std::shared_ptr<ConnectionPool> pool;
169+ {
170+ std::lock_guard<std::mutex> lock (_manager_mutex);
171+ auto it = _pools.find (conn_str);
172+ if (it != _pools.end ()) {
173+ pool = it->second ;
174+ }
175+ }
176+ // Call release() outside _manager_mutex to avoid deadlock.
177+ if (pool) {
178+ pool->release (conn);
134179 }
135180}
136181
@@ -142,6 +187,9 @@ void ConnectionPoolManager::configure(int max_size, int idle_timeout_secs) {
142187
143188void ConnectionPoolManager::closePools () {
144189 std::lock_guard<std::mutex> lock (_manager_mutex);
190+ // Keep _manager_mutex held for the full close operation so that
191+ // acquireConnection()/returnConnection() cannot create or use pools
192+ // while closePools() is in progress.
145193 for (auto & [conn_str, pool] : _pools) {
146194 if (pool) {
147195 pool->close ();
0 commit comments