1414#include "pycore_object_alloc.h" // _PyObject_MallocWithType()
1515#include "pycore_pyerrors.h"
1616#include "pycore_pystate.h" // _PyThreadState_GET()
17- #include "pycore_tuple.h" // _PyTuple_MaybeUntrack ()
17+ #include "pycore_tuple.h" // _PyTuple_CanUntrack ()
1818#include "pycore_weakref.h" // _PyWeakref_ClearRef()
1919#include "pydtrace.h"
2020
@@ -595,18 +595,26 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
595595
596596 while (gc != young ) {
597597 if (gc_get_refs (gc )) {
598- /* gc is definitely reachable from outside the
599- * original 'young'. Mark it as such, and traverse
600- * its pointers to find any other objects that may
601- * be directly reachable from it. Note that the
602- * call to tp_traverse may append objects to young,
603- * so we have to wait until it returns to determine
604- * the next object to visit.
605- */
598+ // gc is definitely reachable from outside the original 'young'.
599+ // Unless it can be untracked, traverse its pointers to find other
600+ // reachable objects. tp_traverse may append objects to young,
601+ // so wait until it returns to determine the next object to visit.
606602 PyObject * op = FROM_GC (gc );
607- traverseproc traverse = Py_TYPE (op )-> tp_traverse ;
608603 _PyObject_ASSERT_WITH_MSG (op , gc_get_refs (gc ) > 0 ,
609604 "refcount is too small" );
605+ // An untrackable tuple cannot lead to another GC candidate.
606+ if (PyTuple_CheckExact (op ) && _PyTuple_CanUntrack (op )) {
607+ // Ordinary untracking would overwrite the next node's gc_refs.
608+ PyGC_Head * next = GC_NEXT (gc );
609+ _PyGCHead_SET_NEXT (prev , next );
610+ gc -> _gc_prev &= _PyGC_PREV_MASK_FINALIZED ;
611+ gc -> _gc_next = 0 ;
612+ _PyInterpreterState_GET ()-> gc .heap_size -- ;
613+ gc = next ;
614+ continue ;
615+ }
616+
617+ traverseproc traverse = Py_TYPE (op )-> tp_traverse ;
610618 // NOTE: visit_reachable may change gc->_gc_next when
611619 // young->_gc_prev == gc. Don't do gc = GC_NEXT(gc) before!
612620 (void ) traverse (op ,
@@ -616,21 +624,8 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
616624 _PyGCHead_SET_PREV (gc , prev );
617625 // gc is not COLLECTING state after here.
618626 gc_clear_collecting (gc );
619- if (PyTuple_CheckExact (op )) {
620- // Untracking gc overwrites the gc_refs of the next object.
621- PyGC_Head * next = GC_NEXT (gc );
622- uintptr_t next_refs = next -> _gc_prev ;
623- _PyTuple_MaybeUntrack (op );
624- next -> _gc_prev = next_refs ;
625- if (gc -> _gc_next != 0 ) {
626- prev = gc ;
627- reachable ++ ;
628- }
629- }
630- else {
631- prev = gc ;
632- reachable ++ ;
633- }
627+ prev = gc ;
628+ reachable ++ ;
634629 }
635630 else {
636631 /* This *may* be unreachable. To make progress,
0 commit comments