Skip to content

Commit 2d825bc

Browse files
committedJun 30, 2016
[OGR provider] Make sure to release dangling connections on provider closing
Fixes #15137
1 parent 3cba1aa commit 2d825bc

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed
 

‎src/providers/ogr/qgsogrprovider.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,9 @@ QgsOgrProvider::~QgsOgrProvider()
385385
{
386386
close();
387387
QgsOgrConnPool::instance()->unref( dataSourceUri() );
388+
// We must also make sure to flush unusef cached connections so that
389+
// the file can be removed (#15137)
390+
QgsOgrConnPool::instance()->invalidateConnections( dataSourceUri() );
388391
}
389392

390393
QgsAbstractFeatureSource* QgsOgrProvider::featureSource() const

‎tests/src/python/test_provider_ogr.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import os
1616
import shutil
17+
import sys
1718
import tempfile
1819

1920
from qgis.core import QgsVectorLayer, QgsVectorDataProvider, QgsWKBTypes
@@ -34,6 +35,21 @@ def GDAL_COMPUTE_VERSION(maj, min, rev):
3435
# Note - doesn't implement ProviderTestCase as most OGR provider is tested by the shapefile provider test
3536

3637

38+
def count_opened_filedescriptors(filename_to_test):
39+
count = -1
40+
if sys.platform.startswith('linux'):
41+
count = 0
42+
open_files_dirname = '/proc/%d/fd' % os.getpid()
43+
filenames = os.listdir(open_files_dirname)
44+
for filename in filenames:
45+
full_filename = open_files_dirname + '/' + filename
46+
if os.path.exists(full_filename):
47+
link = os.readlink(full_filename)
48+
if os.path.basename(link) == os.path.basename(filename_to_test):
49+
count += 1
50+
return count
51+
52+
3753
class PyQgsOGRProvider(unittest.TestCase):
3854

3955
@classmethod
@@ -134,6 +150,76 @@ def testGpxElevation(self):
134150
self.assertEqual(f.constGeometry().geometry().pointN(1).z(), 2)
135151
self.assertEqual(f.constGeometry().geometry().pointN(2).z(), 3)
136152

153+
def testNoDanglingFileDescriptorAfterCloseVariant1(self):
154+
''' Test that when closing the provider all file handles are released '''
155+
156+
datasource = os.path.join(self.basetestpath, 'testNoDanglingFileDescriptorAfterCloseVariant1.csv')
157+
with open(datasource, 'wt') as f:
158+
f.write('id,WKT\n')
159+
f.write('1,\n')
160+
f.write('2,POINT(2 49)\n')
161+
162+
vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
163+
self.assertTrue(vl.isValid())
164+
# The iterator will take one extra connection
165+
myiter = vl.getFeatures()
166+
# Consume one feature but the iterator is still opened
167+
f = next(myiter)
168+
self.assertTrue(f.isValid())
169+
170+
if sys.platform.startswith('linux'):
171+
self.assertEqual(count_opened_filedescriptors(datasource), 2)
172+
173+
# Should release one file descriptor
174+
del vl
175+
176+
# Non portable, but Windows testing is done with trying to unlink
177+
if sys.platform.startswith('linux'):
178+
self.assertEqual(count_opened_filedescriptors(datasource), 1)
179+
180+
f = next(myiter)
181+
self.assertTrue(f.isValid())
182+
183+
# Should release one file descriptor
184+
del myiter
185+
186+
# Non portable, but Windows testing is done with trying to unlink
187+
if sys.platform.startswith('linux'):
188+
self.assertEqual(count_opened_filedescriptors(datasource), 0)
189+
190+
# Check that deletion works well (can only fail on Windows)
191+
os.unlink(datasource)
192+
self.assertFalse(os.path.exists(datasource))
193+
194+
def testNoDanglingFileDescriptorAfterCloseVariant2(self):
195+
''' Test that when closing the provider all file handles are released '''
196+
197+
datasource = os.path.join(self.basetestpath, 'testNoDanglingFileDescriptorAfterCloseVariant2.csv')
198+
with open(datasource, 'wt') as f:
199+
f.write('id,WKT\n')
200+
f.write('1,\n')
201+
f.write('2,POINT(2 49)\n')
202+
203+
vl = QgsVectorLayer(u'{}|layerid=0'.format(datasource), u'test', u'ogr')
204+
self.assertTrue(vl.isValid())
205+
# Consume all features.
206+
myiter = vl.getFeatures()
207+
for feature in myiter:
208+
pass
209+
# The iterator is closed, but the corresponding connection still not closed
210+
if sys.platform.startswith('linux'):
211+
self.assertEqual(count_opened_filedescriptors(datasource), 2)
212+
213+
# Should release one file descriptor
214+
del vl
215+
216+
# Non portable, but Windows testing is done with trying to unlink
217+
if sys.platform.startswith('linux'):
218+
self.assertEqual(count_opened_filedescriptors(datasource), 0)
219+
220+
# Check that deletion works well (can only fail on Windows)
221+
os.unlink(datasource)
222+
self.assertFalse(os.path.exists(datasource))
137223

138224
if __name__ == '__main__':
139225
unittest.main()

0 commit comments

Comments
 (0)