Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions Include/internal/pycore_gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ static inline void _PyObject_GC_SET_SHARED(PyObject *op) {
* When object are moved from the pending space, old[gcstate->visited_space^1]
* into the increment, the old space bit is flipped.
*/
#define _PyGC_NEXT_MASK_OLD_SPACE_1 1

#define _PyGC_PREV_SHIFT 2
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
Expand Down Expand Up @@ -159,13 +158,11 @@ typedef enum {
// Lowest bit of _gc_next is used for flags only in GC.
// But it is always 0 for normal code.
static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) {
uintptr_t next = gc->_gc_next & _PyGC_PREV_MASK;
uintptr_t next = gc->_gc_next;
return (PyGC_Head*)next;
}
static inline void _PyGCHead_SET_NEXT(PyGC_Head *gc, PyGC_Head *next) {
uintptr_t unext = (uintptr_t)next;
assert((unext & ~_PyGC_PREV_MASK) == 0);
gc->_gc_next = (gc->_gc_next & ~_PyGC_PREV_MASK) | unext;
gc->_gc_next = (uintptr_t)next;
}

// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags.
Expand Down Expand Up @@ -249,8 +246,7 @@ static inline void _PyObject_GC_TRACK(
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
_PyGCHead_SET_NEXT(last, gc);
_PyGCHead_SET_PREV(gc, last);
uintptr_t not_visited = 1 ^ gcstate->visited_space;
gc->_gc_next = ((uintptr_t)generation0) | not_visited;
_PyGCHead_SET_NEXT(gc, generation0);
generation0->_gc_prev = (uintptr_t)gc;
gcstate->young.count++; /* number of tracked GC objects */
gcstate->heap_size++;
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ struct _gc_runtime_state {
int visited_space;
int phase;

#ifdef Py_GIL_DISABLED
/* This is the number of objects that survived the last full
collection. It approximates the number of long lived objects
tracked by the GC.
Expand All @@ -246,6 +245,7 @@ struct _gc_runtime_state {
the first time. */
Py_ssize_t long_lived_pending;

#ifdef Py_GIL_DISABLED
/* True if gc.freeze() has been used. */
int freeze_active;

Expand Down
59 changes: 44 additions & 15 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Py_GIL_DISABLED)
from test.support.import_helper import import_module
from test.support.os_helper import temp_dir, TESTFN, unlink
from test.support.script_helper import assert_python_ok, make_script, run_test_script
from test.support.script_helper import assert_python_ok, make_script
from test.support import threading_helper, gc_threshold

import gc
Expand Down Expand Up @@ -399,11 +399,19 @@ def test_collect_generations(self):
# each call to collect(N)
x = []
gc.collect(0)
# x is now in the old gen
# x is now in gen 1
a, b, c = gc.get_count()
# We don't check a since its exact values depends on
gc.collect(1)
# x is now in gen 2
d, e, f = gc.get_count()
gc.collect(2)
# x is now in gen 3
g, h, i = gc.get_count()
# We don't check a, d, g since their exact values depends on
# internal implementation details of the interpreter.
self.assertEqual((b, c), (1, 0))
self.assertEqual((e, f), (0, 1))
self.assertEqual((h, i), (0, 0))

def test_trashcan(self):
class Ouch:
Expand Down Expand Up @@ -870,10 +878,42 @@ def test_get_objects_generations(self):
self.assertTrue(
any(l is element for element in gc.get_objects(generation=0))
)
gc.collect()
self.assertFalse(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=2))
)
gc.collect(generation=0)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertTrue(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=2))
)
gc.collect(generation=1)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertTrue(
any(l is element for element in gc.get_objects(generation=2))
)
gc.collect(generation=2)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=0))
)
self.assertFalse(
any(l is element for element in gc.get_objects(generation=1))
)
self.assertTrue(
any(l is element for element in gc.get_objects(generation=2))
)
del l
gc.collect()

Expand Down Expand Up @@ -1181,17 +1221,6 @@ def test_tuple_untrack_counts(self):
self.assertTrue(new_count - count > (n // 2))


class IncrementalGCTests(unittest.TestCase):
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
@requires_gil_enabled("Free threading does not support incremental GC")
def test_incremental_gc_handles_fast_cycle_creation(self):
# Run this test in a fresh process. The number of alive objects (which can
# be from unit tests run before this one) can influence how quickly cyclic
# garbage is found.
script = support.findfile("_test_gc_fast_cycles.py")
run_test_script(script)


class GCCallbackTests(unittest.TestCase):
def setUp(self):
# Save gc state and disable it.
Expand Down
6 changes: 3 additions & 3 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ gc_get_threshold_impl(PyObject *module)
return Py_BuildValue("(iii)",
gcstate->young.threshold,
gcstate->old[0].threshold,
0);
gcstate->old[1].threshold);
}

/*[clinic input]
Expand All @@ -209,8 +209,8 @@ gc_get_count_impl(PyObject *module)

return Py_BuildValue("(iii)",
gcstate->young.count,
gcstate->old[gcstate->visited_space].count,
gcstate->old[gcstate->visited_space^1].count);
gcstate->old[0].count,
gcstate->old[1].count);
}

/*[clinic input]
Expand Down
Loading
Loading