[pypy-commit] pypy cpyext-gc-cycle: Added consistency check in case rrc graph changed between gc-iterations

stevie_92 pypy.commits at gmail.com
Sat Aug 31 12:25:14 EDT 2019


Author: Stefan Beyer <home at sbeyer.at>
Branch: cpyext-gc-cycle
Changeset: r97352:af16e13f7cbf
Date: 2019-08-31 18:24 +0200
http://bitbucket.org/pypy/pypy/changeset/af16e13f7cbf/

Log:	Added consistency check in case rrc graph changed between gc-
	iterations

diff --git a/rpython/memory/gc/rrc/base.py b/rpython/memory/gc/rrc/base.py
--- a/rpython/memory/gc/rrc/base.py
+++ b/rpython/memory/gc/rrc/base.py
@@ -41,6 +41,7 @@
     PYOBJ_SNAPSHOT_OBJ = lltype.Struct('PyObject_Snapshot',
                                        ('pyobj', llmemory.Address),
                                        ('status', lltype.Signed),
+                                       ('refcnt', lltype.Signed),
                                        ('refcnt_external', lltype.Signed),
                                        ('refs_index', lltype.Signed),
                                        ('refs_len', lltype.Signed),
@@ -629,6 +630,11 @@
         gchdr.c_gc_next = next
         next.c_gc_prev = gchdr
 
+    def _gc_list_remove(self, gchdr):
+        next = gchdr.c_gc_next
+        next.c_gc_prev = gchdr.c_gc_prev
+        gchdr.c_gc_prev.c_gc_next = next
+
     def _gc_list_pop(self, pygclist):
         ret = pygclist.c_gc_next
         pygclist.c_gc_next = ret.c_gc_next
diff --git a/rpython/memory/gc/rrc/incmark.py b/rpython/memory/gc/rrc/incmark.py
--- a/rpython/memory/gc/rrc/incmark.py
+++ b/rpython/memory/gc/rrc/incmark.py
@@ -63,26 +63,59 @@
         ll_assert(self.state == self.STATE_GARBAGE_MARKING, "invalid state")
 
         # sync snapshot with pyob_list:
-        #  * move all dead objs still in pyob_list to pyobj_old_list
+        #  * check the consistency of "dead" objects and keep all of them
+        #    alive, in case an inconsistency is found (the graph changed
+        #    between two pauses, so some of those objects might be alive)
+        #  * move all dead objects still in pyob_list to pyobj_old_list
         #  * for all other objects (in snapshot and new),
-        #    set their cyclic refcount to > 0, to mark them as live
+        #    set their cyclic refcount to > 0 to mark them as live
         pygchdr = self.pyobj_list.c_gc_next
+        consistent = True
+        self.snapshot_consistent = True
         while pygchdr <> self.pyobj_list:
             next_old = pygchdr.c_gc_next
-            if pygchdr.c_gc_refs > 0:
+            if pygchdr.c_gc_refs > 0: # object is in snapshot
                 snapobj = self.snapshot_objs[pygchdr.c_gc_refs - 1]
                 pygchdr.c_gc_refs = snapobj.refcnt_external
-                if snapobj.refcnt_external == 0:
-                    # remove from old list
-                    next = pygchdr.c_gc_next
-                    next.c_gc_prev = pygchdr.c_gc_prev
-                    pygchdr.c_gc_prev.c_gc_next = next
-                    # add to new list (or not, if it is a tuple)
+                if snapobj.refcnt_external == 0: # object considered dead
+                    # check consistency (dead subgraphs can never change):
+                    pyobj = self.gc_as_pyobj(pygchdr)
+                    # refcount equal
+                    consistent = snapobj.refcnt == pyobj.c_ob_refcnt
+                    if not consistent:
+                        break
+                    # outgoing (internal) references equal
+                    self.snapshot_curr = snapobj
+                    self.snapshot_curr_index = 0
+                    self._check_snapshot_traverse(pyobj)
+                    consistent = self.snapshot_consistent
+                    if not consistent:
+                        break
+                    # consistent -> prepare object for collection
+                    self._gc_list_remove(pygchdr)
                     self._gc_list_add(self.pyobj_old_list, pygchdr)
             else:
                 pygchdr.c_gc_refs = 1 # new object, keep alive
             pygchdr = next_old
 
+        self._debug_check_consistency(print_label="end-check-consistency")
+
+        if not consistent:  # keep all objects alive
+            while pygchdr <> self.pyobj_list: # continue previous loop
+                pygchdr.c_gc_refs = 1
+                pygchdr = pygchdr.c_gc_next
+            pygchdr = self.pyobj_old_list.c_gc_next
+            while pygchdr <> self.pyobj_old_list: # resurrect "dead" objects
+                pygchdr.c_gc_refs = 1
+                pygchdr = pygchdr.c_gc_next
+            if not self._gc_list_is_empty(self.pyobj_old_list):
+                self._gc_list_merge(self.pyobj_old_list, self.pyobj_list)
+
+        self._debug_check_consistency(print_label="before-snap-discard")
+
+        # now the snapshot is not needed any more, discard it
+        self._discard_snapshot()
+
         # handle legacy finalizers (assumption: not a lot of legacy finalizers,
         # so no need to do it incrementally)
         if self._find_garbage(False):
@@ -93,8 +126,7 @@
         # handle modern finalizers
         found_finalizer = self._find_finalizer()
         if found_finalizer:
-            self._gc_list_move(self.pyobj_old_list,
-                               self.pyobj_isolate_list)
+            self._gc_list_move(self.pyobj_old_list, self.pyobj_isolate_list)
         use_cylicrc = not found_finalizer
 
         # now mark all pypy objects at the border, depending on the results
@@ -102,7 +134,6 @@
         debug_print("use_cylicrc", use_cylicrc)
         self.p_list_old.foreach(self._major_trace, (use_cylicrc, False))
         self._debug_check_consistency(print_label="end-mark")
-        self._discard_snapshot()
         return True
 
     def _collect_roots(self, pygclist):
@@ -134,7 +165,7 @@
                 obj = self.snapshot_objs[i]
                 found_alive |= self._mark_rawrefcount_obj(obj)
             simple_limit += 1
-            if simple_limit > 3:
+            if simple_limit > 3: # TODO: implement sane limit
                 reached_limit
         return not reached_limit # are there any objects left?
 
@@ -210,6 +241,7 @@
             obj = self.snapshot_objs[objs_index]
             obj.pyobj = llmemory.cast_ptr_to_adr(pyobj)
             obj.status = 1
+            obj.refcnt = pyobj.c_ob_refcnt
             obj.refcnt_external = refcnt
             obj.refs_index = refs_index
             obj.refs_len = 0
@@ -255,6 +287,48 @@
         else:
             self.tp_traverse(pyobj, self._take_snapshot_visit_action, None)
 
+    def _check_snapshot_visit(pyobj, self_ptr):
+        from rpython.rtyper.annlowlevel import cast_adr_to_nongc_instance
+        #
+        self_adr = rffi.cast(llmemory.Address, self_ptr)
+        self = cast_adr_to_nongc_instance(RawRefCountIncMarkGC, self_adr)
+        self._check_snapshot_visit_action(pyobj, None)
+        return rffi.cast(rffi.INT_real, 0)
+
+    def _check_snapshot_visit_action(self, pyobj, ignore):
+        pygchdr = self.pyobj_as_gc(pyobj)
+        if pygchdr <> lltype.nullptr(self.PYOBJ_GC_HDR) and \
+                pygchdr.c_gc_refs != self.RAWREFCOUNT_REFS_UNTRACKED:
+            # check consistency with snapshot
+            curr = self.snapshot_curr
+            curr_index = self.snapshot_curr_index
+            if curr_index < curr.refs_len:
+                # ref changed? -> issue, if traversal order is not stable!!!
+                index = curr.refs_index + curr_index
+                ref_addr = self.snapshot_refs[index]
+                ref = llmemory.cast_adr_to_ptr(ref_addr,
+                                               self.PYOBJ_SNAPSHOT_OBJ_PTR)
+                old_value = ref.pyobj
+                new_value = llmemory.cast_ptr_to_adr(pyobj)
+                if old_value != new_value:
+                    self.snapshot_consistent = False # reference changed
+            else:
+                self.snapshot_consistent = False # references added
+            self.snapshot_curr_index += 1
+
+    def _check_snapshot_traverse(self, pyobj):
+        from rpython.rlib.objectmodel import we_are_translated
+        from rpython.rtyper.annlowlevel import (cast_nongc_instance_to_adr,
+                                                llhelper)
+        #
+        if we_are_translated():
+            callback_ptr = llhelper(self.RAWREFCOUNT_VISIT,
+                                    RawRefCountIncMarkGC._check_snapshot_visit)
+            self_ptr = rffi.cast(rffi.VOIDP, cast_nongc_instance_to_adr(self))
+            self.tp_traverse(pyobj, callback_ptr, self_ptr)
+        else:
+            self.tp_traverse(pyobj, self._check_snapshot_visit_action, None)
+
     def _discard_snapshot(self):
         lltype.free(self.snapshot_objs, flavor='raw', track_allocation=False)
         lltype.free(self.snapshot_refs, flavor='raw', track_allocation=False)
\ No newline at end of file


More information about the pypy-commit mailing list