Skip to content

Commit fe3f917

Browse files
committedNov 1, 2018
Fix authentication database connections
Fixes the authentication database cannot be opened in some circumstances. We need to ensure that the pooled database connection is removed immediately on thread finalisation and cannot defer this until the main thread event loop runs. Fixes #20262 (cherry picked from commit a392a16)
1 parent 635ecad commit fe3f917

File tree

1 file changed

+23
-12
lines changed

1 file changed

+23
-12
lines changed
 

‎src/core/auth/qgsauthmanager.cpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,37 +113,48 @@ QSqlDatabase QgsAuthManager::authDatabaseConnection() const
113113
if ( isDisabled() )
114114
return authdb;
115115

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+
116120
// Sharing the same connection between threads is not allowed.
117121
// We use a dedicated connection for each thread requiring access to the database,
118122
// using the thread address as connection name.
119123
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 );
121125
if ( !QSqlDatabase::contains( connectionName ) )
122126
{
123-
QgsDebugMsgLevel( QStringLiteral( "No existing connection, creating a new one" ), 0 );
127+
QgsDebugMsgLevel( QStringLiteral( "No existing connection, creating a new one" ), 2 );
124128
authdb = QSqlDatabase::addDatabase( QStringLiteral( "QSQLITE" ), connectionName );
125129
authdb.setDatabaseName( authenticationDatabasePath() );
126130
// for background threads, remove database when current thread finishes
127131
if ( QThread::currentThread() != QgsApplication::instance()->thread() )
128132
{
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 ]
131144
{
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 );
133147
QSqlDatabase::removeDatabase( connectionName );
134-
} );
148+
}, Qt::DirectConnection );
135149
}
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 );
139150
}
140151
else
141152
{
142-
QgsDebugMsgLevel( QStringLiteral( "Reusing existing connection" ), 0 );
153+
QgsDebugMsgLevel( QStringLiteral( "Reusing existing connection" ), 2 );
143154
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 );
146155
}
156+
locker.unlock();
157+
147158
if ( !authdb.isOpen() )
148159
{
149160
if ( !authdb.open() )

0 commit comments

Comments
 (0)
Please sign in to comment.