Skip to content

Commit a6a36ac

Browse files
authored
Merge pull request #6316 from nyalldawson/locator_thread
Make QgsLocator more thread safe
2 parents 838bde3 + 2c6100e commit a6a36ac

File tree

12 files changed

+238
-71
lines changed

12 files changed

+238
-71
lines changed

.ci/travis/linux/blacklist.txt

-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ PyQgsSpatialiteProvider
3030
# Flaky, see https://travis-ci.org/qgis/QGIS/jobs/297708174
3131
PyQgsServerAccessControl
3232

33-
# Flaky, see https://dash.orfeo-toolbox.org/testDetails.php?test=61670866&build=297397
34-
PyQgsLocator
35-
3633
# Need a local postgres installation
3734
PyQgsAuthManagerPKIPostgresTest
3835
PyQgsAuthManagerPasswordPostgresTest

python/core/locator/qgslocatorfilter.sip.in

+37
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,22 @@ Abstract base class for filters which collect locator results.
6969
Lowest
7070
};
7171

72+
enum Flag
73+
{
74+
FlagFast,
75+
};
76+
typedef QFlags<QgsLocatorFilter::Flag> Flags;
77+
78+
7279
QgsLocatorFilter( QObject *parent = 0 );
7380
%Docstring
7481
Constructor for QgsLocatorFilter.
82+
%End
83+
84+
virtual QgsLocatorFilter *clone() const = 0 /Factory/;
85+
%Docstring
86+
Creates a clone of the filter. New requests are always executed in a
87+
clone of the original filter.
7588
%End
7689

7790
virtual QString name() const = 0;
@@ -86,6 +99,11 @@ Returns the unique name for the filter. This should be an untranslated string id
8699
Returns a translated, user-friendly name for the filter.
87100

88101
.. seealso:: :py:func:`name`
102+
%End
103+
104+
virtual QgsLocatorFilter::Flags flags() const;
105+
%Docstring
106+
Returns flags which specify the filter's behavior.
89107
%End
90108

91109
virtual Priority priority() const;
@@ -108,6 +126,14 @@ results from this filter.
108126
be ignored.
109127
%End
110128

129+
virtual void prepare( const QString &string, const QgsLocatorContext &context );
130+
%Docstring
131+
Prepares the filter instance for an upcoming search for the specified ``string``. This method is always called
132+
from the main thread, and individual filter subclasses should perform whatever
133+
tasks are required in order to allow a subsequent search to safely execute
134+
on a background thread.
135+
%End
136+
111137
virtual void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) = 0;
112138
%Docstring
113139
Retrieves the filter results for a specified search ``string``. The ``context``
@@ -120,6 +146,9 @@ signal whenever they encounter a matching result.
120146
Subclasses should periodically check the ``feedback`` object to determine
121147
whether the query has been canceled. If so, the subclass should return
122148
from this method as soon as possible.
149+
150+
This will be called from a background thread unless flags() returns the
151+
QgsLocatorFilter.FlagFast flag.
123152
%End
124153

125154
virtual void triggerResult( const QgsLocatorResult &result ) = 0;
@@ -190,6 +219,11 @@ custom configuration widget.
190219

191220
signals:
192221

222+
void finished();
223+
%Docstring
224+
Emitted when the filter finishes fetching results.
225+
%End
226+
193227
void resultFetched( const QgsLocatorResult &result );
194228
%Docstring
195229
Should be emitted by filters whenever they encounter a matching result
@@ -198,6 +232,9 @@ during within their fetchResults() implementation.
198232

199233
};
200234

235+
QFlags<QgsLocatorFilter::Flag> operator|(QgsLocatorFilter::Flag f1, QFlags<QgsLocatorFilter::Flag> f2);
236+
237+
201238

202239

203240

python/plugins/processing/gui/AlgorithmLocatorFilter.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class AlgorithmLocatorFilter(QgsLocatorFilter):
4040
def __init__(self, parent=None):
4141
super(AlgorithmLocatorFilter, self).__init__(parent)
4242

43+
def clone(self):
44+
return AlgorithmLocatorFilter()
45+
4346
def name(self):
4447
return 'processing_alg'
4548

@@ -52,10 +55,13 @@ def priority(self):
5255
def prefix(self):
5356
return 'a'
5457

58+
def flags(self):
59+
return QgsLocatorFilter.FlagFast
60+
5561
def fetchResults(self, string, context, feedback):
62+
# collect results in main thread, since this method is inexpensive and
63+
# accessing the processing registry is not thread safe
5664
for a in QgsApplication.processingRegistry().algorithms():
57-
if feedback.isCanceled():
58-
return
5965
if a.flags() & QgsProcessingAlgorithm.FlagHideFromToolbox:
6066
continue
6167

@@ -81,7 +87,7 @@ def triggerResult(self, result):
8187
dlg.setMessage(message)
8288
dlg.exec_()
8389
return
84-
dlg = alg.createCustomParametersWidget()
90+
dlg = alg.createCustomParametersWidget(None)
8591
if not dlg:
8692
dlg = AlgorithmDialog(alg)
8793
canvas = iface.mapCanvas()

src/app/locator/qgsinbuiltlocatorfilters.cpp

+50-33
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,20 @@ QgsLayerTreeLocatorFilter::QgsLayerTreeLocatorFilter( QObject *parent )
3131
: QgsLocatorFilter( parent )
3232
{}
3333

34-
void QgsLayerTreeLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
34+
QgsLayerTreeLocatorFilter *QgsLayerTreeLocatorFilter::clone() const
35+
{
36+
return new QgsLayerTreeLocatorFilter();
37+
}
38+
39+
void QgsLayerTreeLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback * )
3540
{
3641
QgsLayerTree *tree = QgsProject::instance()->layerTreeRoot();
37-
QList<QgsLayerTreeLayer *> layers = tree->findLayers();
38-
Q_FOREACH ( QgsLayerTreeLayer *layer, layers )
42+
const QList<QgsLayerTreeLayer *> layers = tree->findLayers();
43+
for ( QgsLayerTreeLayer *layer : layers )
3944
{
40-
if ( feedback->isCanceled() )
41-
return;
42-
4345
if ( layer->layer() && stringMatches( layer->layer()->name(), string ) )
4446
{
4547
QgsLocatorResult result;
46-
result.filter = this;
4748
result.displayString = layer->layer()->name();
4849
result.userData = layer->layerId();
4950
result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
@@ -68,18 +69,19 @@ QgsLayoutLocatorFilter::QgsLayoutLocatorFilter( QObject *parent )
6869
: QgsLocatorFilter( parent )
6970
{}
7071

71-
void QgsLayoutLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
72+
QgsLayoutLocatorFilter *QgsLayoutLocatorFilter::clone() const
73+
{
74+
return new QgsLayoutLocatorFilter();
75+
}
76+
77+
void QgsLayoutLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback * )
7278
{
7379
const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts();
7480
for ( QgsMasterLayoutInterface *layout : layouts )
7581
{
76-
if ( feedback->isCanceled() )
77-
return;
78-
7982
if ( layout && stringMatches( layout->name(), string ) )
8083
{
8184
QgsLocatorResult result;
82-
result.filter = this;
8385
result.displayString = layout->name();
8486
result.userData = layout->name();
8587
//result.icon = QgsMapLayerModel::iconForLayer( layer->layer() );
@@ -109,15 +111,20 @@ QgsActionLocatorFilter::QgsActionLocatorFilter( const QList<QWidget *> &parentOb
109111
setUseWithoutPrefix( false );
110112
}
111113

112-
void QgsActionLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
114+
QgsActionLocatorFilter *QgsActionLocatorFilter::clone() const
113115
{
116+
return new QgsActionLocatorFilter( mActionParents );
117+
}
118+
119+
void QgsActionLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback * )
120+
{
121+
// collect results in main thread, since this method is inexpensive and
122+
// accessing the gui actions is not thread safe
123+
114124
QList<QAction *> found;
115125

116-
Q_FOREACH ( QWidget *object, mActionParents )
126+
for ( QWidget *object : qgis::as_const( mActionParents ) )
117127
{
118-
if ( feedback->isCanceled() )
119-
return;
120-
121128
searchActions( string, object, found );
122129
}
123130
}
@@ -131,8 +138,8 @@ void QgsActionLocatorFilter::triggerResult( const QgsLocatorResult &result )
131138

132139
void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *parent, QList<QAction *> &found )
133140
{
134-
QList< QWidget *> children = parent->findChildren<QWidget *>();
135-
Q_FOREACH ( QWidget *widget, children )
141+
const QList< QWidget *> children = parent->findChildren<QWidget *>();
142+
for ( QWidget *widget : children )
136143
{
137144
searchActions( string, widget, found );
138145
}
@@ -154,7 +161,6 @@ void QgsActionLocatorFilter::searchActions( const QString &string, QWidget *pare
154161
if ( stringMatches( searchText, string ) )
155162
{
156163
QgsLocatorResult result;
157-
result.filter = this;
158164
result.displayString = searchText;
159165
result.userData = QVariant::fromValue( action );
160166
result.icon = action->icon();
@@ -171,7 +177,12 @@ QgsActiveLayerFeaturesLocatorFilter::QgsActiveLayerFeaturesLocatorFilter( QObjec
171177
setUseWithoutPrefix( false );
172178
}
173179

174-
void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
180+
QgsActiveLayerFeaturesLocatorFilter *QgsActiveLayerFeaturesLocatorFilter::clone() const
181+
{
182+
return new QgsActiveLayerFeaturesLocatorFilter();
183+
}
184+
185+
void QgsActiveLayerFeaturesLocatorFilter::prepare( const QString &string, const QgsLocatorContext & )
175186
{
176187
if ( string.length() < 3 )
177188
return;
@@ -183,11 +194,9 @@ void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, c
183194
if ( !layer )
184195
return;
185196

186-
int found = 0;
187-
QgsExpression dispExpression( layer->displayExpression() );
188-
QgsExpressionContext context;
189-
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
190-
dispExpression.prepare( &context );
197+
mDispExpression = QgsExpression( layer->displayExpression() );
198+
mContext.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
199+
mDispExpression.prepare( &mContext );
191200

192201
// build up request expression
193202
QStringList expressionParts;
@@ -211,17 +220,25 @@ void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, c
211220
req.setFlags( QgsFeatureRequest::NoGeometry );
212221
req.setFilterExpression( expression );
213222
req.setLimit( 30 );
223+
mIterator = layer->getFeatures( req );
224+
225+
mLayerId = layer->id();
226+
mLayerIcon = QgsMapLayerModel::iconForLayer( layer );
227+
}
228+
229+
void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, const QgsLocatorContext &, QgsFeedback *feedback )
230+
{
231+
int found = 0;
214232
QgsFeature f;
215-
QgsFeatureIterator it = layer->getFeatures( req );
216-
while ( it.nextFeature( f ) )
233+
234+
while ( mIterator.nextFeature( f ) )
217235
{
218236
if ( feedback->isCanceled() )
219237
return;
220238

221239
QgsLocatorResult result;
222-
result.filter = this;
223240

224-
context.setFeature( f );
241+
mContext.setFeature( f );
225242

226243
// find matching field content
227244
Q_FOREACH ( const QVariant &var, f.attributes() )
@@ -236,10 +253,10 @@ void QgsActiveLayerFeaturesLocatorFilter::fetchResults( const QString &string, c
236253
if ( result.displayString.isEmpty() )
237254
continue; //not sure how this result slipped through...
238255

239-
result.description = dispExpression.evaluate( &context ).toString();
256+
result.description = mDispExpression.evaluate( &mContext ).toString();
240257

241-
result.userData = QVariantList() << f.id() << layer->id();
242-
result.icon = QgsMapLayerModel::iconForLayer( layer );
258+
result.userData = QVariantList() << f.id() << mLayerId;
259+
result.icon = mLayerIcon;
243260
result.score = static_cast< double >( string.length() ) / result.displayString.size();
244261
emit resultFetched( result );
245262

src/app/locator/qgsinbuiltlocatorfilters.h

+19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#define QGSINBUILTLOCATORFILTERS_H
2020

2121
#include "qgslocatorfilter.h"
22+
#include "qgsexpressioncontext.h"
23+
#include "qgsfeatureiterator.h"
24+
2225
class QAction;
2326

2427
class QgsLayerTreeLocatorFilter : public QgsLocatorFilter
@@ -28,10 +31,12 @@ class QgsLayerTreeLocatorFilter : public QgsLocatorFilter
2831
public:
2932

3033
QgsLayerTreeLocatorFilter( QObject *parent = nullptr );
34+
QgsLayerTreeLocatorFilter *clone() const override;
3135
QString name() const override { return QStringLiteral( "layertree" ); }
3236
QString displayName() const override { return tr( "Project Layers" ); }
3337
Priority priority() const override { return Highest; }
3438
QString prefix() const override { return QStringLiteral( "l" ); }
39+
QgsLocatorFilter::Flags flags() const override { return QgsLocatorFilter::FlagFast; }
3540

3641
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
3742
void triggerResult( const QgsLocatorResult &result ) override;
@@ -45,10 +50,12 @@ class QgsLayoutLocatorFilter : public QgsLocatorFilter
4550
public:
4651

4752
QgsLayoutLocatorFilter( QObject *parent = nullptr );
53+
QgsLayoutLocatorFilter *clone() const override;
4854
QString name() const override { return QStringLiteral( "layouts" ); }
4955
QString displayName() const override { return tr( "Project Layouts" ); }
5056
Priority priority() const override { return Highest; }
5157
QString prefix() const override { return QStringLiteral( "pl" ); }
58+
QgsLocatorFilter::Flags flags() const override { return QgsLocatorFilter::FlagFast; }
5259

5360
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
5461
void triggerResult( const QgsLocatorResult &result ) override;
@@ -62,10 +69,12 @@ class QgsActionLocatorFilter : public QgsLocatorFilter
6269
public:
6370

6471
QgsActionLocatorFilter( const QList<QWidget *> &parentObjectsForActions, QObject *parent = nullptr );
72+
QgsActionLocatorFilter *clone() const override;
6573
QString name() const override { return QStringLiteral( "actions" ); }
6674
QString displayName() const override { return tr( "Actions" ); }
6775
Priority priority() const override { return Lowest; }
6876
QString prefix() const override { return QStringLiteral( "." ); }
77+
QgsLocatorFilter::Flags flags() const override { return QgsLocatorFilter::FlagFast; }
6978

7079
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
7180
void triggerResult( const QgsLocatorResult &result ) override;
@@ -84,13 +93,23 @@ class QgsActiveLayerFeaturesLocatorFilter : public QgsLocatorFilter
8493
public:
8594

8695
QgsActiveLayerFeaturesLocatorFilter( QObject *parent = nullptr );
96+
QgsActiveLayerFeaturesLocatorFilter *clone() const override;
8797
QString name() const override { return QStringLiteral( "features" ); }
8898
QString displayName() const override { return tr( "Active Layer Features" ); }
8999
Priority priority() const override { return Medium; }
90100
QString prefix() const override { return QStringLiteral( "f" ); }
91101

102+
void prepare( const QString &string, const QgsLocatorContext &context ) override;
92103
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
93104
void triggerResult( const QgsLocatorResult &result ) override;
105+
106+
private:
107+
108+
QgsExpression mDispExpression;
109+
QgsExpressionContext mContext;
110+
QgsFeatureIterator mIterator;
111+
QString mLayerId;
112+
QIcon mLayerIcon;
94113
};
95114

96115

0 commit comments

Comments
 (0)