@@ -45,7 +45,8 @@ typedef struct _gc_runtime_state GCState;
4545// Although this flag is used only in move_unreachable(), move_unreachable()
4646// doesn't clear this flag to skip unnecessary iteration.
4747// move_legacy_finalizers(), clear_unreachable_mask(), or prepare_callbacks()
48- // remove this flag instead.
48+ // remove this flag after initial deduction. clear_weakrefs() removes it after
49+ // a resurrection recheck.
4950// Between them, unreachable list is not normal list and we can not use
5051// most gc_list_* functions for it.
5152#define NEXT_MASK_UNREACHABLE (1)
@@ -202,7 +203,8 @@ NEXT_MASK_UNREACHABLE
202203 The flag is unset and the object is moved back to "reachable" set.
203204
204205 move_legacy_finalizers(), clear_unreachable_mask(), or prepare_callbacks()
205- remove this flag from the "unreachable" set.
206+ remove this flag after initial deduction. clear_weakrefs() removes it after
207+ a resurrection recheck.
206208*/
207209
208210/*** list functions ***/
@@ -304,16 +306,6 @@ gc_list_size(PyGC_Head *list)
304306 return n ;
305307}
306308
307- /* Walk the list and mark all objects as non-collecting */
308- static inline void
309- gc_list_clear_collecting (PyGC_Head * collectable )
310- {
311- PyGC_Head * gc ;
312- for (gc = GC_NEXT (collectable ); gc != collectable ; gc = GC_NEXT (gc )) {
313- gc_clear_collecting (gc );
314- }
315- }
316-
317309/* Append objects in a GC list to a Python list.
318310 * Return 0 if all OK, < 0 if error (out of memory for list)
319311 */
@@ -1092,11 +1084,13 @@ invoke_weakref_callbacks(PyGC_Head *wrcb_to_call, PyGC_Head *old)
10921084}
10931085
10941086/* Clear all weakrefs to unreachable objects without invoking callbacks.
1095- * When this returns, no object in `unreachable` is weakly referenced anymore.
10961087 * See the comments above prepare_callbacks() for why these weakrefs need to be cleared.
10971088 *
1098- * `unreachable`: Final collectable list; membership is unchanged.
1099- * Return: Number of objects in `unreachable`.
1089+ * Arguments:
1090+ * - `unreachable`: final collectable list with an untagged head; member _gc_next links must all be normalized or all carry NEXT_MASK_UNREACHABLE.
1091+ *
1092+ * Return the number of objects in `unreachable`. On return, the list is
1093+ * normalized, membership is unchanged, and no member is weakly referenced.
11001094 */
11011095static Py_ssize_t
11021096clear_weakrefs (PyGC_Head * unreachable )
@@ -1109,6 +1103,7 @@ clear_weakrefs(PyGC_Head *unreachable)
11091103 PyWeakReference * * wrlist ;
11101104
11111105 PyObject * op = FROM_GC (gc );
1106+ gc -> _gc_next &= ~NEXT_MASK_UNREACHABLE ;
11121107 next = GC_NEXT (gc );
11131108 count ++ ;
11141109
@@ -1310,7 +1305,9 @@ The first two steps may be combined for the initial pass of a full collection.
13101305
13111306Contracts:
13121307
1313- * The "base" has to be a valid list with no mask set.
1308+ * The "base" has to be a valid list. Its members may carry
1309+ PREV_MASK_COLLECTING during a resurrection recheck.
1310+ NEXT_MASK_UNREACHABLE must be clear.
13141311
13151312 * The "unreachable" list must be uninitialized (this function calls
13161313 gc_list_init over 'unreachable').
@@ -1329,12 +1326,11 @@ The first two steps may be combined for the initial pass of a full collection.
13291326IMPORTANT: This function leaves 'unreachable' with the NEXT_MASK_UNREACHABLE
13301327flag set but it does not clear it to skip unnecessary iteration. Before the
13311328flag 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. */
1329+ 'move_legacy_finalizers', 'prepare_callbacks', or 'clear_weakrefs'), the
1330+ 'unreachable' list is not a normal list and we can not use most gc_list_* functions for it. */
13341331static inline Py_ssize_t
13351332deduce_unreachable (PyGC_Head * base , PyGC_Head * unreachable ,
13361333 bool full_collection_pass , Py_ssize_t * reachable , Py_ssize_t * legacy_finalizer_count_out ) {
1337- validate_list (base , collecting_clear_unreachable_clear );
13381334 /* Using ob_refcnt and gc_refs, calculate which objects in the
13391335 * container set are reachable from outside the set (i.e., have a
13401336 * refcount greater than 0 when all the references within the
@@ -1406,25 +1402,18 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable,
14061402 * The "still_unreachable" list must be uninitialized (this function calls
14071403 gc_list_init over 'still_unreachable').
14081404
1409- IMPORTANT: After a call to this function, the 'still_unreachable' set will have the
1410- PREV_MARK_COLLECTING set, but the objects in this set are going to be removed so
1411- we can skip the expense of clearing the flag to avoid extra iteration . */
1405+ IMPORTANT: The 'still_unreachable' head is untagged. Its members carry
1406+ PREV_MASK_COLLECTING and NEXT_MASK_UNREACHABLE. 'clear_weakrefs' must normalize
1407+ the list before ordinary list operations or deletion . */
14121408static inline void
14131409handle_resurrected_objects (PyGC_Head * unreachable , PyGC_Head * still_unreachable ,
14141410 PyGC_Head * old_generation )
14151411{
1416- // Remove the PREV_MASK_COLLECTING from unreachable
1417- // to prepare it for a new call to 'deduce_unreachable'
1418- gc_list_clear_collecting (unreachable );
1419-
1420- // After the call to deduce_unreachable, the 'still_unreachable' set will
1421- // have the PREV_MARK_COLLECTING set, but the objects are going to be
1422- // removed so we can skip the expense of clearing the flag.
14231412 PyGC_Head * resurrected = unreachable ;
1413+ validate_list (resurrected , collecting_set_unreachable_clear );
14241414 Py_ssize_t reachable ;
14251415 Py_ssize_t unused_legacy_finalizer_count ;
14261416 deduce_unreachable (resurrected , still_unreachable , false, & reachable , & unused_legacy_finalizer_count );
1427- clear_unreachable_mask (still_unreachable );
14281417
14291418 // Move the resurrected objects to the old generation for future collection.
14301419 gc_list_merge (resurrected , old_generation );
@@ -1443,8 +1432,10 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
14431432 * - `needs_resurrection_check`: whether earlier work may have run Python.
14441433 *
14451434 * Return whether resurrection analysis ran. If true, `collectable` must not be
1446- * used again and `still_unreachable` contains the remaining garbage. Otherwise,
1447- * `collectable` remains selected and `still_unreachable` is untouched.
1435+ * used again and `still_unreachable` contains the remaining garbage with
1436+ * NEXT_MASK_UNREACHABLE set on every member and an untagged head. It must pass
1437+ * through `clear_weakrefs()` before deletion. Otherwise, `collectable` remains
1438+ * selected and `still_unreachable` is untouched.
14481439 */
14491440Py_NO_INLINE static bool
14501441handle_callbacks (PyThreadState * tstate , PyGC_Head * collectable ,
@@ -1728,6 +1719,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
17281719 old = young ;
17291720 }
17301721 validate_list (old , collecting_clear_unreachable_clear );
1722+ validate_list (young , collecting_clear_unreachable_clear );
17311723
17321724 Py_ssize_t reachable ;
17331725 Py_ssize_t legacy_finalizer_count ;
@@ -1799,12 +1791,11 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
17991791 if (resurrection_checked ) {
18001792 collectable = & final_unreachable ;
18011793 // Python code may have changed the garbage set or created weakrefs.
1802- // Refresh both cleanup and counting.
1794+ // Refresh cleanup and counting, and normalize the recheck's tags .
18031795 needs_weakref_cleanup = true;
18041796 }
18051797 }
18061798 validate_list (old , collecting_clear_unreachable_clear );
1807- validate_list (collectable , collecting_set_unreachable_clear );
18081799
18091800 /* Clear weakrefs to objects in the unreachable set. No Python-level
18101801 * code must be allowed to access those unreachable objects. During
@@ -1815,6 +1806,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
18151806 if (needs_weakref_cleanup ) {
18161807 collectable_count = clear_weakrefs (collectable );
18171808 }
1809+ validate_list (collectable , collecting_set_unreachable_clear );
18181810 stats .collected += collectable_count ;
18191811
18201812 /* Call tp_clear on objects in the collectable set. This will cause
0 commit comments