Skip to content

Commit

Permalink
[WFS provider] Fix freeze when feature requests issued from main thre…
Browse files Browse the repository at this point in the history
…ad and authentication manager triggers (fixes #37224)
  • Loading branch information
rouault authored and nyalldawson committed Oct 23, 2020
1 parent 2d99271 commit 571f3bd
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 4 deletions.
40 changes: 38 additions & 2 deletions src/providers/wfs/qgswfsfeatureiterator.cpp
Expand Up @@ -33,6 +33,7 @@
#include "qgsexception.h"
#include "qgsfeedback.h"
#include "qgssqliteutils.h"
#include "qgsnetworkaccessmanager.h"

#include <algorithm>
#include <QDir>
Expand Down Expand Up @@ -84,7 +85,7 @@ QString QgsWFSFeatureHitsAsyncRequest::errorMessageWithReason( const QString &re

// -------------------------

QgsWFSFeatureDownloader::QgsWFSFeatureDownloader( QgsWFSSharedData *shared )
QgsWFSFeatureDownloader::QgsWFSFeatureDownloader( QgsWFSSharedData *shared, bool requestMadeFromMainThread )
: QgsWfsRequest( shared->mURI )
, mShared( shared )
, mStop( false )
Expand All @@ -97,6 +98,22 @@ QgsWFSFeatureDownloader::QgsWFSFeatureDownloader( QgsWFSSharedData *shared )
{
// Needed because used by a signal
qRegisterMetaType< QVector<QgsWFSFeatureGmlIdPair> >( "QVector<QgsWFSFeatureGmlIdPair>" );

if ( requestMadeFromMainThread )
{
auto emitResumeMainThread = [this]()
{
emit resumeMainThread();
};
connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::authRequestOccurred,
this, emitResumeMainThread, Qt::DirectConnection );
connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::proxyAuthenticationRequired,
this, emitResumeMainThread, Qt::DirectConnection );
#ifndef QT_NO_SSL
connect( QgsNetworkAccessManager::instance(), &QgsNetworkAccessManager::sslErrorsOccurred,
this, emitResumeMainThread, Qt::DirectConnection );
#endif
}
}

QgsWFSFeatureDownloader::~QgsWFSFeatureDownloader()
Expand Down Expand Up @@ -893,6 +910,7 @@ QString QgsWFSFeatureDownloader::errorMessageWithReason( const QString &reason )

QgsWFSThreadedFeatureDownloader::QgsWFSThreadedFeatureDownloader( QgsWFSSharedData *shared )
: mShared( shared )
, mRequestMadeFromMainThread( QThread::currentThread() == QApplication::instance()->thread() )
{
}

Expand Down Expand Up @@ -926,7 +944,7 @@ void QgsWFSThreadedFeatureDownloader::startAndWait()
void QgsWFSThreadedFeatureDownloader::run()
{
// We need to construct it in the run() method (i.e. in the new thread)
mDownloader = new QgsWFSFeatureDownloader( mShared );
mDownloader = new QgsWFSFeatureDownloader( mShared, mRequestMadeFromMainThread );
{
QMutexLocker locker( &mWaitMutex );
mWaitCond.wakeOne();
Expand Down Expand Up @@ -1132,6 +1150,9 @@ void QgsWFSFeatureIterator::connectSignals( QgsWFSFeatureDownloader *downloader

connect( downloader, &QgsWFSFeatureDownloader::endOfDownload,
this, &QgsWFSFeatureIterator::endOfDownloadSynchronous, Qt::DirectConnection );

connect( downloader, &QgsWFSFeatureDownloader::resumeMainThread,
this, &QgsWFSFeatureIterator::resumeMainThreadSynchronous, Qt::DirectConnection );
}

void QgsWFSFeatureIterator::endOfDownloadSynchronous( bool )
Expand All @@ -1142,6 +1163,14 @@ void QgsWFSFeatureIterator::endOfDownloadSynchronous( bool )
mWaitCond.wakeOne();
}

void QgsWFSFeatureIterator::resumeMainThreadSynchronous()
{
// Wake up waiting loop
QMutexLocker locker( &mMutex );
mProcessEvents = true;
mWaitCond.wakeOne();
}

void QgsWFSFeatureIterator::setInterruptionChecker( QgsFeedback *interruptionChecker )
{
mInterruptionChecker = interruptionChecker;
Expand Down Expand Up @@ -1267,6 +1296,8 @@ bool QgsWFSFeatureIterator::fetchFeature( QgsFeature &f )
return true;
}

const bool requestMadeFromMainThread = QThread::currentThread() == QApplication::instance()->thread();

// Second step is to wait for features to be notified by the downloader
while ( true )
{
Expand Down Expand Up @@ -1400,6 +1431,11 @@ bool QgsWFSFeatureIterator::fetchFeature( QgsFeature &f )
break;
}
mWaitCond.wait( &mMutex, timeout );
if ( requestMadeFromMainThread && mProcessEvents )
{
QgsApplication::instance()->processEvents();
mProcessEvents = false;
}
if ( mInterruptionChecker && mInterruptionChecker->isCanceled() )
{
mTimeoutOrInterruptionOccurred = true;
Expand Down
10 changes: 9 additions & 1 deletion src/providers/wfs/qgswfsfeatureiterator.h
Expand Up @@ -92,7 +92,7 @@ class QgsWFSFeatureDownloader: public QgsWfsRequest
{
Q_OBJECT
public:
explicit QgsWFSFeatureDownloader( QgsWFSSharedData *shared );
explicit QgsWFSFeatureDownloader( QgsWFSSharedData *shared, bool requestMadeFromMainThread );
~QgsWFSFeatureDownloader() override;

/**
Expand All @@ -118,6 +118,11 @@ class QgsWFSFeatureDownloader: public QgsWfsRequest
//! Emitted when the download is finished (successful or not)
void endOfDownload( bool success );

// Emitted when QgsNetworkAccessManager emit signals that require
// QgsBackgroundCachedFeatureIterator to process (authentication) events,
// if it was started from the main thread.
void resumeMainThread();

//! Used internally by the stop() method
void doStop();

Expand Down Expand Up @@ -187,6 +192,7 @@ class QgsWFSThreadedFeatureDownloader: public QThread
QgsWFSFeatureDownloader *mDownloader = nullptr;
QWaitCondition mWaitCond;
QMutex mWaitMutex;
bool mRequestMadeFromMainThread = false;
};

class QgsWFSFeatureSource;
Expand Down Expand Up @@ -216,6 +222,7 @@ class QgsWFSFeatureIterator : public QObject,
private slots:
void featureReceivedSynchronous( const QVector<QgsWFSFeatureGmlIdPair> &list );
void endOfDownloadSynchronous( bool success );
void resumeMainThreadSynchronous();

private:

Expand All @@ -234,6 +241,7 @@ class QgsWFSFeatureIterator : public QObject,

bool mNewFeaturesReceived = false;
bool mDownloadFinished = false;
bool mProcessEvents = false;
QgsFeatureIterator mCacheIterator;
QgsFeedback *mInterruptionChecker = nullptr;
bool mTimeoutOrInterruptionOccurred = false;
Expand Down
12 changes: 11 additions & 1 deletion src/providers/wfs/qgswfsprovider.cpp
Expand Up @@ -118,9 +118,19 @@ QgsWFSProvider::QgsWFSProvider( const QString &uri, const ProviderOptions &optio
//Failed to detect feature type from describeFeatureType -> get first feature from layer to detect type
if ( mShared->mWKBType == QgsWkbTypes::Unknown )
{
QgsWFSFeatureDownloader downloader( mShared.get() );
const bool requestMadeFromMainThread = QThread::currentThread() == QApplication::instance()->thread();
QgsWFSFeatureDownloader downloader( mShared.get(), requestMadeFromMainThread );
connect( &downloader, static_cast<void ( QgsWFSFeatureDownloader::* )( QVector<QgsWFSFeatureGmlIdPair> )>( &QgsWFSFeatureDownloader::featureReceived ),
this, &QgsWFSProvider::featureReceivedAnalyzeOneFeature );
if ( requestMadeFromMainThread )
{
auto processEvents = []()
{
QgsApplication::instance()->processEvents();
};
connect( &downloader, &QgsWFSFeatureDownloader::resumeMainThread,
this, processEvents );
}
downloader.run( false, /* serialize features */
1 /* maxfeatures */ );
}
Expand Down

0 comments on commit 571f3bd

Please sign in to comment.