Skip to content

Commit

Permalink
BUG: io.netcdf: do not close mmap if there are references left to it
Browse files Browse the repository at this point in the history
  • Loading branch information
pv committed Sep 3, 2014
1 parent db20a63 commit 6a5d0a1
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 5 deletions.
22 changes: 19 additions & 3 deletions scipy/io/netcdf.py
Expand Up @@ -32,6 +32,8 @@
__all__ = ['netcdf_file']


import warnings
import weakref
from operator import mul
import mmap as mm

Expand Down Expand Up @@ -243,9 +245,23 @@ def close(self):
try:
self.flush()
finally:
self._mm_buf = None
if self._mm is not None:
self._mm.close()
self.variables = {}
if self._mm_buf is not None:
ref = weakref.ref(self._mm_buf)
self._mm_buf = None
if ref() is None:
# self._mm_buf is gc'd, and we can close the mmap
self._mm.close()
else:
# we cannot close self._mm, since self._mm_buf is
# alive and there may still be arrays referring to it
warnings.warn((
"Cannot close a netcdf_file opened with mmap=True, when "
"netcdf_variables or arrays referring to its data still exist. "
"All data arrays obtained from such files refer directly to "
"data on disk, and must be copied before the file can be cleanly "
"closed. (See netcdf_file docstring for more information on mmap.)"
), category=RuntimeWarning)
self._mm = None
self.fp.close()
__del__ = close
Expand Down
29 changes: 27 additions & 2 deletions scipy/io/tests/test_netcdf.py
Expand Up @@ -5,12 +5,13 @@
from os.path import join as pjoin, dirname
import shutil
import tempfile
import warnings
from io import BytesIO
from glob import glob
from contextlib import contextmanager

import numpy as np
from numpy.testing import assert_, assert_allclose
from numpy.testing import assert_, assert_allclose, assert_warns

from scipy.io.netcdf import netcdf_file

Expand Down Expand Up @@ -232,10 +233,34 @@ def test_mmaps_closed():
vars = []
for i in range(1100):
f = netcdf_file(filename, mmap=True)
vars.append(f.variables['lat'])
vars.append(f.variables['lat'][:].copy())
f.close()


def test_mmaps_segfault():
filename = pjoin(TEST_DATA_PATH, 'example_1.nc')

with warnings.catch_warnings():
warnings.simplefilter("error")
with netcdf_file(filename, mmap=True) as f:
x = f.variables['lat'][:]
# should not raise warnings
del x

def doit():
with netcdf_file(filename, mmap=True) as f:
return f.variables['lat'][:]

# should raise a warning
assert_warns(RuntimeWarning, doit)

# should not crash
with warnings.catch_warnings():
warnings.simplefilter("ignore")
x = doit()
x.sum()


def test_zero_dimensional_var():
io = BytesIO()
with make_simple(io, 'w') as f:
Expand Down

0 comments on commit 6a5d0a1

Please sign in to comment.