Fix hang when WMS credentials requested

Remove responsibility for credentials mutex locking from external
callers and handle appropriate locks internally. This allows the
mutex lock to be much "tighter" and avoids deadlocks when
credentials are requested while an existing credentials dialog
is being shown.

(No mutex is required protecting the credentials dialog itself
as this is ALWAYS shown in the main thread)

Fixes #20826
This commit is contained in:
Nyall Dawson 2019-02-01 13:37:33 +10:00
parent 59aea69894
commit c9e7616498
9 changed files with 31 additions and 54 deletions

View File

@ -41,27 +41,27 @@ signal destroyed() to be notified of the deletion
retrieves instance retrieves instance
%End %End
void lock(); void lock() /Deprecated/;
%Docstring %Docstring
Lock the instance against access from multiple threads. This does not really lock access to get/put methds, Lock the instance against access from multiple threads. This does not really lock access to get/put methds,
it will just prevent other threads to lock the instance and continue the execution. When the class is used it will just prevent other threads to lock the instance and continue the execution. When the class is used
from non-GUI threads, they should call lock() before the get/put calls to avoid race conditions. from non-GUI threads, they should call lock() before the get/put calls to avoid race conditions.
.. versionadded:: 2.4 .. deprecated:: since QGIS 3.4 - mutex locking is automatically handled
%End %End
void unlock(); void unlock() /Deprecated/;
%Docstring %Docstring
Unlock the instance after being locked. Unlock the instance after being locked.
.. versionadded:: 2.4 .. deprecated:: since QGIS 3.4 - mutex locking is automatically handled
%End %End
QMutex *mutex(); QMutex *mutex() /Deprecated/;
%Docstring %Docstring
Returns pointer to mutex Returns pointer to mutex
.. versionadded:: 2.4 .. deprecated:: since QGIS 3.4 - mutex locking is automatically handled
%End %End
protected: protected:

View File

@ -1589,10 +1589,6 @@ QgisApp::~QgisApp()
qDeleteAll( mCustomDropHandlers ); qDeleteAll( mCustomDropHandlers );
qDeleteAll( mCustomLayoutDropHandlers ); qDeleteAll( mCustomLayoutDropHandlers );
// replace gui based network access managers with failing only ones
QgsNetworkAccessManager::instance()->setSslErrorHandler( qgis::make_unique< QgsSslErrorHandler >() );
QgsNetworkAccessManager::instance()->setAuthHandler( qgis::make_unique< QgsNetworkAuthenticationHandler >() );
const QList<QgsMapCanvas *> canvases = mapCanvases(); const QList<QgsMapCanvas *> canvases = mapCanvases();
for ( QgsMapCanvas *canvas : canvases ) for ( QgsMapCanvas *canvas : canvases )
{ {
@ -13978,7 +13974,6 @@ void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthe
bool ok; bool ok;
{ {
QMutexLocker lock( QgsCredentials::instance()->mutex() );
ok = QgsCredentials::instance()->get( ok = QgsCredentials::instance()->get(
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ), QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
username, password, username, password,
@ -13992,7 +13987,6 @@ void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthe
// credentials didn't change - stored ones probably wrong? clear password and retry // credentials didn't change - stored ones probably wrong? clear password and retry
{ {
QMutexLocker lock( QgsCredentials::instance()->mutex() );
QgsCredentials::instance()->put( QgsCredentials::instance()->put(
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ), QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
username, QString() ); username, QString() );
@ -14000,7 +13994,6 @@ void QgisApp::namProxyAuthenticationRequired( const QNetworkProxy &proxy, QAuthe
} }
{ {
QMutexLocker lock( QgsCredentials::instance()->mutex() );
QgsCredentials::instance()->put( QgsCredentials::instance()->put(
QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ), QStringLiteral( "proxy %1:%2 [%3]" ).arg( proxy.hostName() ).arg( proxy.port() ).arg( auth->realm() ),
username, password username, password

View File

@ -44,15 +44,10 @@ void QgsAppAuthRequestHandler::handleAuthRequest( QNetworkReply *reply, QAuthent
for ( ;; ) for ( ;; )
{ {
bool ok; bool ok = QgsCredentials::instance()->get(
{
QMutexLocker lock( QgsCredentials::instance()->mutex() );
ok = QgsCredentials::instance()->get(
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ), QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
username, password, username, password,
QObject::tr( "Authentication required" ) ); QObject::tr( "Authentication required" ) );
}
if ( !ok ) if ( !ok )
return; return;
@ -60,22 +55,16 @@ void QgsAppAuthRequestHandler::handleAuthRequest( QNetworkReply *reply, QAuthent
break; break;
// credentials didn't change - stored ones probably wrong? clear password and retry // credentials didn't change - stored ones probably wrong? clear password and retry
{
QMutexLocker lock( QgsCredentials::instance()->mutex() );
QgsCredentials::instance()->put( QgsCredentials::instance()->put(
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ), QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
username, QString() ); username, QString() );
} }
}
// save credentials // save credentials
{
QMutexLocker lock( QgsCredentials::instance()->mutex() );
QgsCredentials::instance()->put( QgsCredentials::instance()->put(
QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ), QStringLiteral( "%1 at %2" ).arg( auth->realm(), reply->url().host() ),
username, password username, password
); );
}
auth->setUser( username ); auth->setUser( username );
auth->setPassword( password ); auth->setPassword( password );

View File

@ -3193,10 +3193,8 @@ bool QgsAuthManager::masterPasswordInput()
if ( ! ok ) if ( ! ok )
{ {
QgsCredentials *creds = QgsCredentials::instance(); QgsCredentials *creds = QgsCredentials::instance();
creds->lock();
pass.clear(); pass.clear();
ok = creds->getMasterPassword( pass, masterPasswordHashInDatabase() ); ok = creds->getMasterPassword( pass, masterPasswordHashInDatabase() );
creds->unlock();
} }
if ( ok && !pass.isEmpty() && mMasterPass != pass ) if ( ok && !pass.isEmpty() && mMasterPass != pass )

View File

@ -40,9 +40,11 @@ QgsCredentials *QgsCredentials::instance()
bool QgsCredentials::get( const QString &realm, QString &username, QString &password, const QString &message ) bool QgsCredentials::get( const QString &realm, QString &username, QString &password, const QString &message )
{ {
QMutexLocker locker( &mMutex );
if ( mCredentialCache.contains( realm ) ) if ( mCredentialCache.contains( realm ) )
{ {
QPair<QString, QString> credentials = mCredentialCache.take( realm ); QPair<QString, QString> credentials = mCredentialCache.take( realm );
locker.unlock();
username = credentials.first; username = credentials.first;
password = credentials.second; password = credentials.second;
QgsDebugMsg( QStringLiteral( "retrieved realm:%1 username:%2 password:%3" ).arg( realm, username, password ) ); QgsDebugMsg( QStringLiteral( "retrieved realm:%1 username:%2 password:%3" ).arg( realm, username, password ) );
@ -50,6 +52,7 @@ bool QgsCredentials::get( const QString &realm, QString &username, QString &pass
if ( !password.isNull() ) if ( !password.isNull() )
return true; return true;
} }
locker.unlock();
if ( request( realm, username, password, message ) ) if ( request( realm, username, password, message ) )
{ {
@ -66,6 +69,7 @@ bool QgsCredentials::get( const QString &realm, QString &username, QString &pass
void QgsCredentials::put( const QString &realm, const QString &username, const QString &password ) void QgsCredentials::put( const QString &realm, const QString &username, const QString &password )
{ {
QgsDebugMsg( QStringLiteral( "inserting realm:%1 username:%2 password:%3" ).arg( realm, username, password ) ); QgsDebugMsg( QStringLiteral( "inserting realm:%1 username:%2 password:%3" ).arg( realm, username, password ) );
QMutexLocker locker( &mMutex );
mCredentialCache.insert( realm, QPair<QString, QString>( username, password ) ); mCredentialCache.insert( realm, QPair<QString, QString>( username, password ) );
} }

View File

@ -59,21 +59,25 @@ class CORE_EXPORT QgsCredentials
* Lock the instance against access from multiple threads. This does not really lock access to get/put methds, * Lock the instance against access from multiple threads. This does not really lock access to get/put methds,
* it will just prevent other threads to lock the instance and continue the execution. When the class is used * it will just prevent other threads to lock the instance and continue the execution. When the class is used
* from non-GUI threads, they should call lock() before the get/put calls to avoid race conditions. * from non-GUI threads, they should call lock() before the get/put calls to avoid race conditions.
* \since QGIS 2.4 *
* \deprecated since QGIS 3.4 - mutex locking is automatically handled
*/ */
void lock(); Q_DECL_DEPRECATED void lock() SIP_DEPRECATED;
/** /**
* Unlock the instance after being locked. * Unlock the instance after being locked.
* \since QGIS 2.4 * \deprecated since QGIS 3.4 - mutex locking is automatically handled
*/ */
void unlock(); Q_DECL_DEPRECATED void unlock() SIP_DEPRECATED;
/** /**
* Returns pointer to mutex * Returns pointer to mutex
* \since QGIS 2.4 * \deprecated since QGIS 3.4 - mutex locking is automatically handled
*/ */
QMutex *mutex() { return &mMutex; } Q_DECL_DEPRECATED QMutex *mutex() SIP_DEPRECATED
{
return &mMutex;
}
protected: protected:

View File

@ -235,7 +235,6 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
db.setPort( port.toInt() ); db.setPort( port.toInt() );
bool connected = false; bool connected = false;
int i = 0; int i = 0;
QgsCredentials::instance()->lock();
while ( !connected && i < 3 ) while ( !connected && i < 3 )
{ {
i++; i++;
@ -249,7 +248,6 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
{ {
errMsg = QStringLiteral( "Cancel clicked" ); errMsg = QStringLiteral( "Cancel clicked" );
QgsDebugMsg( errMsg ); QgsDebugMsg( errMsg );
QgsCredentials::instance()->unlock();
break; break;
} }
} }
@ -291,7 +289,6 @@ QSqlDatabase QgsDb2Provider::getDatabase( const QString &connInfo, QString &errM
{ {
QgsCredentials::instance()->put( databaseName, userName, password ); QgsCredentials::instance()->put( databaseName, userName, password );
} }
QgsCredentials::instance()->unlock();
return db; return db;
} }

View File

@ -96,8 +96,6 @@ QgsOracleConn::QgsOracleConn( QgsDataSourceUri uri )
QgsDebugMsg( QStringLiteral( "Connecting with options: " ) + options ); QgsDebugMsg( QStringLiteral( "Connecting with options: " ) + options );
if ( !mDatabase.open() ) if ( !mDatabase.open() )
{ {
QgsCredentials::instance()->lock();
while ( !mDatabase.open() ) while ( !mDatabase.open() )
{ {
bool ok = QgsCredentials::instance()->get( realm, username, password, mDatabase.lastError().text() ); bool ok = QgsCredentials::instance()->get( realm, username, password, mDatabase.lastError().text() );
@ -127,8 +125,6 @@ QgsOracleConn::QgsOracleConn( QgsDataSourceUri uri )
if ( mDatabase.isOpen() ) if ( mDatabase.isOpen() )
QgsCredentials::instance()->put( realm, username, password ); QgsCredentials::instance()->put( realm, username, password );
QgsCredentials::instance()->unlock();
} }
if ( !mDatabase.isOpen() ) if ( !mDatabase.isOpen() )

View File

@ -272,8 +272,6 @@ QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool s
QString username = uri.username(); QString username = uri.username();
QString password = uri.password(); QString password = uri.password();
QgsCredentials::instance()->lock();
int i = 0; int i = 0;
while ( PQstatus() != CONNECTION_OK && i < 5 ) while ( PQstatus() != CONNECTION_OK && i < 5 )
{ {
@ -298,8 +296,6 @@ QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool s
if ( PQstatus() == CONNECTION_OK ) if ( PQstatus() == CONNECTION_OK )
QgsCredentials::instance()->put( conninfo, username, password ); QgsCredentials::instance()->put( conninfo, username, password );
QgsCredentials::instance()->unlock();
} }
if ( PQstatus() != CONNECTION_OK ) if ( PQstatus() != CONNECTION_OK )