1
0
mirror of https://github.com/qgis/QGIS.git synced 2025-03-29 00:05:09 -04:00

Fix libpq access from different threads

This is a forward port from 05f949b58, following PR .

In addition, two other fixes are added (that will be backported to
3.4):
- PQexecNR, openCursor and closeCursor are protected
- uniqueCursorName is also protected (I stumbled accross a bug where
the cursor name was reused by two different threads)
This commit is contained in:
Hugo Mercier 2019-01-14 14:49:56 +01:00
parent c263750930
commit 648672d7db
5 changed files with 58 additions and 37 deletions

@ -211,6 +211,7 @@ QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool s
, mNextCursorId( 0 ) , mNextCursorId( 0 )
, mShared( shared ) , mShared( shared )
, mTransaction( transaction ) , mTransaction( transaction )
, mLock( QMutex::Recursive )
{ {
QgsDebugMsg( QStringLiteral( "New PostgreSQL connection for " ) + conninfo ); QgsDebugMsg( QStringLiteral( "New PostgreSQL connection for " ) + conninfo );
@ -1065,33 +1066,37 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError ) const
} }
QgsDebugMsgLevel( QStringLiteral( "Executing SQL: %1" ).arg( query ), 3 ); QgsDebugMsgLevel( QStringLiteral( "Executing SQL: %1" ).arg( query ), 3 );
PGresult *res = ::PQexec( mConn, query.toUtf8() ); PGresult *res = nullptr;
if ( res )
{ {
int errorStatus = PQresultStatus( res ); QMutexLocker locker( &mLock );
if ( errorStatus != PGRES_COMMAND_OK && errorStatus != PGRES_TUPLES_OK ) res = ::PQexec( mConn, query.toUtf8() );
if ( res )
{ {
if ( logError ) int errorStatus = PQresultStatus( res );
if ( errorStatus != PGRES_COMMAND_OK && errorStatus != PGRES_TUPLES_OK )
{ {
QgsMessageLog::logMessage( tr( "Erroneous query: %1 returned %2 [%3]" ) if ( logError )
.arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ), {
tr( "PostGIS" ) ); QgsMessageLog::logMessage( tr( "Erroneous query: %1 returned %2 [%3]" )
} .arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ),
else tr( "PostGIS" ) );
{ }
QgsDebugMsg( QStringLiteral( "Not logged erroneous query: %1 returned %2 [%3]" ) else
.arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ) ); {
QgsDebugMsg( QStringLiteral( "Not logged erroneous query: %1 returned %2 [%3]" )
.arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ) );
}
} }
} }
} else if ( logError )
else if ( logError ) {
{ QgsMessageLog::logMessage( tr( "Query failed: %1\nError: no result buffer" ).arg( query ), tr( "PostGIS" ) );
QgsMessageLog::logMessage( tr( "Query failed: %1\nError: no result buffer" ).arg( query ), tr( "PostGIS" ) ); }
} else
else {
{ QgsDebugMsg( QStringLiteral( "Not logged query failed: %1\nError: no result buffer" ).arg( query ) );
QgsDebugMsg( QStringLiteral( "Not logged query failed: %1\nError: no result buffer" ).arg( query ) ); }
} }
return res; return res;
@ -1099,6 +1104,8 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError ) const
bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql ) bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql )
{ {
QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
if ( mOpenCursors++ == 0 && !mTransaction ) if ( mOpenCursors++ == 0 && !mTransaction )
{ {
QgsDebugMsgLevel( QStringLiteral( "Starting read-only transaction: %1" ).arg( mPostgresqlVersion ), 4 ); QgsDebugMsgLevel( QStringLiteral( "Starting read-only transaction: %1" ).arg( mPostgresqlVersion ), 4 );
@ -1114,6 +1121,8 @@ bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql
bool QgsPostgresConn::closeCursor( const QString &cursorName ) bool QgsPostgresConn::closeCursor( const QString &cursorName )
{ {
QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
if ( !PQexecNR( QStringLiteral( "CLOSE %1" ).arg( cursorName ) ) ) if ( !PQexecNR( QStringLiteral( "CLOSE %1" ).arg( cursorName ) ) )
return false; return false;
@ -1128,11 +1137,14 @@ bool QgsPostgresConn::closeCursor( const QString &cursorName )
QString QgsPostgresConn::uniqueCursorName() QString QgsPostgresConn::uniqueCursorName()
{ {
QMutexLocker locker( &mLock ); // to protect access to mNextCursorId
return QStringLiteral( "qgis_%1" ).arg( ++mNextCursorId ); return QStringLiteral( "qgis_%1" ).arg( ++mNextCursorId );
} }
bool QgsPostgresConn::PQexecNR( const QString &query, bool retry ) bool QgsPostgresConn::PQexecNR( const QString &query, bool retry )
{ {
QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
QgsPostgresResult res( PQexec( query, false ) ); QgsPostgresResult res( PQexec( query, false ) );
ExecStatusType errorStatus = res.PQresultStatus(); ExecStatusType errorStatus = res.PQresultStatus();
@ -1194,11 +1206,15 @@ PGresult *QgsPostgresConn::PQgetResult()
PGresult *QgsPostgresConn::PQprepare( const QString &stmtName, const QString &query, int nParams, const Oid *paramTypes ) PGresult *QgsPostgresConn::PQprepare( const QString &stmtName, const QString &query, int nParams, const Oid *paramTypes )
{ {
QMutexLocker locker( &mLock );
return ::PQprepare( mConn, stmtName.toUtf8(), query.toUtf8(), nParams, paramTypes ); return ::PQprepare( mConn, stmtName.toUtf8(), query.toUtf8(), nParams, paramTypes );
} }
PGresult *QgsPostgresConn::PQexecPrepared( const QString &stmtName, const QStringList &params ) PGresult *QgsPostgresConn::PQexecPrepared( const QString &stmtName, const QStringList &params )
{ {
QMutexLocker locker( &mLock );
const char **param = new const char *[ params.size()]; const char **param = new const char *[ params.size()];
QList<QByteArray> qparam; QList<QByteArray> qparam;
@ -1222,6 +1238,8 @@ PGresult *QgsPostgresConn::PQexecPrepared( const QString &stmtName, const QStrin
void QgsPostgresConn::PQfinish() void QgsPostgresConn::PQfinish()
{ {
QMutexLocker locker( &mLock );
Q_ASSERT( mConn ); Q_ASSERT( mConn );
::PQfinish( mConn ); ::PQfinish( mConn );
mConn = nullptr; mConn = nullptr;
@ -1229,12 +1247,16 @@ void QgsPostgresConn::PQfinish()
int QgsPostgresConn::PQstatus() const int QgsPostgresConn::PQstatus() const
{ {
QMutexLocker locker( &mLock );
Q_ASSERT( mConn ); Q_ASSERT( mConn );
return ::PQstatus( mConn ); return ::PQstatus( mConn );
} }
QString QgsPostgresConn::PQerrorMessage() const QString QgsPostgresConn::PQerrorMessage() const
{ {
QMutexLocker locker( &mLock );
Q_ASSERT( mConn ); Q_ASSERT( mConn );
return QString::fromUtf8( ::PQerrorMessage( mConn ) ); return QString::fromUtf8( ::PQerrorMessage( mConn ) );
} }

@ -244,16 +244,26 @@ class QgsPostgresConn : public QObject
// libpq wrapper // libpq wrapper
// //
// run a query and check for errors // run a query and check for errors, thread-safe
PGresult *PQexec( const QString &query, bool logError = true ) const; PGresult *PQexec( const QString &query, bool logError = true ) const;
void PQfinish(); void PQfinish();
QString PQerrorMessage() const; QString PQerrorMessage() const;
int PQsendQuery( const QString &query );
int PQstatus() const; int PQstatus() const;
PGresult *PQgetResult();
PGresult *PQprepare( const QString &stmtName, const QString &query, int nParams, const Oid *paramTypes ); PGresult *PQprepare( const QString &stmtName, const QString &query, int nParams, const Oid *paramTypes );
PGresult *PQexecPrepared( const QString &stmtName, const QStringList &params ); PGresult *PQexecPrepared( const QString &stmtName, const QStringList &params );
/**
* PQsendQuery is used for asynchronous queries (with PQgetResult)
* Thread safety must be ensured by the caller by calling QgsPostgresConn::lock() and QgsPostgresConn::unlock()
*/
int PQsendQuery( const QString &query );
/**
* PQgetResult is used for asynchronous queries (with PQsendQuery)
* Thread safety must be ensured by the caller by calling QgsPostgresConn::lock() and QgsPostgresConn::unlock()
*/
PGresult *PQgetResult();
bool begin(); bool begin();
bool commit(); bool commit();
bool rollback(); bool rollback();
@ -425,7 +435,7 @@ class QgsPostgresConn : public QObject
bool mTransaction; bool mTransaction;
QMutex mLock; mutable QMutex mLock;
}; };
// clazy:excludeall=qstring-allocations // clazy:excludeall=qstring-allocations

@ -386,9 +386,7 @@ bool QgsPostgresFeatureIterator::rewind()
// move cursor to first record // move cursor to first record
lock();
mConn->PQexecNR( QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName ) ); mConn->PQexecNR( QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName ) );
unlock();
mFeatureQueue.clear(); mFeatureQueue.clear();
mFetched = 0; mFetched = 0;
mLastFetch = false; mLastFetch = false;
@ -401,9 +399,7 @@ bool QgsPostgresFeatureIterator::close()
if ( !mConn ) if ( !mConn )
return false; return false;
lock();
mConn->closeCursor( mCursorName ); mConn->closeCursor( mCursorName );
unlock();
if ( !mIsTransactionConnection ) if ( !mIsTransactionConnection )
{ {
@ -651,17 +647,14 @@ bool QgsPostgresFeatureIterator::declareCursor( const QString &whereClause, long
if ( !orderBy.isEmpty() ) if ( !orderBy.isEmpty() )
query += QStringLiteral( " ORDER BY %1 " ).arg( orderBy ); query += QStringLiteral( " ORDER BY %1 " ).arg( orderBy );
lock();
if ( !mConn->openCursor( mCursorName, query ) ) if ( !mConn->openCursor( mCursorName, query ) )
{ {
unlock();
// reloading the fields might help next time around // reloading the fields might help next time around
// TODO how to cleanly force reload of fields? P->loadFields(); // TODO how to cleanly force reload of fields? P->loadFields();
if ( closeOnFail ) if ( closeOnFail )
close(); close();
return false; return false;
} }
unlock();
mLastFetch = false; mLastFetch = false;
return true; return true;

@ -4095,9 +4095,7 @@ QgsCoordinateReferenceSystem QgsPostgresProvider::crs() const
else else
{ {
QgsPostgresConn *conn = connectionRO(); QgsPostgresConn *conn = connectionRO();
conn->lock();
QgsPostgresResult result( conn->PQexec( QStringLiteral( "SELECT proj4text FROM spatial_ref_sys WHERE srid=%1" ).arg( srid ) ) ); QgsPostgresResult result( conn->PQexec( QStringLiteral( "SELECT proj4text FROM spatial_ref_sys WHERE srid=%1" ).arg( srid ) ) );
conn->unlock();
if ( result.PQresultStatus() == PGRES_TUPLES_OK ) if ( result.PQresultStatus() == PGRES_TUPLES_OK )
{ {
srs = QgsCoordinateReferenceSystem::fromProj4( result.PQgetvalue( 0, 0 ) ); srs = QgsCoordinateReferenceSystem::fromProj4( result.PQgetvalue( 0, 0 ) );

@ -71,9 +71,7 @@ bool QgsPostgresTransaction::executeSql( const QString &sql, QString &errorMsg,
} }
QgsDebugMsg( QStringLiteral( "Transaction sql: %1" ).arg( sql ) ); QgsDebugMsg( QStringLiteral( "Transaction sql: %1" ).arg( sql ) );
mConn->lock();
QgsPostgresResult r( mConn->PQexec( sql, true ) ); QgsPostgresResult r( mConn->PQexec( sql, true ) );
mConn->unlock();
if ( r.PQresultStatus() == PGRES_BAD_RESPONSE || if ( r.PQresultStatus() == PGRES_BAD_RESPONSE ||
r.PQresultStatus() == PGRES_FATAL_ERROR ) r.PQresultStatus() == PGRES_FATAL_ERROR )
{ {