@@ -113,37 +113,48 @@ QSqlDatabase QgsAuthManager::authDatabaseConnection() const
113
113
if ( isDisabled () )
114
114
return authdb;
115
115
116
+ // while everything we use from QSqlDatabase here is thread safe, we need to ensure
117
+ // that the connection cleanup on thread finalization happens in a predictable order
118
+ QMutexLocker locker ( mMutex );
119
+
116
120
// Sharing the same connection between threads is not allowed.
117
121
// We use a dedicated connection for each thread requiring access to the database,
118
122
// using the thread address as connection name.
119
123
const QString connectionName = QStringLiteral ( " authentication.configs:0x%1" ).arg ( reinterpret_cast <quintptr>( QThread::currentThread () ), 2 * QT_POINTER_SIZE, 16 , QLatin1Char ( ' 0' ) );
120
- QgsDebugMsgLevel ( QStringLiteral ( " Using auth db connection name: %1 " ).arg ( connectionName ), 0 );
124
+ QgsDebugMsgLevel ( QStringLiteral ( " Using auth db connection name: %1 " ).arg ( connectionName ), 2 );
121
125
if ( !QSqlDatabase::contains ( connectionName ) )
122
126
{
123
- QgsDebugMsgLevel ( QStringLiteral ( " No existing connection, creating a new one" ), 0 );
127
+ QgsDebugMsgLevel ( QStringLiteral ( " No existing connection, creating a new one" ), 2 );
124
128
authdb = QSqlDatabase::addDatabase ( QStringLiteral ( " QSQLITE" ), connectionName );
125
129
authdb.setDatabaseName ( authenticationDatabasePath () );
126
130
// for background threads, remove database when current thread finishes
127
131
if ( QThread::currentThread () != QgsApplication::instance ()->thread () )
128
132
{
129
- QgsDebugMsgLevel ( QStringLiteral ( " Scheduled auth db remove on thread close" ), 0 );
130
- connect ( QThread::currentThread (), &QThread::finished, this , [connectionName]
133
+ QgsDebugMsgLevel ( QStringLiteral ( " Scheduled auth db remove on thread close" ), 2 );
134
+
135
+ // IMPORTANT - we use a direct connection here, because the database removal must happen immediately
136
+ // when the thread finishes, and we cannot let this get queued on the main thread's event loop (where
137
+ // QgsAuthManager lives).
138
+ // Otherwise, the QSqlDatabase's private data's thread gets reset immediately the QThread::finished,
139
+ // and a subsequent call to QSqlDatabase::database with the same thread address (yep it happens, actually a lot)
140
+ // triggers a condition in QSqlDatabase which detects the nullptr private thread data and returns an invalid database instead.
141
+ // QSqlDatabase::removeDatabase is thread safe, so this is ok to do.
142
+ // Right about now is a good time to re-evaluate your selected career ;)
143
+ connect ( QThread::currentThread (), &QThread::finished, this , [connectionName, this ]
131
144
{
132
- QgsDebugMsgLevel ( QStringLiteral ( " Removing outdated connection to %1 on thread exit" ).arg ( connectionName ), 0 );
145
+ QMutexLocker locker ( mMutex );
146
+ QgsDebugMsgLevel ( QStringLiteral ( " Removing outdated connection to %1 on thread exit" ).arg ( connectionName ), 2 );
133
147
QSqlDatabase::removeDatabase ( connectionName );
134
- } );
148
+ }, Qt::DirectConnection );
135
149
}
136
-
137
- QgsDebugMsgLevel ( QStringLiteral ( " Actual DB thread is: 0x%1" ).arg ( reinterpret_cast <quintptr>( authdb.driver ()->thread () ), 2 * QT_POINTER_SIZE, 16 , QLatin1Char ( ' 0' ) ), 0 );
138
- QgsDebugMsgLevel ( QStringLiteral ( " Actual DB connection name is: %1" ).arg ( authdb.connectionName () ), 0 );
139
150
}
140
151
else
141
152
{
142
- QgsDebugMsgLevel ( QStringLiteral ( " Reusing existing connection" ), 0 );
153
+ QgsDebugMsgLevel ( QStringLiteral ( " Reusing existing connection" ), 2 );
143
154
authdb = QSqlDatabase::database ( connectionName );
144
- QgsDebugMsgLevel ( QStringLiteral ( " Retrieved DB thread is: 0x%1" ).arg ( reinterpret_cast <quintptr>( authdb.driver ()->thread () ), 2 * QT_POINTER_SIZE, 16 , QLatin1Char ( ' 0' ) ), 0 );
145
- QgsDebugMsgLevel ( QStringLiteral ( " Retrieved DB connection name is: %1" ).arg ( authdb.connectionName () ), 0 );
146
155
}
156
+ locker.unlock ();
157
+
147
158
if ( !authdb.isOpen () )
148
159
{
149
160
if ( !authdb.open () )
0 commit comments