@@ -44,7 +44,8 @@ typedef struct _gc_runtime_state GCState;
4444//
4545// Although this flag is used only in move_unreachable(), move_unreachable()
4646// doesn't clear this flag to skip unnecessary iteration.
47- // move_legacy_finalizers() removes this flag instead.
47+ // move_legacy_finalizers(), clear_unreachable_mask(), or prepare_callbacks()
48+ // remove this flag instead.
4849// Between them, unreachable list is not normal list and we can not use
4950// most gc_list_* functions for it.
5051#define NEXT_MASK_UNREACHABLE (1)
@@ -200,7 +201,8 @@ NEXT_MASK_UNREACHABLE
200201 "unreachable" set.
201202 The flag is unset and the object is moved back to "reachable" set.
202203
203- move_legacy_finalizers() will remove this flag from "unreachable" set.
204+ move_legacy_finalizers(), clear_unreachable_mask(), or prepare_callbacks()
205+ remove this flag from the "unreachable" set.
204206*/
205207
206208/*** list functions ***/
@@ -522,19 +524,26 @@ _PyGC_VisitFrameStack(_PyInterpreterFrame *frame, visitproc visit, void *arg)
522524/* Subtract internal references from gc_refs. After this, gc_refs is >= 0
523525 * for all objects in containers. The ones with gc_refs > 0 are directly
524526 * reachable from outside containers, and so can't be collected.
527+ *
528+ * Return the number of objects whose type defines tp_del. A zero count
529+ * excludes legacy finalizers from the eventual unreachable subset.
525530 */
526- static void
531+ static Py_ssize_t
527532subtract_refs (PyGC_Head * containers )
528533{
534+ Py_ssize_t legacy_finalizer_count = 0 ;
529535 traverseproc traverse ;
530536 PyGC_Head * gc = GC_NEXT (containers );
531537 for (; gc != containers ; gc = GC_NEXT (gc )) {
532538 PyObject * op = FROM_GC (gc );
533- traverse = Py_TYPE (op )-> tp_traverse ;
539+ PyTypeObject * type = Py_TYPE (op );
540+ legacy_finalizer_count += type -> tp_del != NULL ;
541+ traverse = type -> tp_traverse ;
534542 (void ) traverse (op ,
535543 visit_decref ,
536544 op );
537545 }
546+ return legacy_finalizer_count ;
538547}
539548
540549/* Initialize and subtract references in one traversal of all ordinary generations.
@@ -550,13 +559,15 @@ subtract_refs(PyGC_Head *containers)
550559 *
551560 * Arguments:
552561 * - `containers`: tracked objects belonging to the initial main interpreter.
562+ * - `legacy_finalizer_count_out`: receives the number of mortal candidates whose type defines tp_del.
553563 *
554564 * Return the number of mortal collection candidates.
555565 */
556566static Py_ssize_t
557- update_and_subtract_refs (PyGC_Head * containers )
567+ update_and_subtract_refs (PyGC_Head * containers , Py_ssize_t * legacy_finalizer_count_out )
558568{
559569 Py_ssize_t candidates = 0 ;
570+ Py_ssize_t legacy_finalizer_count = 0 ;
560571 PyGC_Head * previous = containers ;
561572 PyGC_Head * gc = GC_NEXT (containers );
562573
@@ -581,13 +592,16 @@ update_and_subtract_refs(PyGC_Head *containers)
581592 }
582593
583594 candidates ++ ;
584- (void )Py_TYPE (op )-> tp_traverse (op , gc_decref_and_mark_collecting , op );
595+ PyTypeObject * type = Py_TYPE (op );
596+ legacy_finalizer_count += type -> tp_del != NULL ;
597+ (void )type -> tp_traverse (op , gc_decref_and_mark_collecting , op );
585598 previous = gc ;
586599 gc = next ;
587600 }
588601
589602 // The tail must remain valid until reachability restores predecessor links.
590603 containers -> _gc_prev = (uintptr_t )previous ;
604+ * legacy_finalizer_count_out = legacy_finalizer_count ;
591605 return candidates ;
592606}
593607
@@ -886,19 +900,22 @@ move_legacy_finalizer_reachable(PyGC_Head *finalizers)
886900 * Python-level code. See bpo-38006 as an example bug.
887901 *
888902 * Arguments:
889- * - `unreachable`: normalized collecting list to inspect .
903+ * - `unreachable`: collecting list with an untagged head; member _gc_next links must all be normalized or all carry NEXT_MASK_UNREACHABLE .
890904 * - `wrcb_to_call`: initialized list receiving weakrefs whose callbacks are safe to invoke.
891905 * - `collectable_count_out`: receives the number of objects in `unreachable`.
892906 * - `needs_weakref_cleanup_out`: receives whether garbage weakrefs or remaining incoming weakrefs may need cleanup.
893907 *
894908 * Return whether any finalizer is pending or a weakref callback is queued.
895- * This function does not invoke callbacks. The count and cleanup decision
896- * remain valid only while the unreachable set and its weakrefs cannot change.
909+ * On return, `unreachable` is normalized. This function does not invoke callbacks.
910+ * The count and cleanup decision remain valid only while the unreachable set and its weakrefs cannot change.
897911 */
898912Py_NO_INLINE static bool
899913prepare_callbacks (PyGC_Head * unreachable , PyGC_Head * wrcb_to_call ,
900914 Py_ssize_t * collectable_count_out , bool * needs_weakref_cleanup_out )
901915{
916+ assert ((unreachable -> _gc_next & NEXT_MASK_UNREACHABLE ) == 0 );
917+ validate_list (unreachable , (GC_NEXT (unreachable )-> _gc_next & NEXT_MASK_UNREACHABLE ) ? collecting_set_unreachable_set : collecting_set_unreachable_clear );
918+
902919 PyGC_Head * gc ;
903920 PyGC_Head * next ;
904921 Py_ssize_t count = 0 ;
@@ -918,6 +935,7 @@ prepare_callbacks(PyGC_Head *unreachable, PyGC_Head *wrcb_to_call,
918935 if (type -> tp_finalize != NULL && !has_pending_finalizer ) {
919936 has_pending_finalizer = !_PyGC_FINALIZED (op );
920937 }
938+ gc -> _gc_next &= ~NEXT_MASK_UNREACHABLE ;
921939 next = GC_NEXT (gc );
922940
923941 // Garbage weakrefs need clearing even if their referents are outside
@@ -1005,6 +1023,7 @@ prepare_callbacks(PyGC_Head *unreachable, PyGC_Head *wrcb_to_call,
10051023 needs_weakref_cleanup |= * wrlist != NULL ;
10061024 }
10071025
1026+ validate_list (unreachable , collecting_set_unreachable_clear );
10081027 * collectable_count_out = count ;
10091028 * needs_weakref_cleanup_out = needs_weakref_cleanup ;
10101029 return has_pending_finalizer || !gc_list_is_empty (wrcb_to_call );
@@ -1301,16 +1320,20 @@ The first two steps may be combined for the initial pass of a full collection.
13011320
13021321 * "reachable" must point to storage for the number of survivors that remain tracked.
13031322
1323+ * "legacy_finalizer_count_out" receives the number of candidate objects
1324+ whose type defines tp_del. Zero excludes legacy finalizers from the
1325+ unreachable subset.
1326+
13041327 * The return value is the number of mortal collection candidates.
13051328
13061329IMPORTANT: This function leaves 'unreachable' with the NEXT_MASK_UNREACHABLE
13071330flag set but it does not clear it to skip unnecessary iteration. Before the
1308- flag is cleared (for example, by using 'clear_unreachable_mask' function or
1309- by a call to 'move_legacy_finalizers '), the 'unreachable' list is not a normal
1310- list and we can not use most gc_list_* functions for it. */
1331+ flag is cleared (for example, by 'clear_unreachable_mask',
1332+ 'move_legacy_finalizers', or 'prepare_callbacks '), the 'unreachable' list is
1333+ not a normal list and we can not use most gc_list_* functions for it. */
13111334static inline Py_ssize_t
13121335deduce_unreachable (PyGC_Head * base , PyGC_Head * unreachable ,
1313- bool full_collection_pass , Py_ssize_t * reachable ) {
1336+ bool full_collection_pass , Py_ssize_t * reachable , Py_ssize_t * legacy_finalizer_count_out ) {
13141337 validate_list (base , collecting_clear_unreachable_clear );
13151338 /* Using ob_refcnt and gc_refs, calculate which objects in the
13161339 * container set are reachable from outside the set (i.e., have a
@@ -1323,11 +1346,11 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable,
13231346 if (full_collection_pass &&
13241347 gc_list_is_empty (& get_gc_state ()-> permanent_generation .head ) &&
13251348 !_PyInterpreterState_MayHaveForeignGcObjects ()) {
1326- candidates = update_and_subtract_refs (base );
1349+ candidates = update_and_subtract_refs (base , legacy_finalizer_count_out );
13271350 }
13281351 else {
13291352 candidates = update_refs (base ); // gc_prev is used for gc_refs
1330- subtract_refs (base );
1353+ * legacy_finalizer_count_out = subtract_refs (base );
13311354 }
13321355
13331356 /* Leave everything reachable from outside base in base, and move
@@ -1399,7 +1422,8 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
13991422 // removed so we can skip the expense of clearing the flag.
14001423 PyGC_Head * resurrected = unreachable ;
14011424 Py_ssize_t reachable ;
1402- deduce_unreachable (resurrected , still_unreachable , false, & reachable );
1425+ Py_ssize_t unused_legacy_finalizer_count ;
1426+ deduce_unreachable (resurrected , still_unreachable , false, & reachable , & unused_legacy_finalizer_count );
14031427 clear_unreachable_mask (still_unreachable );
14041428
14051429 // Move the resurrected objects to the old generation for future collection.
@@ -1706,7 +1730,8 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
17061730 validate_list (old , collecting_clear_unreachable_clear );
17071731
17081732 Py_ssize_t reachable ;
1709- stats .candidates = deduce_unreachable (young , & unreachable , generation == NUM_GENERATIONS - 1 , & reachable );
1733+ Py_ssize_t legacy_finalizer_count ;
1734+ stats .candidates = deduce_unreachable (young , & unreachable , generation == NUM_GENERATIONS - 1 , & reachable , & legacy_finalizer_count );
17101735
17111736 /* Move reachable objects to next generation. */
17121737 if (young != old ) {
@@ -1731,22 +1756,28 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
17311756 * legacy finalizers (e.g. tp_del) can't safely be deleted.
17321757 */
17331758 gc_list_init (& finalizers );
1734- // NEXT_MASK_UNREACHABLE is cleared here.
1735- // After move_legacy_finalizers(), unreachable is normal list.
1736- move_legacy_finalizers (& unreachable , & finalizers );
1737- /* finalizers contains the unreachable objects with a legacy finalizer;
1738- * unreachable objects reachable *from* those are also uncollectable,
1739- * and we move those into the finalizers list too.
1740- */
1741- move_legacy_finalizer_reachable (& finalizers );
1759+ if (legacy_finalizer_count != 0 ) {
1760+ // NEXT_MASK_UNREACHABLE is cleared here.
1761+ // After move_legacy_finalizers(), unreachable is a normal list.
1762+ move_legacy_finalizers (& unreachable , & finalizers );
1763+ /* finalizers contains the unreachable objects with a legacy finalizer;
1764+ * unreachable objects reachable *from* those are also uncollectable,
1765+ * and we move those into the finalizers list too.
1766+ */
1767+ move_legacy_finalizer_reachable (& finalizers );
1768+ validate_list (& unreachable , collecting_set_unreachable_clear );
1769+ }
17421770
17431771 validate_list (& finalizers , collecting_clear_unreachable_clear );
1744- validate_list (& unreachable , collecting_set_unreachable_clear );
17451772
17461773 bool needs_resurrection_check = false;
17471774
17481775 /* Print debugging information. */
17491776 if (gcstate -> debug & _PyGC_DEBUG_COLLECTABLE ) {
1777+ if (legacy_finalizer_count == 0 ) {
1778+ // Python invoked by debug output requires normal GC list links.
1779+ clear_unreachable_mask (& unreachable );
1780+ }
17501781 /* sys.stderr.write() can resurrect objects. */
17511782 needs_resurrection_check = true;
17521783 for (gc = GC_NEXT (& unreachable ); gc != & unreachable ; gc = GC_NEXT (gc )) {
0 commit comments