diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c index 34d14660..172aaefd 100644 --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -6065,6 +6065,17 @@ static PyObject *b_gcp(PyObject *self, PyObject *args, PyObject *kwds) &CData_Type, &origobj, &destructor)) return NULL; + if (destructor == Py_None) { + if (!PyObject_TypeCheck(origobj, &CDataGCP_Type)) { + PyErr_SetString(PyExc_TypeError, + "Can remove destructor only on a object " + "previously returned by ffi.gc()"); + return NULL; + } + Py_CLEAR(((CDataObject_gcp *)origobj)->destructor); + Py_RETURN_NONE; + } + cd = allocate_gcp_object(origobj, origobj->c_type, destructor); return (PyObject *)cd; } diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py index f09b194c..507a0aca 100644 --- a/cffi/backend_ctypes.py +++ b/cffi/backend_ctypes.py @@ -995,13 +995,23 @@ def callback(self, BType, source, error, onerror): def gcp(self, cdata, destructor): BType = self.typeof(cdata) + + if destructor is None: + if not (hasattr(BType, '_gcp_type') and + BType._gcp_type is BType): + raise TypeError("Can remove destructor only on a object " + "previously returned by ffi.gc()") + cdata._destructor = None + return None + try: gcp_type = BType._gcp_type except AttributeError: class CTypesDataGcp(BType): __slots__ = ['_orig', '_destructor'] def __del__(self): - self._destructor(self._orig) + if self._destructor is not None: + self._destructor(self._orig) gcp_type = BType._gcp_type = CTypesDataGcp new_cdata = self.cast(gcp_type, cdata) new_cdata._orig = cdata diff --git a/doc/source/ref.rst b/doc/source/ref.rst index fd847678..46efa026 100644 --- a/doc/source/ref.rst +++ b/doc/source/ref.rst @@ -318,6 +318,11 @@ returned by ``ffi.new()``, the returned pointer objects have *ownership*, which means the destructor is called as soon as *this* exact returned object is garbage-collected. +**ffi.gc(ptr, None)**: removes the ownership on a object returned by a +regular call to ``ffi.gc``, and no destructor will be called when it +is garbage-collected. The object is modified in-place, and the +function returns ``None``. + Note that this should be avoided for large memory allocations or for limited resources. This is particularly true on PyPy: its GC does not know how much memory or how many resources the returned ``ptr`` diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst index fa31f147..58e9eb80 100644 --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -3,6 +3,13 @@ What's New ====================== +v1.next +======= + +* ``ffi.gc(p, None)`` removes the destructor on an object previously + created by another call to ``ffi.gc()`` + + v1.6 ==== diff --git a/testing/cffi0/backend_tests.py b/testing/cffi0/backend_tests.py index 536d9ea5..3a9dcdaf 100644 --- a/testing/cffi0/backend_tests.py +++ b/testing/cffi0/backend_tests.py @@ -1522,6 +1522,20 @@ def test_gc_4(self): import gc; gc.collect(); gc.collect(); gc.collect() assert seen == [3] + def test_gc_disable(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int *", 123) + py.test.raises(TypeError, ffi.gc, p, None) + seen = [] + q1 = ffi.gc(p, lambda p: seen.append(1)) + q2 = ffi.gc(q1, lambda p: seen.append(2)) + import gc; gc.collect() + assert seen == [] + assert ffi.gc(q1, None) is None + del q1, q2 + import gc; gc.collect(); gc.collect(); gc.collect() + assert seen == [2] + def test_gc_finite_list(self): ffi = FFI(backend=self.Backend()) p = ffi.new("int *", 123)