Skip to content

Commit 8f902e7

Browse files
committed
Implement manual locks on QgsSpatialIndex
Since libspatialindex is not thread safe on all platforms, and have expressed desire to remove the thread safety that they DO have on remaining platforms, it's safer and easier for us to manually add locks to QgsSpatialIndex and be gauranteed that this class is thread safe on all platforms and libspatialindex versions. Also improve docs for the class.
1 parent 426c72f commit 8f902e7

File tree

4 files changed

+78
-57
lines changed

4 files changed

+78
-57
lines changed

python/core/qgsspatialindex.sip.in

+32-22
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@
1616

1717
class QgsSpatialIndex
1818
{
19+
%Docstring
20+
21+
A spatial index for QgsFeature objects.
22+
23+
QgsSpatialIndex objects are implicitly shared and can be inexpensively copied.
24+
25+
.. note::
26+
27+
While the underlying libspatialindex is not thread safe on some platforms, the QgsSpatialIndex
28+
class implements its own locks and accordingly, a single QgsSpatialIndex object can safely
29+
be used across multiple threads.
30+
%End
1931

2032
%TypeHeaderCode
2133
#include "qgsspatialindex.h"
@@ -25,7 +37,7 @@ class QgsSpatialIndex
2537

2638
QgsSpatialIndex();
2739
%Docstring
28-
Constructor - creates R-tree
40+
Constructor for QgsSpatialIndex. Creates an empty R-tree index.
2941
%End
3042

3143
explicit QgsSpatialIndex( const QgsFeatureIterator &fi, QgsFeedback *feedback = 0 );
@@ -60,24 +72,10 @@ Copy constructor
6072
~QgsSpatialIndex();
6173

6274

63-
void detach();
64-
%Docstring
65-
Detaches the index, forcing a deep copy of the underlying
66-
spatial index data.
67-
68-
Since the underlying libspatialindex is not thread safe on some platforms (e.g. Windows),
69-
manual calls to detach() must be made if a QgsSpatialIndex is to be accessed across multiple threads.
70-
71-
Note that for platforms on which libspatialindex is thread safe, calling
72-
detach() has no effect and does not force the deep copy.
7375

74-
.. versionadded:: 3.0
75-
%End
76-
77-
78-
bool insertFeature( const QgsFeature &f );
76+
bool insertFeature( const QgsFeature &feature );
7977
%Docstring
80-
Add feature to index
78+
Adds a ``feature`` to the index.
8179
%End
8280

8381
bool insertFeature( QgsFeatureId id, const QgsRectangle &bounds );
@@ -89,21 +87,33 @@ Add a feature ``id`` to the index with a specified bounding box.
8987
.. versionadded:: 3.0
9088
%End
9189

92-
bool deleteFeature( const QgsFeature &f );
90+
bool deleteFeature( const QgsFeature &feature );
9391
%Docstring
94-
Remove feature from index
92+
Removes a ``feature`` from the index.
9593
%End
9694

9795

9896

99-
QList<QgsFeatureId> intersects( const QgsRectangle &rect ) const;
97+
QList<QgsFeatureId> intersects( const QgsRectangle &rectangle ) const;
10098
%Docstring
101-
Returns features that intersect the specified rectangle
99+
Returns a list of features with a bounding box which intersects the specified ``rectangle``.
100+
101+
.. note::
102+
103+
The intersection test is performed based on the feature bounding boxes only, so for non-point
104+
geometry features it is necessary to manually test the returned features for exact geometry intersection
105+
when required.
102106
%End
103107

104108
QList<QgsFeatureId> nearestNeighbor( const QgsPointXY &point, int neighbors ) const;
105109
%Docstring
106-
Returns nearest neighbors (their count is specified by second parameter)
110+
Returns nearest neighbors to a ``point``. The number of neighbours returned is specified
111+
by the ``neighbours`` argument.
112+
113+
.. note::
114+
115+
The nearest neighbour test is performed based on the feature bounding boxes only, so for non-point
116+
geometry features this method is not guaranteed to return the actual closest neighbours.
107117
%End
108118

109119

src/core/providers/memory/qgsmemoryfeatureiterator.cpp

+1-5
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,13 @@ bool QgsMemoryFeatureIterator::close()
231231
QgsMemoryFeatureSource::QgsMemoryFeatureSource( const QgsMemoryProvider *p )
232232
: mFields( p->mFields )
233233
, mFeatures( p->mFeatures )
234-
, mSpatialIndex( p->mSpatialIndex ? qgis::make_unique< QgsSpatialIndex >( *p->mSpatialIndex ) : nullptr ) // this is just a shallow copy, but see below...
234+
, mSpatialIndex( p->mSpatialIndex ? qgis::make_unique< QgsSpatialIndex >( *p->mSpatialIndex ) : nullptr ) // just shallow copy
235235
, mSubsetString( p->mSubsetString )
236236
, mCrs( p->mCrs )
237237
{
238238
mExpressionContext << QgsExpressionContextUtils::globalScope()
239239
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() );
240240
mExpressionContext.setFields( mFields );
241-
242-
// QgsSpatialIndex is not thread safe - so make spatial index safe to use across threads by forcing a full deep copy
243-
if ( mSpatialIndex && p->thread() != QThread::currentThread() )
244-
mSpatialIndex->detach();
245241
}
246242

247243
QgsFeatureIterator QgsMemoryFeatureSource::getFeatures( const QgsFeatureRequest &request )

src/core/qgsspatialindex.cpp

+11-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "qgsfeedback.h"
2525

2626
#include "SpatialIndex.h"
27+
#include <QMutex>
28+
#include <QMutexLocker>
2729

2830
using namespace SpatialIndex;
2931

@@ -183,6 +185,8 @@ class QgsSpatialIndexData : public QSharedData
183185
QgsSpatialIndexData( const QgsSpatialIndexData &other )
184186
: QSharedData( other )
185187
{
188+
QMutexLocker locker( &other.mMutex );
189+
186190
initTree();
187191

188192
// copy R-tree data one by one (is there a faster way??)
@@ -230,6 +234,8 @@ class QgsSpatialIndexData : public QSharedData
230234
//! R-tree containing spatial index
231235
SpatialIndex::ISpatialIndex *mRTree = nullptr;
232236

237+
mutable QMutex mMutex;
238+
233239
};
234240

235241
// -------------------------------------------------------------------------
@@ -266,14 +272,6 @@ QgsSpatialIndex &QgsSpatialIndex::operator=( const QgsSpatialIndex &other )
266272
return *this;
267273
}
268274

269-
void QgsSpatialIndex::detach()
270-
{
271-
// libspatialindex is not thread safe on windows - so force the deep copy
272-
#if defined(Q_OS_WIN)
273-
d.detach();
274-
#endif
275-
}
276-
277275
SpatialIndex::Region QgsSpatialIndex::rectToRegion( const QgsRectangle &rect )
278276
{
279277
double pt1[2] = { rect.xMinimum(), rect.yMinimum() },
@@ -315,6 +313,8 @@ bool QgsSpatialIndex::insertFeature( QgsFeatureId id, const QgsRectangle &rect )
315313
{
316314
SpatialIndex::Region r( rectToRegion( rect ) );
317315

316+
QMutexLocker locker( &d->mMutex );
317+
318318
// TODO: handle possible exceptions correctly
319319
try
320320
{
@@ -346,6 +346,7 @@ bool QgsSpatialIndex::deleteFeature( const QgsFeature &f )
346346
if ( !featureInfo( f, r, id ) )
347347
return false;
348348

349+
QMutexLocker locker( &d->mMutex );
349350
// TODO: handle exceptions
350351
return d->mRTree->deleteData( r, FID_TO_NUMBER( id ) );
351352
}
@@ -357,6 +358,7 @@ QList<QgsFeatureId> QgsSpatialIndex::intersects( const QgsRectangle &rect ) cons
357358

358359
SpatialIndex::Region r = rectToRegion( rect );
359360

361+
QMutexLocker locker( &d->mMutex );
360362
d->mRTree->intersectsWithQuery( r, visitor );
361363

362364
return list;
@@ -370,6 +372,7 @@ QList<QgsFeatureId> QgsSpatialIndex::nearestNeighbor( const QgsPointXY &point, i
370372
double pt[2] = { point.x(), point.y() };
371373
Point p( pt, 2 );
372374

375+
QMutexLocker locker( &d->mMutex );
373376
d->mRTree->nearestNeighborQuery( neighbors, p, visitor );
374377

375378
return list;

src/core/qgsspatialindex.h

+34-22
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ class QgsFeatureSource;
5252
/**
5353
* \ingroup core
5454
* \class QgsSpatialIndex
55+
*
56+
* A spatial index for QgsFeature objects.
57+
*
58+
* QgsSpatialIndex objects are implicitly shared and can be inexpensively copied.
59+
*
60+
* \note While the underlying libspatialindex is not thread safe on some platforms, the QgsSpatialIndex
61+
* class implements its own locks and accordingly, a single QgsSpatialIndex object can safely
62+
* be used across multiple threads.
5563
*/
5664
class CORE_EXPORT QgsSpatialIndex
5765
{
@@ -60,7 +68,9 @@ class CORE_EXPORT QgsSpatialIndex
6068

6169
/* creation of spatial index */
6270

63-
//! Constructor - creates R-tree
71+
/**
72+
* Constructor for QgsSpatialIndex. Creates an empty R-tree index.
73+
*/
6474
QgsSpatialIndex();
6575

6676
/**
@@ -97,24 +107,12 @@ class CORE_EXPORT QgsSpatialIndex
97107
//! Implement assignment operator
98108
QgsSpatialIndex &operator=( const QgsSpatialIndex &other );
99109

100-
/**
101-
* Detaches the index, forcing a deep copy of the underlying
102-
* spatial index data.
103-
*
104-
* Since the underlying libspatialindex is not thread safe on some platforms (e.g. Windows),
105-
* manual calls to detach() must be made if a QgsSpatialIndex is to be accessed across multiple threads.
106-
*
107-
* Note that for platforms on which libspatialindex is thread safe, calling
108-
* detach() has no effect and does not force the deep copy.
109-
*
110-
* \since QGIS 3.0
111-
*/
112-
void detach();
113-
114110
/* operations */
115111

116-
//! Add feature to index
117-
bool insertFeature( const QgsFeature &f );
112+
/**
113+
* Adds a \a feature to the index.
114+
*/
115+
bool insertFeature( const QgsFeature &feature );
118116

119117
/**
120118
* Add a feature \a id to the index with a specified bounding box.
@@ -123,16 +121,30 @@ class CORE_EXPORT QgsSpatialIndex
123121
*/
124122
bool insertFeature( QgsFeatureId id, const QgsRectangle &bounds );
125123

126-
//! Remove feature from index
127-
bool deleteFeature( const QgsFeature &f );
124+
/**
125+
* Removes a \a feature from the index.
126+
*/
127+
bool deleteFeature( const QgsFeature &feature );
128128

129129

130130
/* queries */
131131

132-
//! Returns features that intersect the specified rectangle
133-
QList<QgsFeatureId> intersects( const QgsRectangle &rect ) const;
132+
/**
133+
* Returns a list of features with a bounding box which intersects the specified \a rectangle.
134+
*
135+
* \note The intersection test is performed based on the feature bounding boxes only, so for non-point
136+
* geometry features it is necessary to manually test the returned features for exact geometry intersection
137+
* when required.
138+
*/
139+
QList<QgsFeatureId> intersects( const QgsRectangle &rectangle ) const;
134140

135-
//! Returns nearest neighbors (their count is specified by second parameter)
141+
/**
142+
* Returns nearest neighbors to a \a point. The number of neighbours returned is specified
143+
* by the \a neighbours argument.
144+
*
145+
* \note The nearest neighbour test is performed based on the feature bounding boxes only, so for non-point
146+
* geometry features this method is not guaranteed to return the actual closest neighbours.
147+
*/
136148
QList<QgsFeatureId> nearestNeighbor( const QgsPointXY &point, int neighbors ) const;
137149

138150
/* debugging */

0 commit comments

Comments
 (0)