Skip to content

Commit f72f2c9

Browse files
committed
gh-159437: Combine GC resurrection bookkeeping
1 parent d9a38bb commit f72f2c9

2 files changed

Lines changed: 27 additions & 34 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
With the GIL enabled, avoid separate passes that clear collecting bits before resurrection analysis and unreachable tags afterward. The existing reference and late weak-reference passes now perform that bookkeeping.

‎Python/gc.c‎

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
11011095
static Py_ssize_t
11021096
clear_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
13111306
Contracts:
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.
13291326
IMPORTANT: This function leaves 'unreachable' with the NEXT_MASK_UNREACHABLE
13301327
flag set but it does not clear it to skip unnecessary iteration. Before the
13311328
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. */
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. */
13341331
static inline Py_ssize_t
13351332
deduce_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. */
14121408
static inline void
14131409
handle_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
*/
14491440
Py_NO_INLINE static bool
14501441
handle_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

Comments
 (0)