Skip to content

Commit c237ba7

Browse files
committed
[Spatialite provider] Make sure to release dangling connections on provider closing
Fixes #15137
1 parent 55f172b commit c237ba7

File tree

4 files changed

+105
-5
lines changed

4 files changed

+105
-5
lines changed

src/providers/spatialite/qgsspatialiteconnection.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ class QgsSqliteHandle
131131
//
132132
public:
133133
QgsSqliteHandle( sqlite3 * handle, const QString& dbPath, bool shared )
134-
: ref( shared ? 1 : -1 ), sqlite_handle( handle ), mDbPath( dbPath )
134+
: ref( shared ? 1 : -1 )
135+
, sqlite_handle( handle )
136+
, mDbPath( dbPath )
137+
, mIsValid( true )
135138
{
136139
}
137140

@@ -145,6 +148,16 @@ class QgsSqliteHandle
145148
return mDbPath;
146149
}
147150

151+
bool isValid() const
152+
{
153+
return mIsValid;
154+
}
155+
156+
void invalidate()
157+
{
158+
mIsValid = false;
159+
}
160+
148161
//
149162
// libsqlite3 wrapper
150163
//
@@ -165,6 +178,7 @@ class QgsSqliteHandle
165178
int ref;
166179
sqlite3 *sqlite_handle;
167180
QString mDbPath;
181+
bool mIsValid;
168182

169183
static QMap < QString, QgsSqliteHandle * > handles;
170184
};

src/providers/spatialite/qgsspatialiteconnpool.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ inline void qgsConnectionPool_ConnectionDestroy( QgsSqliteHandle* c )
3636

3737
inline void qgsConnectionPool_InvalidateConnection( QgsSqliteHandle* c )
3838
{
39-
Q_UNUSED( c );
39+
/* Invalidation is used in particular by the WFS provider that uses a */
40+
/* temporary spatialite DB and want to delete it at some point. For that */
41+
/* it must invalidate all handles pointing to it */
42+
c->invalidate();
4043
}
4144

4245
inline bool qgsConnectionPool_ConnectionIsValid( QgsSqliteHandle* c )
4346
{
44-
Q_UNUSED( c );
45-
return true;
47+
return c->isValid();
4648
}
4749

4850

src/providers/spatialite/qgsspatialiteprovider.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
589589
QgsSpatiaLiteProvider::~QgsSpatiaLiteProvider()
590590
{
591591
closeDb();
592+
invalidateConnections( mSqlitePath );
592593
}
593594

594595
QgsAbstractFeatureSource* QgsSpatiaLiteProvider::featureSource() const

tests/src/python/test_provider_spatialite.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import qgis
1616
import os
1717
import shutil
18+
import sys
1819
import tempfile
1920

2021
from qgis.core import QgsVectorLayer, QgsPoint, QgsFeature
@@ -36,10 +37,23 @@
3637
start_app()
3738
TEST_DATA_DIR = unitTestDataPath()
3839

39-
4040
def die(error_message):
4141
raise Exception(error_message)
4242

43+
def count_opened_filedescriptors(filename_to_test):
44+
count = -1
45+
if sys.platform.startswith('linux'):
46+
count = 0
47+
open_files_dirname = '/proc/%d/fd' % os.getpid()
48+
filenames = os.listdir(open_files_dirname)
49+
for filename in filenames:
50+
full_filename = open_files_dirname + '/' + filename
51+
if os.path.exists(full_filename):
52+
link = os.readlink(full_filename)
53+
if os.path.basename(link) == os.path.basename(filename_to_test):
54+
count += 1
55+
return count
56+
4357

4458
class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
4559

@@ -214,5 +228,74 @@ def test_invalid_iterator(self):
214228
layer = None
215229
os.unlink(corrupt_dbname)
216230

231+
232+
def testNoDanglingFileDescriptorAfterCloseVariant1(self):
233+
''' Test that when closing the provider all file handles are released '''
234+
235+
temp_dbname = self.dbname + '.no_dangling_test1'
236+
shutil.copy(self.dbname, temp_dbname)
237+
238+
vl = QgsVectorLayer("dbname=%s table=test_n (geometry)" % temp_dbname, "test_n", "spatialite")
239+
self.assertTrue(vl.isValid())
240+
# The iterator will take one extra connection
241+
myiter = vl.getFeatures()
242+
print(vl.featureCount())
243+
# Consume one feature but the iterator is still opened
244+
f = next(myiter)
245+
self.assertTrue(f.isValid())
246+
247+
if sys.platform.startswith('linux'):
248+
self.assertEqual(count_opened_filedescriptors(temp_dbname), 2)
249+
250+
# does NO release one file descriptor, because shared with the iterator
251+
del vl
252+
253+
# Non portable, but Windows testing is done with trying to unlink
254+
if sys.platform.startswith('linux'):
255+
self.assertEqual(count_opened_filedescriptors(temp_dbname), 2)
256+
257+
f = next(myiter)
258+
self.assertTrue(f.isValid())
259+
260+
# Should release one file descriptor
261+
del myiter
262+
263+
# Non portable, but Windows testing is done with trying to unlink
264+
if sys.platform.startswith('linux'):
265+
self.assertEqual(count_opened_filedescriptors(temp_dbname), 0)
266+
267+
# Check that deletion works well (can only fail on Windows)
268+
os.unlink(temp_dbname)
269+
self.assertFalse(os.path.exists(temp_dbname))
270+
271+
def testNoDanglingFileDescriptorAfterCloseVariant2(self):
272+
''' Test that when closing the provider all file handles are released '''
273+
274+
temp_dbname = self.dbname + '.no_dangling_test2'
275+
shutil.copy(self.dbname, temp_dbname)
276+
277+
vl = QgsVectorLayer("dbname=%s table=test_n (geometry)" % temp_dbname, "test_n", "spatialite")
278+
self.assertTrue(vl.isValid())
279+
self.assertTrue(vl.isValid())
280+
# Consume all features.
281+
myiter = vl.getFeatures()
282+
for feature in myiter:
283+
pass
284+
# The iterator is closed
285+
if sys.platform.startswith('linux'):
286+
self.assertEqual(count_opened_filedescriptors(temp_dbname), 2)
287+
288+
# Should release one file descriptor
289+
del vl
290+
291+
# Non portable, but Windows testing is done with trying to unlink
292+
if sys.platform.startswith('linux'):
293+
self.assertEqual(count_opened_filedescriptors(temp_dbname), 0)
294+
295+
# Check that deletion works well (can only fail on Windows)
296+
os.unlink(temp_dbname)
297+
self.assertFalse(os.path.exists(temp_dbname))
298+
299+
217300
if __name__ == '__main__':
218301
unittest.main()

0 commit comments

Comments
 (0)