Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r91958:3c4fb99e0c59
Date: 2017-07-24 10:07 +0200
http://bitbucket.org/pypy/pypy/changeset/3c4fb99e0c59/

Log:    Issue #2612

        Simplify gc.get_referrers(). Before, it wasn't guaranteed to return
        the opposite result as gc.get_referents().

diff --git a/pypy/module/gc/referents.py b/pypy/module/gc/referents.py
--- a/pypy/module/gc/referents.py
+++ b/pypy/module/gc/referents.py
@@ -47,57 +47,6 @@
 
 # ____________________________________________________________
 
-class PathEntry(object):
-    # PathEntries are nodes of a complete tree of all objects, but
-    # built lazily (there is only one branch alive at any time).
-    # Each node has a 'gcref' and the list of referents from this gcref.
-    def __init__(self, prev, gcref, referents):
-        self.prev = prev
-        self.gcref = gcref
-        self.referents = referents
-        self.remaining = len(referents)
-
-    def get_most_recent_w_obj(self):
-        entry = self
-        while entry is not None:
-            if entry.gcref:
-                w_obj = try_cast_gcref_to_w_root(entry.gcref)
-                if w_obj is not None:
-                    return w_obj
-            entry = entry.prev
-        return None
-
-def do_get_referrers(w_arg):
-    result_w = []
-    gcarg = rgc.cast_instance_to_gcref(w_arg)
-    roots = [gcref for gcref in rgc.get_rpy_roots() if gcref]
-    head = PathEntry(None, rgc.NULL_GCREF, roots)
-    while True:
-        head.remaining -= 1
-        if head.remaining >= 0:
-            gcref = head.referents[head.remaining]
-            if not rgc.get_gcflag_extra(gcref):
-                # not visited so far
-                if gcref == gcarg:
-                    w_obj = head.get_most_recent_w_obj()
-                    if w_obj is not None:
-                        result_w.append(w_obj)   # found!
-                        rgc.toggle_gcflag_extra(gcref)  # toggle twice
-                rgc.toggle_gcflag_extra(gcref)
-                head = PathEntry(head, gcref, rgc.get_rpy_referents(gcref))
-        else:
-            # no more referents to visit
-            head = head.prev
-            if head is None:
-                break
-    # done.  Clear flags carefully
-    rgc.toggle_gcflag_extra(gcarg)
-    rgc.clear_gcflag_extra(roots)
-    rgc.clear_gcflag_extra([gcarg])
-    return result_w
-
-# ____________________________________________________________
-
 def _list_w_obj_referents(gcref, result_w):
     # Get all W_Root reachable directly from gcref, and add them to
     # the list 'result_w'.
@@ -184,9 +133,22 @@
     """Return the list of objects that directly refer to any of objs."""
     if not rgc.has_gcflag_extra():
         raise missing_operation(space)
+    # xxx uses a lot of memory to make the list of all W_Root objects,
+    # but it's simpler this way and more correct than the previous
+    # version of this code (issue #2612).  It is potentially very slow
+    # because each of the n calls to _list_w_obj_referents() could take
+    # O(n) time as well, in theory, but I hope in practice the whole
+    # thing takes much less than O(n^2).  We could re-add an algorithm
+    # that visits most objects only once, if needed...
+    all_objects_w = rgc.do_get_objects(try_cast_gcref_to_w_root)
     result_w = []
-    for w_arg in args_w:
-        result_w += do_get_referrers(w_arg)
+    for w_obj in all_objects_w:
+        refs_w = []
+        gcref = rgc.cast_instance_to_gcref(w_obj)
+        _list_w_obj_referents(gcref, refs_w)
+        for w_arg in args_w:
+            if w_arg in refs_w:
+                result_w.append(w_obj)
     rgc.assert_no_more_gcflags()
     return space.newlist(result_w)
 
diff --git a/pypy/module/gc/test/test_referents.py 
b/pypy/module/gc/test/test_referents.py
--- a/pypy/module/gc/test/test_referents.py
+++ b/pypy/module/gc/test/test_referents.py
@@ -116,3 +116,37 @@
                 break   # found
         else:
             assert 0, "the tuple (7,) is not found as gc.get_referrers(7)"
+
+
+class AppTestReferentsMore(object):
+
+    def setup_class(cls):
+        from rpython.rlib import rgc
+        cls._backup = [rgc.get_rpy_roots]
+        l4 = cls.space.newlist([])
+        cls.ALL_ROOTS = [l4]
+        cls.w_ALL_ROOTS = cls.space.newlist(cls.ALL_ROOTS)
+        rgc.get_rpy_roots = lambda: (
+            map(rgc._GcRef, cls.ALL_ROOTS) + [rgc.NULL_GCREF]*2)
+        cls.w_runappdirect = cls.space.wrap(option.runappdirect)
+
+    def teardown_class(cls):
+        from rpython.rlib import rgc
+        rgc.get_rpy_roots = cls._backup[0]
+
+    def test_get_referrers(self):
+        import gc
+        class A(object):
+            pass
+        a = A()
+        if not self.runappdirect:
+            l4 = self.ALL_ROOTS[0]
+            l4.append(a)              # add 'a' to the list which is in roots
+        lst = gc.get_referrers(A)
+        assert a in lst
+        lst = gc.get_referrers(A)
+        assert a in lst
+        lst = gc.get_referrers(A)
+        assert a in lst
+        lst = gc.get_referrers(A)
+        assert a in lst
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to