[pypy-commit] cffi default: Finally found out the "right" way to implement ffi.gc(), in just a

arigo noreply at buildbot.pypy.org
Thu Aug 9 11:35:07 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r793:a8efbdc7c1cc
Date: 2012-08-09 11:34 +0200
http://bitbucket.org/cffi/cffi/changeset/a8efbdc7c1cc/

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

diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -234,6 +234,18 @@
             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]
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -2,7 +2,7 @@
 from . import model
 
 class CTypesData(object):
-    __slots__ = []
+    __slots__ = ['__weakref__']
 
     def __init__(self, *args):
         raise TypeError("cannot instantiate %r" % (self.__class__,))
diff --git a/cffi/gc_weakref.py b/cffi/gc_weakref.py
new file mode 100644
--- /dev/null
+++ b/cffi/gc_weakref.py
@@ -0,0 +1,19 @@
+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
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -914,6 +914,14 @@
 ``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
 ----------------------
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -1279,3 +1279,18 @@
         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]


More information about the pypy-commit mailing list