@@ -775,10 +775,13 @@ has_legacy_finalizer(PyObject *op)
775
775
*
776
776
* This function also removes NEXT_MASK_UNREACHABLE flag
777
777
* from _gc_next in unreachable.
778
+ *
779
+ * Returns the number of items remaining in the `unreachable` list.
778
780
*/
779
- static void
781
+ static Py_ssize_t
780
782
move_legacy_finalizers (PyGC_Head * unreachable , PyGC_Head * finalizers )
781
783
{
784
+ Py_ssize_t count = 0 ;
782
785
PyGC_Head * gc , * next ;
783
786
_PyObject_ASSERT (
784
787
FROM_GC (unreachable ),
@@ -797,8 +800,11 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
797
800
if (has_legacy_finalizer (op )) {
798
801
gc_clear_collecting (gc );
799
802
gc_list_move (gc , finalizers );
803
+ } else {
804
+ ++ count ;
800
805
}
801
806
}
807
+ return count ;
802
808
}
803
809
804
810
static inline void
@@ -1072,7 +1078,7 @@ handle_legacy_finalizers(PyThreadState *tstate,
1072
1078
1073
1079
/* Run first-time finalizers (if any) on all the objects in collectable.
1074
1080
* Note that this may remove some (or even all) of the objects from the
1075
- * list, due to refcounts falling to 0.
1081
+ * list, due to refcounts falling to 0. Return the number of finalizers run.
1076
1082
*/
1077
1083
static int
1078
1084
finalize_garbage (PyThreadState * tstate , PyGC_Head * collectable )
@@ -1717,7 +1723,8 @@ gc_collect_region(PyThreadState *tstate,
1717
1723
gc_list_init (& finalizers );
1718
1724
// NEXT_MASK_UNREACHABLE is cleared here.
1719
1725
// After move_legacy_finalizers(), unreachable is normal list.
1720
- move_legacy_finalizers (& unreachable , & finalizers );
1726
+ Py_ssize_t unreachable_count = move_legacy_finalizers (& unreachable , & finalizers );
1727
+
1721
1728
/* finalizers contains the unreachable objects with a legacy finalizer;
1722
1729
* unreachable objects reachable *from* those are also uncollectable,
1723
1730
* and we move those into the finalizers list too.
@@ -1734,6 +1741,7 @@ gc_collect_region(PyThreadState *tstate,
1734
1741
1735
1742
/* Clear weakrefs and invoke callbacks as necessary. */
1736
1743
stats -> collected += handle_weakrefs (& unreachable , to );
1744
+
1737
1745
gc_list_validate_space (to , gcstate -> visited_space );
1738
1746
validate_list (to , collecting_clear_unreachable_clear );
1739
1747
validate_list (& unreachable , collecting_set_unreachable_clear );
@@ -1750,15 +1758,17 @@ gc_collect_region(PyThreadState *tstate,
1750
1758
gc_list_init (& final_unreachable );
1751
1759
if (check_resurrected ) {
1752
1760
handle_resurrected_objects (& unreachable , & final_unreachable , to );
1761
+ unreachable_count = gc_list_size (& final_unreachable );
1753
1762
} else {
1754
1763
gc_list_merge (& unreachable , & final_unreachable );
1764
+ assert (unreachable_count == gc_list_size (& final_unreachable ));
1755
1765
}
1756
1766
1757
1767
/* Call tp_clear on objects in the final_unreachable set. This will cause
1758
1768
* the reference cycles to be broken. It may also cause some objects
1759
1769
* in finalizers to be freed.
1760
1770
*/
1761
- stats -> collected += gc_list_size ( & final_unreachable ) ;
1771
+ stats -> collected += unreachable_count ;
1762
1772
delete_garbage (tstate , gcstate , & final_unreachable , to );
1763
1773
1764
1774
/* Collect statistics on uncollectable objects found and print
0 commit comments