Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #7792 from m-kuhn/threadsafefeaturesource
Add threadsafe method to get featuresource from layer
  • Loading branch information
m-kuhn committed Sep 5, 2018
2 parents 969c7c5 + 86f4293 commit f875bb7
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/core/qgsvectorlayerutils.cpp
Expand Up @@ -498,6 +498,37 @@ QgsFeature QgsVectorLayerUtils::duplicateFeature( QgsVectorLayer *layer, const Q
return newFeature;
}

std::unique_ptr<QgsVectorLayerFeatureSource> QgsVectorLayerUtils::getFeatureSource( QPointer<QgsVectorLayer> layer )
{
std::unique_ptr<QgsVectorLayerFeatureSource> featureSource;

auto getFeatureSource = [ layer, &featureSource ]
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
Q_ASSERT( QThread::currentThread() == qApp->thread() );
#endif
QgsVectorLayer *lyr = layer.data();

if ( lyr )
{
featureSource.reset( new QgsVectorLayerFeatureSource( lyr ) );
}
};

#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
// Make sure we only deal with the vector layer on the main thread where it lives.
// Anything else risks a crash.
if ( QThread::currentThread() == qApp->thread() )
getFeatureSource();
else
QMetaObject::invokeMethod( qApp, getFeatureSource, Qt::BlockingQueuedConnection );
#else
getFeatureSource();
#endif

return featureSource;
}

QList<QgsVectorLayer *> QgsVectorLayerUtils::QgsDuplicateFeatureContext::layers() const
{
QList<QgsVectorLayer *> layers;
Expand Down
13 changes: 13 additions & 0 deletions src/core/qgsvectorlayerutils.h
Expand Up @@ -19,6 +19,7 @@
#include "qgis_core.h"
#include "qgsvectorlayer.h"
#include "qgsgeometry.h"
#include "qgsvectorlayerfeatureiterator.h"

/**
* \ingroup core
Expand Down Expand Up @@ -155,6 +156,18 @@ class CORE_EXPORT QgsVectorLayerUtils
*/
static QgsFeature duplicateFeature( QgsVectorLayer *layer, const QgsFeature &feature, QgsProject *project, int depth, QgsDuplicateFeatureContext &duplicateFeatureContext SIP_OUT );

/**
* Gets the feature source from a QgsVectorLayer pointer.
* This method is thread-safe but will block the main thread for execution. Executing it from the main
* thread is safe too.
* This should be used in scenarios, where a ``QWeakPointer<QgsVectorLayer>`` is kept in a thread
* and features should be fetched from this layer. Using the layer directly is not safe to do.
* The result will be ``nullptr`` if the layer has been deleted.
*
* \note Requires Qt >= 5.10 to make use of the thread-safe implementation
* \since QGIS 3.4
*/
static std::unique_ptr<QgsVectorLayerFeatureSource> getFeatureSource( QPointer<QgsVectorLayer> layer ) SIP_SKIP;
};


Expand Down
1 change: 1 addition & 0 deletions tests/src/core/CMakeLists.txt
Expand Up @@ -192,6 +192,7 @@ SET(TESTS
testqgsvectorlayercache.cpp
testqgsvectorlayerjoinbuffer.cpp
testqgsvectorlayer.cpp
testqgsvectorlayerutils.cpp
testziplayer.cpp
testqgsmeshlayer.cpp
testqgsmeshlayerrenderer.cpp
Expand Down
129 changes: 129 additions & 0 deletions tests/src/core/testqgsvectorlayerutils.cpp
@@ -0,0 +1,129 @@
/***************************************************************************
test_template.cpp
--------------------------------------
Date : Sun Sep 16 12:22:23 AKDT 2007
Copyright : (C) 2007 by Gary E. Sherman
Email : sherman at mrcc dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgstest.h"

#include "qgsvectorlayerutils.h"

/**
* \ingroup UnitTests
* This is a unit test for the vector layer class.
*/
class TestQgsVectorLayerUtils : public QObject
{
Q_OBJECT
public:
TestQgsVectorLayerUtils() = default;

private slots:

void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.

void testGetFeatureSource();
};

void TestQgsVectorLayerUtils::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
QgsApplication::showSettings();

}

void TestQgsVectorLayerUtils::cleanupTestCase()
{
QgsApplication::exitQgis();
}

class FeatureFetcher : public QThread
{
Q_OBJECT

public:
FeatureFetcher( QPointer<QgsVectorLayer> layer )
: mLayer( layer )
{
}

void run() override
{
QgsFeature feat;
auto fs = QgsVectorLayerUtils::getFeatureSource( mLayer );
if ( fs )
fs->getFeatures().nextFeature( feat );
emit resultReady( feat.attribute( QStringLiteral( "col1" ) ) );
}

signals:
void resultReady( const QVariant &attribute );

private:
QPointer<QgsVectorLayer> mLayer;
};


void TestQgsVectorLayerUtils::testGetFeatureSource()
{
std::unique_ptr<QgsVectorLayer> vl = qgis::make_unique<QgsVectorLayer>( QStringLiteral( "Point?field=col1:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
vl->startEditing();
QgsFeature f1( vl->fields(), 1 );
f1.setAttribute( QStringLiteral( "col1" ), 10 );
vl->addFeature( f1 );

QPointer<QgsVectorLayer> vlPtr( vl.get() );

QgsFeature feat;
QgsVectorLayerUtils::getFeatureSource( vlPtr )->getFeatures().nextFeature( feat );
QCOMPARE( feat.attribute( QStringLiteral( "col1" ) ).toInt(), 10 );

FeatureFetcher *thread = new FeatureFetcher( vlPtr );

bool finished = false;
QVariant result;

auto onResultReady = [&finished, &result]( const QVariant & res )
{
finished = true;
result = res;
};

connect( thread, &FeatureFetcher::resultReady, this, onResultReady );
connect( thread, &QThread::finished, thread, &QThread::deleteLater );

thread->start();
while ( !finished )
QCoreApplication::processEvents();
QCOMPARE( result.toInt(), 10 );
thread->quit();

FeatureFetcher *thread2 = new FeatureFetcher( vlPtr );

finished = false;
result = QVariant();
connect( thread2, &FeatureFetcher::resultReady, this, onResultReady );
connect( thread2, &QThread::finished, thread, &QThread::deleteLater );

vl.reset();
thread2->start();
while ( !finished )
QCoreApplication::processEvents();
QVERIFY( result.isNull() );
thread2->quit();
}

QGSTEST_MAIN( TestQgsVectorLayerUtils )
#include "testqgsvectorlayerutils.moc"

0 comments on commit f875bb7

Please sign in to comment.