Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 97f762a

Browse files
committed
Add a lot more validation and fix a couple of lingering bugs
1 parent af24cd7 commit 97f762a

File tree

1 file changed

+76
-28
lines changed

1 file changed

+76
-28
lines changed

Modules/gcmodule.c

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ gc_list_merge(PyGC_Head *from, PyGC_Head *to)
345345
PyGC_Head *from_tail = GC_PREV(from);
346346
assert(from_head != from);
347347
assert(from_tail != from);
348+
assert(gc_list_is_empty(to) ||
349+
gc_old_space(to_tail) == gc_old_space(from_tail));
348350

349351
_PyGCHead_SET_NEXT(to_tail, from_head);
350352
_PyGCHead_SET_PREV(from_head, to_tail);
@@ -413,8 +415,8 @@ enum flagstates {collecting_clear_unreachable_clear,
413415
static void
414416
validate_list(PyGC_Head *head, enum flagstates flags)
415417
{
416-
assert((head->_gc_prev & PREV_MASK_COLLECTING) == 0);
417-
assert((head->_gc_next & NEXT_MASK_UNREACHABLE) == 0);
418+
assert((head->_gc_prev & ~_PyGC_PREV_MASK) == 0);
419+
assert((head->_gc_next & ~_PyGC_PREV_MASK) == 0);
418420
uintptr_t prev_value = 0, next_value = 0;
419421
switch (flags) {
420422
case collecting_clear_unreachable_clear:
@@ -488,9 +490,37 @@ validate_list_is_aging(PyGC_Head *head)
488490
assert(prev == GC_PREV(head));
489491
}
490492

493+
static void
494+
validate_consistent_old_space(PyGC_Head *head)
495+
{
496+
PyGC_Head *prev = head;
497+
PyGC_Head *gc = GC_NEXT(head);
498+
if (gc == head) {
499+
return;
500+
}
501+
uintptr_t old_space = gc_old_space(gc);
502+
while (gc != head) {
503+
PyGC_Head *truenext = GC_NEXT(gc);
504+
assert(truenext != NULL);
505+
assert(gc_old_space(gc) == old_space);
506+
prev = gc;
507+
gc = truenext;
508+
}
509+
assert(prev == GC_PREV(head));
510+
}
511+
512+
static void
513+
validate_list_header(PyGC_Head *head)
514+
{
515+
assert(GC_PREV(head) == (PyGC_Head *)head->_gc_prev);
516+
assert(GC_NEXT(head) == (PyGC_Head *)head->_gc_next);
517+
}
518+
491519
#else
492520
#define validate_old(g) do{}while(0)
521+
#define validate_list_header(g) do{}while(0)
493522
#define validate_list_is_aging(l) do{}while(0)
523+
#define validate_consistent_old_space(l) do{}while(0)
494524
#endif
495525

496526
/*** end of list stuff ***/
@@ -618,7 +648,7 @@ visit_reachable(PyObject *op, PyGC_Head *reachable)
618648
prev->_gc_next & NEXT_MASK_UNREACHABLE);
619649
_PyObject_ASSERT(FROM_GC(next),
620650
next->_gc_next & NEXT_MASK_UNREACHABLE);
621-
prev->_gc_next = gc->_gc_next; // copy NEXT_MASK_UNREACHABLE
651+
prev->_gc_next = gc->_gc_next; // copy flag bits
622652
gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
623653
_PyGCHead_SET_PREV(next, prev);
624654

@@ -670,6 +700,9 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
670700
* or to the right have been scanned yet.
671701
*/
672702

703+
validate_consistent_old_space(young);
704+
/* Record which old space we are in, and set NEXT_MASK_UNREACHABLE bit for convenience */
705+
uintptr_t flags = NEXT_MASK_UNREACHABLE | (gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1);
673706
while (gc != young) {
674707
if (gc_get_refs(gc)) {
675708
/* gc is definitely reachable from outside the
@@ -715,15 +748,16 @@ move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
715748
// But this may pollute the unreachable list head's 'next' pointer
716749
// too. That's semantically senseless but expedient here - the
717750
// damage is repaired when this function ends.
718-
last->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)gc);
751+
last->_gc_next = flags | (uintptr_t)gc;
719752
_PyGCHead_SET_PREV(gc, last);
720-
gc->_gc_next = (NEXT_MASK_UNREACHABLE | (uintptr_t)unreachable);
753+
gc->_gc_next = flags | (uintptr_t)unreachable;
721754
unreachable->_gc_prev = (uintptr_t)gc;
722755
}
723756
gc = _PyGCHead_NEXT(prev);
724757
}
725758
// young->_gc_prev must be last element remained in the list.
726759
young->_gc_prev = (uintptr_t)prev;
760+
young->_gc_next &= _PyGC_PREV_MASK;
727761
// don't let the pollution of the list head's next pointer leak
728762
unreachable->_gc_next &= _PyGC_PREV_MASK;
729763
}
@@ -782,8 +816,8 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
782816
PyObject *op = FROM_GC(gc);
783817

784818
_PyObject_ASSERT(op, gc->_gc_next & NEXT_MASK_UNREACHABLE);
785-
gc->_gc_next &= _PyGC_PREV_MASK;
786-
next = (PyGC_Head*)gc->_gc_next;
819+
next = GC_NEXT(gc);
820+
gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
787821

788822
if (has_legacy_finalizer(op)) {
789823
gc_clear_collecting(gc);
@@ -802,8 +836,8 @@ clear_unreachable_mask(PyGC_Head *unreachable)
802836
assert((unreachable->_gc_next & NEXT_MASK_UNREACHABLE) == 0);
803837
for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
804838
_PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE);
805-
gc->_gc_next &= _PyGC_PREV_MASK;
806-
next = (PyGC_Head*)gc->_gc_next;
839+
next = GC_NEXT(gc);
840+
gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
807841
}
808842
validate_list(unreachable, collecting_set_unreachable_clear);
809843
}
@@ -1181,8 +1215,11 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
11811215
* refcount greater than 0 when all the references within the
11821216
* set are taken into account).
11831217
*/
1218+
validate_list_header(base);
11841219
update_refs(base); // gc_prev is used for gc_refs
1220+
validate_list_header(base);
11851221
subtract_refs(base);
1222+
validate_list_header(base);
11861223

11871224
/* Leave everything reachable from outside base in base, and move
11881225
* everything else (in base) to unreachable.
@@ -1219,7 +1256,6 @@ deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
12191256
* the reachable objects instead. But this is a one-time cost, probably not
12201257
* worth complicating the code to speed just a little.
12211258
*/
1222-
gc_list_init(unreachable);
12231259
move_unreachable(base, unreachable); // gc_prev is pointer again
12241260
validate_list(base, collecting_clear_unreachable_clear);
12251261
validate_list(unreachable, collecting_set_unreachable_set);
@@ -1244,13 +1280,16 @@ handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
12441280
{
12451281
// Remove the PREV_MASK_COLLECTING from unreachable
12461282
// to prepare it for a new call to 'deduce_unreachable'
1283+
validate_list_header(unreachable);
12471284
gc_list_clear_collecting(unreachable);
12481285

12491286
// After the call to deduce_unreachable, the 'still_unreachable' set will
12501287
// have the PREV_MARK_COLLECTING set, but the objects are going to be
12511288
// removed so we can skip the expense of clearing the flag.
12521289
PyGC_Head* resurrected = unreachable;
1290+
validate_list_header(resurrected);
12531291
deduce_unreachable(resurrected, still_unreachable);
1292+
validate_list_header(resurrected);
12541293
clear_unreachable_mask(still_unreachable);
12551294

12561295
// Move the resurrected objects to the old generation for future collection.
@@ -1437,7 +1476,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14371476
PyGC_Head increment;
14381477
gc_list_init(&increment);
14391478
Py_ssize_t work_to_do = -gcstate->incremental_gc_progress;
1440-
Py_ssize_t region_size = 0;
14411479
validate_old(gcstate);
14421480
if (gc_list_is_empty(oldest)) {
14431481
if (gc_list_is_empty(aging)) {
@@ -1453,6 +1491,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14531491
gcstate->aging_space = flip_old_space(gcstate->aging_space);
14541492
}
14551493
validate_old(gcstate);
1494+
Py_ssize_t region_size = 0;
14561495
while (region_size < work_to_do) {
14571496
if (gc_list_is_empty(oldest)) {
14581497
gcstate->incremental_gc_progress = 0;
@@ -1465,17 +1504,12 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14651504
}
14661505
validate_old(gcstate);
14671506
validate_list_is_aging(&increment);
1468-
#ifdef Py_STATS
1469-
{
1470-
Py_ssize_t count = 0;
1471-
PyGC_Head *gc;
1472-
for (gc = GC_NEXT(&increment); gc != &increment; gc = GC_NEXT(gc)) {
1473-
count++;
1474-
}
1475-
GC_STAT_ADD(NUM_GENERATIONS, objects_queued, count);
1476-
}
1477-
#endif
1478-
gc_collect_region(tstate, &increment, aging, 0, stats);
1507+
GC_STAT_ADD(1, objects_queued, region_size);
1508+
PyGC_Head survivors;
1509+
gc_list_init(&survivors);
1510+
gc_collect_region(tstate, &increment, &survivors, 0, stats);
1511+
validate_list_is_aging(&survivors);
1512+
gc_list_merge(&survivors, aging);
14791513
validate_old(gcstate);
14801514
assert(gc_list_is_empty(&increment));
14811515
gcstate->incremental_gc_progress += region_size;
@@ -1525,8 +1559,6 @@ gc_collect_region(PyThreadState *tstate,
15251559
int untrack,
15261560
struct gc_collection_stats *stats)
15271561
{
1528-
Py_ssize_t m = 0; /* # objects collected */
1529-
Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */
15301562
PyGC_Head unreachable; /* non-problematic unreachable trash */
15311563
PyGC_Head finalizers; /* objects with, & reachable from, __del__ */
15321564
PyGC_Head *gc; /* initialize to prevent a compiler warning */
@@ -1537,17 +1569,23 @@ gc_collect_region(PyThreadState *tstate,
15371569

15381570
validate_list(from, collecting_clear_unreachable_clear);
15391571
validate_list(to, collecting_clear_unreachable_clear);
1572+
validate_consistent_old_space(from);
1573+
validate_consistent_old_space(to);
15401574

1575+
gc_list_init(&unreachable);
15411576
deduce_unreachable(from, &unreachable);
1577+
validate_consistent_old_space(from);
15421578
if (untrack & UNTRACK_TUPLES) {
15431579
untrack_tuples(from);
15441580
}
15451581
if (untrack & UNTRACK_DICTS) {
15461582
untrack_dicts(from);
15471583
}
1584+
validate_consistent_old_space(to);
15481585
if (from != to) {
15491586
gc_list_merge(from, to);
15501587
}
1588+
validate_consistent_old_space(to);
15511589
validate_old(gcstate);
15521590
/* Move reachable objects to next generation. */
15531591

@@ -1558,11 +1596,14 @@ gc_collect_region(PyThreadState *tstate,
15581596
// NEXT_MASK_UNREACHABLE is cleared here.
15591597
// After move_legacy_finalizers(), unreachable is normal list.
15601598
move_legacy_finalizers(&unreachable, &finalizers);
1599+
validate_consistent_old_space(&unreachable);
1600+
validate_consistent_old_space(&finalizers);
15611601
/* finalizers contains the unreachable objects with a legacy finalizer;
15621602
* unreachable objects reachable *from* those are also uncollectable,
15631603
* and we move those into the finalizers list too.
15641604
*/
15651605
move_legacy_finalizer_reachable(&finalizers);
1606+
validate_consistent_old_space(&finalizers);
15661607

15671608
validate_list(&finalizers, collecting_clear_unreachable_clear);
15681609
validate_list(&unreachable, collecting_set_unreachable_clear);
@@ -1575,44 +1616,51 @@ gc_collect_region(PyThreadState *tstate,
15751616
}
15761617

15771618
/* Clear weakrefs and invoke callbacks as necessary. */
1578-
m += handle_weakrefs(&unreachable, to);
1619+
stats->collected += handle_weakrefs(&unreachable, to);
1620+
validate_consistent_old_space(to);
15791621

15801622
validate_list(to, collecting_clear_unreachable_clear);
15811623
validate_list(&unreachable, collecting_set_unreachable_clear);
15821624

15831625
/* Call tp_finalize on objects which have one. */
15841626
finalize_garbage(tstate, &unreachable);
1627+
validate_consistent_old_space(&unreachable);
15851628

15861629
/* Handle any objects that may have resurrected after the call
15871630
* to 'finalize_garbage' and continue the collection with the
15881631
* objects that are still unreachable */
15891632
PyGC_Head final_unreachable;
1633+
gc_list_init(&final_unreachable);
1634+
validate_consistent_old_space(to);
15901635
handle_resurrected_objects(&unreachable, &final_unreachable, to);
1636+
validate_consistent_old_space(to);
15911637

15921638
/* Call tp_clear on objects in the final_unreachable set. This will cause
15931639
* the reference cycles to be broken. It may also cause some objects
15941640
* in finalizers to be freed.
15951641
*/
1596-
m += gc_list_size(&final_unreachable);
1642+
stats->collected += gc_list_size(&final_unreachable);
15971643
delete_garbage(tstate, gcstate, &final_unreachable, to);
1644+
validate_consistent_old_space(to);
15981645

15991646
/* Collect statistics on uncollectable objects found and print
16001647
* debugging information. */
1648+
Py_ssize_t n = 0;
16011649
for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) {
16021650
n++;
16031651
if (gcstate->debug & DEBUG_UNCOLLECTABLE)
16041652
debug_cycle("uncollectable", FROM_GC(gc));
16051653
}
16061654

1655+
stats->uncollectable = n;
16071656
/* Append instances in the uncollectable set to a Python
16081657
* reachable list of garbage. The programmer has to deal with
16091658
* this if they insist on creating this type of structure.
16101659
*/
16111660
handle_legacy_finalizers(tstate, gcstate, &finalizers, to);
16121661
validate_list(to, collecting_clear_unreachable_clear);
1662+
validate_consistent_old_space(to);
16131663

1614-
stats->collected = m;
1615-
stats->uncollectable = n;
16161664
validate_old(gcstate);
16171665
}
16181666

0 commit comments

Comments
 (0)