diff options
author | Sebastian Berg <sebastian@sipsolutions.net> | 2020-08-13 15:22:52 -0500 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2020-10-19 18:33:41 -0500 |
commit | 78080362911a36a2f4eff99a41b42dfb4c18c39c (patch) | |
tree | 4801464c6a641c0b25c604245d7f456e6491055e | |
parent | 028a61af17b2b3d960323fc0a9595bdfd91b9a7e (diff) | |
download | numpy-78080362911a36a2f4eff99a41b42dfb4c18c39c.tar.gz |
BUG: Fix leak for relaxed strides when exporting both C- and F-order
Exporting these multiple times alternating would previously cause
a new buffer-info to be created each time.
-rw-r--r-- | numpy/core/src/multiarray/buffer.c | 30 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 15 |
2 files changed, 36 insertions, 9 deletions
diff --git a/numpy/core/src/multiarray/buffer.c b/numpy/core/src/multiarray/buffer.c index 770813e70..6415dc276 100644 --- a/numpy/core/src/multiarray/buffer.c +++ b/numpy/core/src/multiarray/buffer.c @@ -625,22 +625,34 @@ _buffer_get_info(PyObject *obj, npy_bool f_contiguous) item_list = PyDict_GetItem(_buffer_info_cache, key); if (item_list != NULL) { + Py_ssize_t item_list_length = PyList_GET_SIZE(item_list); Py_INCREF(item_list); - if (PyList_GET_SIZE(item_list) > 0) { - item = PyList_GetItem(item_list, PyList_GET_SIZE(item_list) - 1); + if (item_list_length > 0) { + item = PyList_GetItem(item_list, item_list_length - 1); old_info = (_buffer_info_t*)PyLong_AsVoidPtr(item); - /* - * TODO: In rare cases when an array is both C- and - * F-contiguous, and buffers are exported alternating - * we may always have a cache miss even though the correct - * info had already been exported before (causing a memory - * leak). - */ if (_buffer_info_cmp(info, old_info) == 0) { _buffer_info_free(info); info = old_info; } + else if (item_list_length > 1 && info->ndim > 1) { + /* + * Some arrays are C- and F-contiguous and if they have more + * than one dimension, the buffer-info may differ between the + * two due to RELAXED_STRIDES_CHECKING. + * If we export both buffers, the first stored one may be the + * one for the other contiguity, so check both in this case. + * This is generally very unlikely in all other cases, since + * in all other cases the first one will match unless array + * metadata was modified in-place (which is discouraged). + */ + item = PyList_GetItem(item_list, item_list_length - 2); + old_info = (_buffer_info_t*)PyLong_AsVoidPtr(item); + if (_buffer_info_cmp(info, old_info) == 0) { + _buffer_info_free(info); + info = old_info; + } + } } } else { diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index d376fe6b7..c80d0c073 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -7206,6 +7206,21 @@ class TestNewBufferProtocol: arr, ['C_CONTIGUOUS']) assert_(strides[-1] == 8) + @pytest.mark.valgrind_error(reason="leaks buffer info cache temporarily.") + @pytest.mark.skipif(not np.ones((10, 1), order="C").flags.f_contiguous, + reason="Test is unnecessary (but fails) without relaxed strides.") + def test_relaxed_strides_buffer_info_leak(self, arr=np.ones((1, 10))): + """Test that alternating export of C- and F-order buffers from + an array which is both C- and F-order when relaxed strides is + active works. + This test defines array in the signature to ensure leaking more + references every time the test is run (catching the leak with + pytest-leaks). + """ + for i in range(10): + _multiarray_tests.get_buffer_info(arr, ['F_CONTIGUOUS']) + _multiarray_tests.get_buffer_info(arr, ['C_CONTIGUOUS']) + def test_out_of_order_fields(self): dt = np.dtype(dict( formats=['<i4', '<i4'], |