Skip to content

Commit 8bef34f

Browse files
authored
GH-117108: Set the "old space bit" to "visited" for all young objects (#117213)
Change old space bit of young objects from 0 to gcstate->visited_space. This ensures that any object created *and* collected during cycle GC has the bit set correctly.
1 parent bf82f77 commit 8bef34f

File tree

5 files changed

+56
-43
lines changed

5 files changed

+56
-43
lines changed

Include/internal/pycore_gc.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,19 @@ static inline void _PyObject_GC_SET_SHARED_INLINE(PyObject *op) {
113113
/* Bit 1 is set when the object is in generation which is GCed currently. */
114114
#define _PyGC_PREV_MASK_COLLECTING 2
115115

116-
/* Bit 0 is set if the object belongs to old space 1 */
116+
/* Bit 0 in _gc_next is the old space bit.
117+
* It is set as follows:
118+
* Young: gcstate->visited_space
119+
* old[0]: 0
120+
* old[1]: 1
121+
* permanent: 0
122+
*
123+
* During a collection all objects handled should have the bit set to
124+
* gcstate->visited_space, as objects are moved from the young gen
125+
* and the increment into old[gcstate->visited_space].
126+
* When object are moved from the pending space, old[gcstate->visited_space^1]
127+
* into the increment, the old space bit is flipped.
128+
*/
117129
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1
118130

119131
#define _PyGC_PREV_SHIFT 2

Include/internal/pycore_object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,8 @@ static inline void _PyObject_GC_TRACK(
318318
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
319319
_PyGCHead_SET_NEXT(last, gc);
320320
_PyGCHead_SET_PREV(gc, last);
321-
_PyGCHead_SET_NEXT(gc, generation0);
322-
assert((gc->_gc_next & _PyGC_NEXT_MASK_OLD_SPACE_1) == 0);
321+
/* Young objects will be moved into the visited space during GC, so set the bit here */
322+
gc->_gc_next = ((uintptr_t)generation0) | interp->gc.visited_space;
323323
generation0->_gc_prev = (uintptr_t)gc;
324324
#endif
325325
}

Lib/test/test_gc.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -823,32 +823,10 @@ def test_get_objects_generations(self):
823823
self.assertTrue(
824824
any(l is element for element in gc.get_objects(generation=0))
825825
)
826-
self.assertFalse(
827-
any(l is element for element in gc.get_objects(generation=1))
828-
)
829-
self.assertFalse(
830-
any(l is element for element in gc.get_objects(generation=2))
831-
)
832-
gc.collect(generation=0)
833-
self.assertFalse(
834-
any(l is element for element in gc.get_objects(generation=0))
835-
)
836-
self.assertTrue(
837-
any(l is element for element in gc.get_objects(generation=1))
838-
)
839-
self.assertFalse(
840-
any(l is element for element in gc.get_objects(generation=2))
841-
)
842-
gc.collect(generation=2)
826+
gc.collect()
843827
self.assertFalse(
844828
any(l is element for element in gc.get_objects(generation=0))
845829
)
846-
self.assertFalse(
847-
any(l is element for element in gc.get_objects(generation=1))
848-
)
849-
self.assertTrue(
850-
any(l is element for element in gc.get_objects(generation=2))
851-
)
852830
del l
853831
gc.collect()
854832

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change the old space bit of objects in the young generation from 0 to
2+
gcstate->visited, so that any objects created during GC will have the old
3+
bit set correctly if they get moved into the old generation.

Python/gc.c

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -455,10 +455,20 @@ validate_consistent_old_space(PyGC_Head *head)
455455
assert(prev == GC_PREV(head));
456456
}
457457

458+
static void
459+
gc_list_validate_space(PyGC_Head *head, int space) {
460+
PyGC_Head *gc = GC_NEXT(head);
461+
while (gc != head) {
462+
assert(gc_old_space(gc) == space);
463+
gc = GC_NEXT(gc);
464+
}
465+
}
466+
458467
#else
459468
#define validate_list(x, y) do{}while(0)
460469
#define validate_old(g) do{}while(0)
461470
#define validate_consistent_old_space(l) do{}while(0)
471+
#define gc_list_validate_space(l, s) do{}while(0)
462472
#endif
463473

464474
/*** end of list stuff ***/
@@ -949,6 +959,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
949959
/* Invoke the callbacks we decided to honor. It's safe to invoke them
950960
* because they can't reference unreachable objects.
951961
*/
962+
int visited_space = get_gc_state()->visited_space;
952963
while (! gc_list_is_empty(&wrcb_to_call)) {
953964
PyObject *temp;
954965
PyObject *callback;
@@ -983,6 +994,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
983994
Py_DECREF(op);
984995
if (wrcb_to_call._gc_next == (uintptr_t)gc) {
985996
/* object is still alive -- move it */
997+
gc_set_old_space(gc, visited_space);
986998
gc_list_move(gc, old);
987999
}
9881000
else {
@@ -1389,6 +1401,14 @@ completed_cycle(GCState *gcstate)
13891401
assert(gc_list_is_empty(not_visited));
13901402
#endif
13911403
gcstate->visited_space = flip_old_space(gcstate->visited_space);
1404+
/* Make sure all young objects have old space bit set correctly */
1405+
PyGC_Head *young = &gcstate->young.head;
1406+
PyGC_Head *gc = GC_NEXT(young);
1407+
while (gc != young) {
1408+
PyGC_Head *next = GC_NEXT(gc);
1409+
gc_set_old_space(gc, gcstate->visited_space);
1410+
gc = next;
1411+
}
13921412
gcstate->work_to_do = 0;
13931413
}
13941414

@@ -1406,10 +1426,7 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14061426
}
14071427
gc_list_merge(&gcstate->young.head, &increment);
14081428
gcstate->young.count = 0;
1409-
if (gcstate->visited_space) {
1410-
/* objects in visited space have bit set, so we set it here */
1411-
gc_list_set_space(&increment, 1);
1412-
}
1429+
gc_list_validate_space(&increment, gcstate->visited_space);
14131430
Py_ssize_t increment_size = 0;
14141431
while (increment_size < gcstate->work_to_do) {
14151432
if (gc_list_is_empty(not_visited)) {
@@ -1421,9 +1438,11 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14211438
gc_set_old_space(gc, gcstate->visited_space);
14221439
increment_size += expand_region_transitively_reachable(&increment, gc, gcstate);
14231440
}
1441+
gc_list_validate_space(&increment, gcstate->visited_space);
14241442
PyGC_Head survivors;
14251443
gc_list_init(&survivors);
14261444
gc_collect_region(tstate, &increment, &survivors, UNTRACK_TUPLES, stats);
1445+
gc_list_validate_space(&survivors, gcstate->visited_space);
14271446
gc_list_merge(&survivors, visited);
14281447
assert(gc_list_is_empty(&increment));
14291448
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
@@ -1444,23 +1463,18 @@ gc_collect_full(PyThreadState *tstate,
14441463
GCState *gcstate = &tstate->interp->gc;
14451464
validate_old(gcstate);
14461465
PyGC_Head *young = &gcstate->young.head;
1447-
PyGC_Head *old0 = &gcstate->old[0].head;
1448-
PyGC_Head *old1 = &gcstate->old[1].head;
1449-
/* merge all generations into old0 */
1450-
gc_list_merge(young, old0);
1466+
PyGC_Head *pending = &gcstate->old[gcstate->visited_space^1].head;
1467+
PyGC_Head *visited = &gcstate->old[gcstate->visited_space].head;
1468+
/* merge all generations into visited */
1469+
gc_list_validate_space(young, gcstate->visited_space);
1470+
gc_list_set_space(pending, gcstate->visited_space);
1471+
gc_list_merge(young, pending);
14511472
gcstate->young.count = 0;
1452-
PyGC_Head *gc = GC_NEXT(old1);
1453-
while (gc != old1) {
1454-
PyGC_Head *next = GC_NEXT(gc);
1455-
gc_set_old_space(gc, 0);
1456-
gc = next;
1457-
}
1458-
gc_list_merge(old1, old0);
1473+
gc_list_merge(pending, visited);
14591474

1460-
gc_collect_region(tstate, old0, old0,
1475+
gc_collect_region(tstate, visited, visited,
14611476
UNTRACK_TUPLES | UNTRACK_DICTS,
14621477
stats);
1463-
gcstate->visited_space = 1;
14641478
gcstate->young.count = 0;
14651479
gcstate->old[0].count = 0;
14661480
gcstate->old[1].count = 0;
@@ -1527,6 +1541,7 @@ gc_collect_region(PyThreadState *tstate,
15271541

15281542
/* Clear weakrefs and invoke callbacks as necessary. */
15291543
stats->collected += handle_weakrefs(&unreachable, to);
1544+
gc_list_validate_space(to, gcstate->visited_space);
15301545
validate_list(to, collecting_clear_unreachable_clear);
15311546
validate_list(&unreachable, collecting_set_unreachable_clear);
15321547

@@ -1560,6 +1575,7 @@ gc_collect_region(PyThreadState *tstate,
15601575
* this if they insist on creating this type of structure.
15611576
*/
15621577
handle_legacy_finalizers(tstate, gcstate, &finalizers, to);
1578+
gc_list_validate_space(to, gcstate->visited_space);
15631579
validate_list(to, collecting_clear_unreachable_clear);
15641580
}
15651581

@@ -1708,6 +1724,10 @@ void
17081724
_PyGC_Freeze(PyInterpreterState *interp)
17091725
{
17101726
GCState *gcstate = &interp->gc;
1727+
/* The permanent_generation has its old space bit set to zero */
1728+
if (gcstate->visited_space) {
1729+
gc_list_set_space(&gcstate->young.head, 0);
1730+
}
17111731
gc_list_merge(&gcstate->young.head, &gcstate->permanent_generation.head);
17121732
gcstate->young.count = 0;
17131733
PyGC_Head*old0 = &gcstate->old[0].head;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy