diff options
author | Matti Picus <matti.picus@gmail.com> | 2019-10-15 23:24:33 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-15 23:24:33 +0300 |
commit | 96f32207285423ee14341c2b16e99a46c6da3728 (patch) | |
tree | 16ac874afb9a25693c0f44add0146d1dd98bdd72 | |
parent | 4e11da37bf94a0f496f236e9706205ac81683058 (diff) | |
parent | 53dd7d6a706f5d9e93b41f714b46c6d32b085aec (diff) | |
download | numpy-96f32207285423ee14341c2b16e99a46c6da3728.tar.gz |
Merge pull request #14469 from sunqm/master
BUG: Fix _ctypes class circular reference. (#13808)
-rw-r--r-- | numpy/core/_internal.py | 55 | ||||
-rw-r--r-- | numpy/core/tests/test_multiarray.py | 24 |
2 files changed, 34 insertions, 45 deletions
diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index b0ea603e1..5fd643505 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -247,55 +247,13 @@ class _missing_ctypes(object): self.value = ptr -class _unsafe_first_element_pointer(object): - """ - Helper to allow viewing an array as a ctypes pointer to the first element - - This avoids: - * dealing with strides - * `.view` rejecting object-containing arrays - * `memoryview` not supporting overlapping fields - """ - def __init__(self, arr): - self.base = arr - - @property - def __array_interface__(self): - i = dict( - shape=(), - typestr='|V0', - data=(self.base.__array_interface__['data'][0], False), - strides=(), - version=3, - ) - return i - - -def _get_void_ptr(arr): - """ - Get a `ctypes.c_void_p` to arr.data, that keeps a reference to the array - """ - import numpy as np - # convert to a 0d array that has a data pointer referrign to the start - # of arr. This holds a reference to arr. - simple_arr = np.asarray(_unsafe_first_element_pointer(arr)) - - # create a `char[0]` using the same memory. - c_arr = (ctypes.c_char * 0).from_buffer(simple_arr) - - # finally cast to void* - return ctypes.cast(ctypes.pointer(c_arr), ctypes.c_void_p) - - class _ctypes(object): def __init__(self, array, ptr=None): self._arr = array if ctypes: self._ctypes = ctypes - # get a void pointer to the buffer, which keeps the array alive - self._data = _get_void_ptr(array) - assert self._data.value == ptr + self._data = self._ctypes.c_void_p(ptr) else: # fake a pointer-like object that holds onto the reference self._ctypes = _missing_ctypes() @@ -317,7 +275,14 @@ class _ctypes(object): The returned pointer will keep a reference to the array. """ - return self._ctypes.cast(self._data, obj) + # _ctypes.cast function causes a circular reference of self._data in + # self._data._objects. Attributes of self._data cannot be released + # until gc.collect is called. Make a copy of the pointer first then let + # it hold the array reference. This is a workaround to circumvent the + # CPython bug https://bugs.python.org/issue12836 + ptr = self._ctypes.cast(self._data, obj) + ptr._arr = self._arr + return ptr def shape_as(self, obj): """ @@ -385,7 +350,7 @@ class _ctypes(object): Enables `c_func(some_array.ctypes)` """ - return self._data + return self.data_as(ctypes.c_void_p) # kept for compatibility get_data = data.fget diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 9b124f603..596707581 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -7975,6 +7975,8 @@ class TestFormat(object): dst = object.__format__(a, '30') assert_equal(res, dst) +from numpy.testing import IS_PYPY + class TestCTypes(object): def test_ctypes_is_available(self): @@ -8041,7 +8043,29 @@ class TestCTypes(object): # but when the `ctypes_ptr` object dies, so should `arr` del ctypes_ptr + if IS_PYPY: + # Pypy does not recycle arr objects immediately. Trigger gc to + # release arr. Cpython uses refcounts. An explicit call to gc + # should not be needed here. + break_cycles() + assert_(arr_ref() is None, "unknowable whether ctypes pointer holds a reference") + + def test_ctypes_as_parameter_holds_reference(self): + arr = np.array([None]).copy() + + arr_ref = weakref.ref(arr) + + ctypes_ptr = arr.ctypes._as_parameter_ + + # `ctypes_ptr` should hold onto `arr` + del arr break_cycles() + assert_(arr_ref() is not None, "ctypes pointer did not hold onto a reference") + + # but when the `ctypes_ptr` object dies, so should `arr` + del ctypes_ptr + if IS_PYPY: + break_cycles() assert_(arr_ref() is None, "unknowable whether ctypes pointer holds a reference") |