Skip to content
Open
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
18 changes: 18 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,24 @@ def test_heap_size(self):
del l
self.assertEqual(count, _testinternalcapi.get_tracked_heap_size())

@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
def test_clear_frame_on_early_return(self):
# __del__ methods can trigger collection, make this to happen
thresholds = gc.get_threshold()
gc.enable()
gc.set_threshold(1)

class A:
def __del__(self):
dir(self)

x = [A() for _ in range(10)]
del x
self.assertTrue(_testinternalcapi.is_gc_frame_clear())

gc.disable()
gc.set_threshold(*thresholds)


class GCCallbackTests(unittest.TestCase):
def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a bug in the garbage collector where frames could be kept alive longer
than necessary, potentially distorting profiler statistics. Patch by Sergey
Miryanov.
14 changes: 14 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -3206,6 +3206,19 @@ test_thread_state_ensure_from_view_interp_switch(PyObject *self, PyObject *unuse
Py_RETURN_NONE;
}

static PyObject *
is_gc_frame_clear(PyObject *self, PyObject *unused)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
assert(interp != NULL);

if (!interp->gc.frame) {
Py_RETURN_TRUE;
}

Py_RETURN_FALSE;
}

/* Self interrupting context manager */

typedef struct {
Expand Down Expand Up @@ -3393,6 +3406,7 @@ static PyMethodDef module_functions[] = {
{"test_interp_guard_countdown", test_interp_guard_countdown, METH_NOARGS},
{"test_interp_view_countdown", test_interp_view_countdown, METH_NOARGS},
{"test_thread_state_ensure_from_view_interp_switch", test_thread_state_ensure_from_view_interp_switch, METH_NOARGS},
{"is_gc_frame_clear", is_gc_frame_clear, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
1 change: 1 addition & 0 deletions Python/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
// objects from that generation and all generations younger than it.
generation = gc_select_generation(gcstate);
if (generation < 0) {
gcstate->frame = NULL;
// No generation needs to be collected.
_Py_atomic_store_int(&gcstate->collecting, 0);
return 0;
Expand Down
Loading