Commits

Armin Rigo committed a8efbdc

Finally found out the "right" way to implement ffi.gc(), in just a
few lines of Python code using weakrefs with callbacks.

Comments (0)

Files changed (5)

             replace_with = ' ' + replace_with
         return self._backend.getcname(cdecl, replace_with)
 
+    def gc(self, cdata, destructor):
+        """Return a new cdata object that points to the same
+        data.  Later, when this new cdata object is garbage-collected,
+        'destructor(old_cdata_object)' will be called.
+        """
+        try:
+            gc_weakrefs = self.gc_weakrefs
+        except AttributeError:
+            from .gc_weakref import GcWeakrefs
+            gc_weakrefs = self.gc_weakrefs = GcWeakrefs(self)
+        return gc_weakrefs.build(cdata, destructor)
+
     def _get_cached_btype(self, type):
         try:
             BType = self._cached_btypes[type]

cffi/backend_ctypes.py

 from . import model
 
 class CTypesData(object):
-    __slots__ = []
+    __slots__ = ['__weakref__']
 
     def __init__(self, *args):
         raise TypeError("cannot instantiate %r" % (self.__class__,))
+from weakref import ref
+
+
+class GcWeakrefs(object):
+    # code copied and adapted from WeakKeyDictionary.
+
+    def __init__(self, ffi):
+        self.ffi = ffi
+        self.data = data = {}
+        def remove(k):
+            destructor, cdata = data.pop(k)
+            destructor(cdata)
+        self.remove = remove
+
+    def build(self, cdata, destructor):
+        # make a new cdata of the same type as the original one
+        new_cdata = self.ffi.cast(self.ffi.typeof(cdata), cdata)
+        self.data[ref(new_cdata, self.remove)] = destructor, cdata
+        return new_cdata
 ``ffi.getcname(ffi.typeof(x), "*")`` returns the string representation
 of the C type "pointer to the same type than x".
 
+``ffi.gc(cdata, destructor)``: return a new cdata object that points to the
+same data.  Later, when this new cdata object is garbage-collected,
+``destructor(old_cdata_object)`` will be called.  Example of usage:
+``ptr = ffi.gc(lib.malloc(42), lib.free)``.  *New in version 0.3* (together
+with the fact that any cdata object can be weakly referenced).
+
+.. "versionadded:: 0.3" --- inlined in the previous paragraph
+
 
 Unimplemented features
 ----------------------

testing/backend_tests.py

         q = ffi.cast("int[3]", p)
         assert q[0] == -5
         assert repr(q).startswith("<cdata 'int[3]' 0x")
+
+    def test_gc(self):
+        ffi = FFI(backend=self.Backend())
+        p = ffi.new("int *", 123)
+        seen = []
+        def destructor(p1):
+            assert p1 is p
+            assert p1[0] == 123
+            seen.append(1)
+        q = ffi.gc(p, destructor)
+        import gc; gc.collect()
+        assert seen == []
+        del q
+        import gc; gc.collect(); gc.collect(); gc.collect()
+        assert seen == [1]