gh-97922: Run the GC only on eval breaker (#97920) · python/cpython@83eb827 · GitHub
Skip to content

Commit 83eb827

Browse files
authored
gh-97922: Run the GC only on eval breaker (#97920)
1 parent c66dbdd commit 83eb827

8 files changed

Lines changed: 74 additions & 14 deletions

File tree

Doc/whatsnew/3.12.rst

Lines changed: 7 additions & 0 deletions

Include/internal/pycore_gc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ extern void _PyList_ClearFreeList(PyInterpreterState *interp);
202202
extern void _PyDict_ClearFreeList(PyInterpreterState *interp);
203203
extern void _PyAsyncGen_ClearFreeLists(PyInterpreterState *interp);
204204
extern void _PyContext_ClearFreeList(PyInterpreterState *interp);
205+
extern void _Py_ScheduleGC(PyInterpreterState *interp);
206+
extern void _Py_RunGC(PyThreadState *tstate);
205207

206208
#ifdef __cplusplus
207209
}

Include/internal/pycore_interp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ struct _ceval_state {
4949
_Py_atomic_int eval_breaker;
5050
/* Request for dropping the GIL */
5151
_Py_atomic_int gil_drop_request;
52+
/* The GC is ready to be executed */
53+
_Py_atomic_int gc_scheduled;
5254
struct _pending_calls pending;
5355
};
5456

Lib/test/test_frame.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def callback(phase, info):
277277
frame!
278278
"""
279279
nonlocal sneaky_frame_object
280-
sneaky_frame_object = sys._getframe().f_back
280+
sneaky_frame_object = sys._getframe().f_back.f_back
281281
# We're done here:
282282
gc.callbacks.remove(callback)
283283

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The Garbage Collector now runs only on the eval breaker mechanism of the
2+
Python bytecode evaluation loop instead on object allocations. The GC can
3+
also run when :c:func:`PyErr_CheckSignals` is called so C extensions that
4+
need to run for a long time without executing any Python code also have a
5+
chance to execute the GC periodically.

Modules/gcmodule.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,6 +2252,20 @@ PyObject_IS_GC(PyObject *obj)
22522252
return _PyObject_IS_GC(obj);
22532253
}
22542254

2255+
void
2256+
_Py_ScheduleGC(PyInterpreterState *interp)
2257+
{
2258+
GCState *gcstate = &interp->gc;
2259+
if (gcstate->collecting == 1) {
2260+
return;
2261+
}
2262+
struct _ceval_state *ceval = &interp->ceval;
2263+
if (!_Py_atomic_load_relaxed(&ceval->gc_scheduled)) {
2264+
_Py_atomic_store_relaxed(&ceval->gc_scheduled, 1);
2265+
_Py_atomic_store_relaxed(&ceval->eval_breaker, 1);
2266+
}
2267+
}
2268+
22552269
void
22562270
_PyObject_GC_Link(PyObject *op)
22572271
{
@@ -2269,12 +2283,19 @@ _PyObject_GC_Link(PyObject *op)
22692283
!gcstate->collecting &&
22702284
!_PyErr_Occurred(tstate))
22712285
{
2272-
gcstate->collecting = 1;
2273-
gc_collect_generations(tstate);
2274-
gcstate->collecting = 0;
2286+
_Py_ScheduleGC(tstate->interp);
22752287
}
22762288
}
22772289

2290+
void
2291+
_Py_RunGC(PyThreadState *tstate)
2292+
{
2293+
GCState *gcstate = &tstate->interp->gc;
2294+
gcstate->collecting = 1;
2295+
gc_collect_generations(tstate);
2296+
gcstate->collecting = 0;
2297+
}
2298+
22782299
static PyObject *
22792300
gc_alloc(size_t basicsize, size_t presize)
22802301
{

Modules/signalmodule.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,19 @@ int
17981798
PyErr_CheckSignals(void)
17991799
{
18001800
PyThreadState *tstate = _PyThreadState_GET();
1801+
1802+
/* Opportunistically check if the GC is scheduled to run and run it
1803+
if we have a request. This is done here because native code needs
1804+
to call this API if is going to run for some time without executing
1805+
Python code to ensure signals are handled. Checking for the GC here
1806+
allows long running native code to clean cycles created using the C-API
1807+
even if it doesn't run the evaluation loop */
1808+
struct _ceval_state *interp_ceval_state = &tstate->interp->ceval;
1809+
if (_Py_atomic_load_relaxed(&interp_ceval_state->gc_scheduled)) {
1810+
_Py_atomic_store_relaxed(&interp_ceval_state->gc_scheduled, 0);
1811+
_Py_RunGC(tstate);
1812+
}
1813+
18011814
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
18021815
return 0;
18031816
}

Python/ceval_gil.c

Lines changed: 20 additions & 10 deletions

0 commit comments

Comments
 (0)