Skip to content

Commit 7480de0

Browse files
tim-onepablogsal
authored andcommitted
Misc gc code & comment cleanups. (pythonGH-16752)
* Misc gc code & comment cleanups. validate_list: there are two temp flags polluting pointers, but this checked only one. Now it checks both, and verifies that the list head's pointers are not polluted. move_unreachable: repaired incoherent comments. Added new comments. Cleared the pollution of the unreachable list head's 'next' pointer (it was expedient while the function was running, but there's no excuse for letting this damage survive the function's end). * Update Modules/gcmodule.c Co-Authored-By: Pablo Galindo <[email protected]>
1 parent 4e259e4 commit 7480de0

File tree

1 file changed

+44
-22
lines changed

1 file changed

+44
-22
lines changed

Modules/gcmodule.c

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -331,23 +331,37 @@ append_objects(PyObject *py_list, PyGC_Head *gc_list)
331331

332332
#ifdef GC_DEBUG
333333
// validate_list checks list consistency. And it works as document
334-
// describing when expected_mask is set / unset.
334+
// describing when flags are expected to be set / unset.
335+
// `head` must be a doubly-linked gc list, although it's fine (expected!) if
336+
// the prev and next pointers are "polluted" with flags.
337+
// What's checked:
338+
// - The `head` pointers are not polluted.
339+
// - The objects' prev pointers' PREV_MASK_COLLECTING flags are all
340+
// `prev_value` (PREV_MASK_COLLECTING for set, 0 for clear).
341+
// - The objects' next pointers' NEXT_MASK_UNREACHABLE flags are all
342+
// `next_value` (NEXT_MASK_UNREACHABLE for set, 0 for clear).
343+
// - The prev and next pointers are mutually consistent.
335344
static void
336-
validate_list(PyGC_Head *head, uintptr_t expected_mask)
345+
validate_list(PyGC_Head *head, uintptr_t prev_value, uintptr_t next_value)
337346
{
347+
assert((head->_gc_prev & PREV_MASK_COLLECTING) == 0);
348+
assert((head->_gc_next & NEXT_MASK_UNREACHABLE) == 0);
338349
PyGC_Head *prev = head;
339350
PyGC_Head *gc = GC_NEXT(head);
340351
while (gc != head) {
341-
assert(GC_NEXT(gc) != NULL);
342-
assert(GC_PREV(gc) == prev);
343-
assert((gc->_gc_prev & PREV_MASK_COLLECTING) == expected_mask);
352+
PyGC_Head *trueprev = GC_PREV(gc);
353+
PyGC_Head *truenext = (PyGC_Head *)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE);
354+
assert(truenext != NULL);
355+
assert(trueprev == prev);
356+
assert((gc->_gc_prev & PREV_MASK_COLLECTING) == prev_value);
357+
assert((gc->_gc_next & NEXT_MASK_UNREACHABLE) == next_value);
344358
prev = gc;
345-
gc = GC_NEXT(gc);
359+
gc = truenext;
346360
}
347361
assert(prev == GC_PREV(head));
348362
}
349363
#else
350-
#define validate_list(x,y) do{}while(0)
364+
#define validate_list(x, y, z) do{}while(0)
351365
#endif
352366

353367
/*** end of list stuff ***/
@@ -434,6 +448,9 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
434448
const Py_ssize_t gc_refs = gc_get_refs(gc);
435449

436450
// Ignore untracked objects and objects in other generation.
451+
// This also skips objects "to the left" of the current position in
452+
// move_unreachable's scan of the 'young' list - they've already been
453+
// traversed, and no longer have the PREV_MASK_COLLECTING flag.
437454
if (gc->_gc_next == 0 || !gc_is_collecting(gc)) {
438455
return 0;
439456
}
@@ -445,7 +462,8 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
445462
* and move_unreachable will eventually get to it
446463
* again.
447464
*/
448-
// Manually unlink gc from unreachable list because
465+
// Manually unlink gc from unreachable list because the list functions
466+
// don't work right in the presence of NEXT_MASK_UNREACHABLE flags.
449467
PyGC_Head *prev = GC_PREV(gc);
450468
PyGC_Head *next = (PyGC_Head*)(gc->_gc_next & ~NEXT_MASK_UNREACHABLE);
451469
_PyObject_ASSERT(FROM_GC(prev),
@@ -546,8 +564,9 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
546564
PyGC_Head *last = GC_PREV(unreachable);
547565
// NOTE: Since all objects in unreachable set has
548566
// NEXT_MASK_UNREACHABLE flag, we set it unconditionally.
549-
// But this may set the flat to unreachable too.
550-
// move_legacy_finalizers() should care about it.
567+
// But this may pollute the unreachable list head's 'next' pointer
568+
// too. That's semantically senseless but expedient here - the
569+
// damage is repaired when this fumction ends.
551570
last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc);
552571
_PyGCHead_SET_PREV(gc, last);
553572
gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable);
@@ -557,6 +576,8 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
557576
}
558577
// young->_gc_prev must be last element remained in the list.
559578
young->_gc_prev = (uintptr_t)prev;
579+
// don't let the pollution of the list head's next pointer leak
580+
unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
560581
}
561582

562583
static void
@@ -604,7 +625,7 @@ static void
604625
move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
605626
{
606627
PyGC_Head *gc, *next;
607-
unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
628+
assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0);
608629

609630
/* March over unreachable. Move objects with finalizers into
610631
* `finalizers`.
@@ -630,13 +651,13 @@ clear_unreachable_mask(PyGC_Head *unreachable)
630651
assert(((uintptr_t)unreachable & NEXT_MASK_UNREACHABLE) == 0);
631652

632653
PyGC_Head *gc, *next;
633-
unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
654+
assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0);
634655
for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
635656
_PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE);
636657
gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
637658
next = (PyGC_Head*)gc->_gc_next;
638659
}
639-
validate_list(unreachable, PREV_MASK_COLLECTING);
660+
validate_list(unreachable, PREV_MASK_COLLECTING, 0);
640661
}
641662

642663
/* A traversal callback for move_legacy_finalizer_reachable. */
@@ -1021,15 +1042,15 @@ show_stats_each_generations(struct _gc_runtime_state *state)
10211042
10221043
* The "unreachable" list must be uninitialized (this function calls
10231044
gc_list_init over 'unreachable').
1024-
1045+
10251046
IMPORTANT: This function leaves 'unreachable' with the NEXT_MASK_UNREACHABLE
10261047
flag set but it does not clear it to skip unnecessary iteration. Before the
10271048
flag is cleared (for example, by using 'clear_unreachable_mask' function or
10281049
by a call to 'move_legacy_finalizers'), the 'unreachable' list is not a normal
10291050
list and we can not use most gc_list_* functions for it. */
10301051
static inline void
10311052
deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
1032-
validate_list(base, 0);
1053+
validate_list(base, 0, 0);
10331054
/* Using ob_refcnt and gc_refs, calculate which objects in the
10341055
* container set are reachable from outside the set (i.e., have a
10351056
* refcount greater than 0 when all the references within the
@@ -1046,7 +1067,8 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
10461067
*/
10471068
gc_list_init(unreachable);
10481069
move_unreachable(base, unreachable); // gc_prev is pointer again
1049-
validate_list(base, 0);
1070+
validate_list(base, 0, 0);
1071+
validate_list(unreachable, PREV_MASK_COLLECTING, NEXT_MASK_UNREACHABLE);
10501072
}
10511073

10521074
/* Handle objects that may have resurrected after a call to 'finalize_garbage', moving
@@ -1123,7 +1145,7 @@ collect(struct _gc_runtime_state *state, int generation,
11231145
old = GEN_HEAD(state, generation+1);
11241146
else
11251147
old = young;
1126-
validate_list(old, 0);
1148+
validate_list(old, 0, 0);
11271149

11281150
deduce_unreachable(young, &unreachable);
11291151

@@ -1156,8 +1178,8 @@ collect(struct _gc_runtime_state *state, int generation,
11561178
*/
11571179
move_legacy_finalizer_reachable(&finalizers);
11581180

1159-
validate_list(&finalizers, 0);
1160-
validate_list(&unreachable, PREV_MASK_COLLECTING);
1181+
validate_list(&finalizers, 0, 0);
1182+
validate_list(&unreachable, PREV_MASK_COLLECTING, 0);
11611183

11621184
/* Print debugging information. */
11631185
if (state->debug & DEBUG_COLLECTABLE) {
@@ -1169,8 +1191,8 @@ collect(struct _gc_runtime_state *state, int generation,
11691191
/* Clear weakrefs and invoke callbacks as necessary. */
11701192
m += handle_weakrefs(&unreachable, old);
11711193

1172-
validate_list(old, 0);
1173-
validate_list(&unreachable, PREV_MASK_COLLECTING);
1194+
validate_list(old, 0, 0);
1195+
validate_list(&unreachable, PREV_MASK_COLLECTING, 0);
11741196

11751197
/* Call tp_finalize on objects which have one. */
11761198
finalize_garbage(&unreachable);
@@ -1208,7 +1230,7 @@ collect(struct _gc_runtime_state *state, int generation,
12081230
* this if they insist on creating this type of structure.
12091231
*/
12101232
handle_legacy_finalizers(state, &finalizers, old);
1211-
validate_list(old, 0);
1233+
validate_list(old, 0, 0);
12121234

12131235
/* Clear free list only during the collection of the highest
12141236
* generation */

0 commit comments

Comments
 (0)