@@ -34,6 +34,7 @@ static const QString PROVIDER_KEY = QStringLiteral( "DB2" );
34
34
static const QString PROVIDER_DESCRIPTION = QStringLiteral( " DB2 Spatial Extender provider" );
35
35
36
36
int QgsDb2Provider::sConnectionId = 0 ;
37
+ QMutex QgsDb2Provider::sMutex { QMutex::Recursive };
37
38
38
39
QgsDb2Provider::QgsDb2Provider ( const QString &uri, const ProviderOptions &options )
39
40
: QgsVectorDataProvider( uri, options )
@@ -193,17 +194,42 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
193
194
const QString threadSafeConnectionName = dbConnectionName ( connectionName );
194
195
QgsDebugMsg ( " threadSafeConnectionName: " + threadSafeConnectionName );
195
196
197
+ // while everything we use from QSqlDatabase here is thread safe, we need to ensure
198
+ // that the connection cleanup on thread finalization happens in a predictable order
199
+ QMutexLocker locker ( &sMutex );
200
+
196
201
/* if new database connection */
197
202
if ( !QSqlDatabase::contains ( threadSafeConnectionName ) )
198
203
{
199
204
QgsDebugMsg ( QStringLiteral ( " new connection. create new QODBC mapping" ) );
200
205
db = QSqlDatabase::addDatabase ( QStringLiteral ( " QODBC3" ), threadSafeConnectionName );
201
- }
206
+ db.setConnectOptions ( QStringLiteral ( " SQL_ATTR_CONNECTION_POOLING=SQL_CP_ONE_PER_HENV" ) );
207
+
208
+ // for background threads, remove database when current thread finishes
209
+ if ( QThread::currentThread () != QCoreApplication::instance ()->thread () )
210
+ {
211
+ QgsDebugMsgLevel ( QStringLiteral ( " Scheduled auth db remove on thread close" ), 2 );
212
+
213
+ // IMPORTANT - we use a direct connection here, because the database removal must happen immediately
214
+ // when the thread finishes, and we cannot let this get queued on the main thread's event loop.
215
+ // Otherwise, the QSqlDatabase's private data's thread gets reset immediately the QThread::finished,
216
+ // and a subsequent call to QSqlDatabase::database with the same thread address (yep it happens, actually a lot)
217
+ // triggers a condition in QSqlDatabase which detects the nullptr private thread data and returns an invalid database instead.
218
+ // QSqlDatabase::removeDatabase is thread safe, so this is ok to do.
219
+ QObject::connect ( QThread::currentThread (), &QThread::finished, QThread::currentThread (), [connectionName]
220
+ {
221
+ QMutexLocker locker ( &sMutex );
222
+ QSqlDatabase::removeDatabase ( connectionName );
223
+ }, Qt::DirectConnection );
224
+ }
225
+ }
202
226
else /* if existing database connection */
203
227
{
204
228
QgsDebugMsg ( QStringLiteral ( " found existing connection, use the existing one" ) );
205
229
db = QSqlDatabase::database ( threadSafeConnectionName );
206
230
}
231
+ locker.unlock ();
232
+
207
233
db.setHostName ( host );
208
234
db.setPort ( port.toInt () );
209
235
bool connected = false ;
@@ -1759,8 +1785,7 @@ QString QgsDb2Provider::dbConnectionName( const QString &name )
1759
1785
// Starting with Qt 5.11, sharing the same connection between threads is not allowed.
1760
1786
// We use a dedicated connection for each thread requiring access to the database,
1761
1787
// using the thread address as connection name.
1762
- const QString threadAddress = QStringLiteral ( " :0x%1" ).arg ( QString::number ( reinterpret_cast < quintptr >( QThread::currentThread () ), 16 ) );
1763
- return name + threadAddress;
1788
+ return QStringLiteral ( " %1:0x%2" ).arg ( name ).arg ( reinterpret_cast <quintptr>( QThread::currentThread () ), 2 * QT_POINTER_SIZE, 16 , QLatin1Char ( ' 0' ) );
1764
1789
}
1765
1790
1766
1791
#ifdef HAVE_GUI
0 commit comments