Skip to content

Commit

Permalink
Add an optional QgsFeedback object for QgsFeatureRequest, and use
Browse files Browse the repository at this point in the history
this to abort the acquisition of a connection in the OGR
connection pool

Provide a mechanism to avoid a deadlock when multiple OGR iterators
are trying to obtain a connection to a resource at once.

Refs #43572
  • Loading branch information
nyalldawson committed Jun 18, 2021
1 parent d83a769 commit e416d77
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 3 deletions.
23 changes: 23 additions & 0 deletions python/core/auto_generated/qgsfeaturerequest.sip.in
Expand Up @@ -744,6 +744,29 @@ For example, this should be set on requests that are issued from an
expression function.

.. versionadded:: 3.4
%End

void setFeedback( QgsFeedback *feedback );
%Docstring
Attach a ``feedback`` object that can be queried regularly by the iterator to check
if it should be canceled.

Ownership of ``feedback`` is NOT transferred, and the caller must take care that it exists
for the lifetime of the feature request and feature iterators.

.. seealso:: :py:func:`feedback`

.. versionadded:: 3.20
%End

QgsFeedback *feedback() const;
%Docstring
Returns the feedback object that can be queried regularly by the iterator to check
if it should be canceled, if set.

.. seealso:: :py:func:`setFeedback`

.. versionadded:: 3.20
%End

protected:
Expand Down
6 changes: 5 additions & 1 deletion src/core/providers/ogr/qgsogrfeatureiterator.cpp
Expand Up @@ -83,9 +83,13 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
else
{
//QgsDebugMsg( "Feature iterator of " + mSource->mLayerName + ": acquiring connection");
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource, mSource->mShareSameDatasetAmongLayers ), mRequest.timeout(), mRequest.requestMayBeNested() );
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource, mSource->mShareSameDatasetAmongLayers ),
mRequest.timeout(),
mRequest.requestMayBeNested(),
mRequest.feedback() );
if ( !mConn || !mConn->ds )
{
iteratorClosed();
return;
}

Expand Down
27 changes: 25 additions & 2 deletions src/core/qgsconnectionpool.h
Expand Up @@ -20,6 +20,8 @@

#include "qgis.h"
#include "qgsapplication.h"
#include "qgsfeedback.h"

#include <QCoreApplication>
#include <QMap>
#include <QMutex>
Expand Down Expand Up @@ -288,9 +290,12 @@ class QgsConnectionPool
* If \a timeout is a negative value the calling thread will be blocked
* until a connection becomes available. This is the default behavior.
*
* The optional \a feedback argument can be used to cancel the request
* before the connection is acquired.
*
* \returns initialized connection or NULLPTR if unsuccessful
*/
T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false )
T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false, QgsFeedback *feedback = nullptr )
{
mMutex.lock();
typename T_Groups::iterator it = mGroups.find( connInfo );
Expand All @@ -301,7 +306,25 @@ class QgsConnectionPool
T_Group *group = *it;
mMutex.unlock();

return group->acquire( timeout, requestMayBeNested );
if ( feedback )
{
QElapsedTimer timer;
timer.start();

while ( !feedback->isCanceled() )
{
if ( T conn = group->acquire( 300, requestMayBeNested ) )
return conn;

if ( timeout > 0 && timer.elapsed() >= timeout )
return nullptr;
}
return nullptr;
}
else
{
return group->acquire( timeout, requestMayBeNested );
}
}

//! Release an existing connection so it will get back into the pool and can be reused
Expand Down
11 changes: 11 additions & 0 deletions src/core/qgsfeaturerequest.cpp
Expand Up @@ -86,6 +86,7 @@ QgsFeatureRequest &QgsFeatureRequest::operator=( const QgsFeatureRequest &rh )
mTransformErrorCallback = rh.mTransformErrorCallback;
mTimeout = rh.mTimeout;
mRequestMayBeNested = rh.mRequestMayBeNested;
mFeedback = rh.mFeedback;
return *this;
}

Expand Down Expand Up @@ -334,6 +335,16 @@ QgsFeatureRequest &QgsFeatureRequest::setRequestMayBeNested( bool requestMayBeNe
return *this;
}

void QgsFeatureRequest::setFeedback( QgsFeedback *feedback )
{
mFeedback = feedback;
}

QgsFeedback *QgsFeatureRequest::feedback()
{
return mFeedback;
}


#include "qgsfeatureiterator.h"
#include "qgslogger.h"
Expand Down
24 changes: 24 additions & 0 deletions src/core/qgsfeaturerequest.h
Expand Up @@ -714,6 +714,29 @@ class CORE_EXPORT QgsFeatureRequest
*/
QgsFeatureRequest &setRequestMayBeNested( bool requestMayBeNested );

/**
* Attach a \a feedback object that can be queried regularly by the iterator to check
* if it should be canceled.
*
* Ownership of \a feedback is NOT transferred, and the caller must take care that it exists
* for the lifetime of the feature request and feature iterators.
*
* \see feedback()
*
* \since QGIS 3.20
*/
void setFeedback( QgsFeedback *feedback );

/**
* Returns the feedback object that can be queried regularly by the iterator to check
* if it should be canceled, if set.
*
* \see setFeedback()
*
* \since QGIS 3.20
*/
QgsFeedback *feedback() const;

protected:
FilterType mFilter = FilterNone;
QgsRectangle mFilterRect;
Expand All @@ -733,6 +756,7 @@ class CORE_EXPORT QgsFeatureRequest
QgsCoordinateTransformContext mTransformContext;
int mTimeout = -1;
int mRequestMayBeNested = false;
QgsFeedback *mFeedback = nullptr;
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags )
Expand Down
2 changes: 2 additions & 0 deletions src/core/vector/qgsvectorlayerrenderer.cpp
Expand Up @@ -392,6 +392,8 @@ bool QgsVectorLayerRenderer::renderInternal( QgsFeatureRenderer *renderer )
context.setVectorSimplifyMethod( vectorMethod );
}

featureRequest.setFeedback( mInterruptionChecker.get() );

QgsFeatureIterator fit = mSource->getFeatures( featureRequest );
// Attach an interruption checker so that iterators that have potentially
// slow fetchFeature() implementations, such as in the WFS provider, can
Expand Down

0 comments on commit e416d77

Please sign in to comment.