Skip to content

Commit

Permalink
Add ffi.gc(ptr, None) which *removes* the destructor in-place on a ff…
Browse files Browse the repository at this point in the history
…i.gc() object.
  • Loading branch information
amauryfa committed Apr 23, 2016
1 parent f5ed0eb commit 17a1bbf
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 1 deletion.
11 changes: 11 additions & 0 deletions c/_cffi_backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
12 changes: 11 additions & 1 deletion cffi/backend_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions doc/source/ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down
7 changes: 7 additions & 0 deletions doc/source/whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
====

Expand Down
14 changes: 14 additions & 0 deletions testing/cffi0/backend_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 17a1bbf

Please sign in to comment.