Skip to content

Commit 47b0d3c

Browse files
committed
Run the GC only on eval breaker
1 parent 4f380db commit 47b0d3c

File tree

5 files changed

+47
-8
lines changed

5 files changed

+47
-8
lines changed

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

Modules/gcmodule.c

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

2255+
void
2256+
_Py_ScheduleGC(PyInterpreterState *interp)
2257+
{
2258+
struct _ceval_state *ceval = &interp->ceval;
2259+
_Py_atomic_store_relaxed(&ceval->gc_scheduled, 1);
2260+
_Py_atomic_store_relaxed(&ceval->eval_breaker, 1);
2261+
}
2262+
22552263
void
22562264
_PyObject_GC_Link(PyObject *op)
22572265
{
@@ -2269,12 +2277,19 @@ _PyObject_GC_Link(PyObject *op)
22692277
!gcstate->collecting &&
22702278
!_PyErr_Occurred(tstate))
22712279
{
2272-
gcstate->collecting = 1;
2273-
gc_collect_generations(tstate);
2274-
gcstate->collecting = 0;
2280+
_Py_ScheduleGC(tstate->interp);
22752281
}
22762282
}
22772283

2284+
void
2285+
_Py_RunGC(PyThreadState *tstate)
2286+
{
2287+
GCState *gcstate = &tstate->interp->gc;
2288+
gcstate->collecting = 1;
2289+
gc_collect_generations(tstate);
2290+
gcstate->collecting = 0;
2291+
}
2292+
22782293
static PyObject *
22792294
gc_alloc(size_t basicsize, size_t presize)
22802295
{

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: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "pycore_pyerrors.h" // _PyErr_Fetch()
66
#include "pycore_pylifecycle.h" // _PyErr_Print()
77
#include "pycore_initconfig.h" // _PyStatus_OK()
8+
#include "pycore_interp.h" // _Py_RunGC()
89
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
910

1011
/*
@@ -938,6 +939,13 @@ _Py_HandlePending(PyThreadState *tstate)
938939
{
939940
_PyRuntimeState * const runtime = &_PyRuntime;
940941
struct _ceval_runtime_state *ceval = &runtime->ceval;
942+
struct _ceval_state *interp_ceval_state = &tstate->interp->ceval;
943+
944+
/* GC scheduled to run */
945+
if (_Py_atomic_load_relaxed_int32(&interp_ceval_state->gc_scheduled)) {
946+
_Py_atomic_store_relaxed(&interp_ceval_state->gc_scheduled, 0);
947+
_Py_RunGC(tstate);
948+
}
941949

942950
/* Pending signals */
943951
if (_Py_atomic_load_relaxed_int32(&ceval->signals_pending)) {
@@ -947,20 +955,19 @@ _Py_HandlePending(PyThreadState *tstate)
947955
}
948956

949957
/* Pending calls */
950-
struct _ceval_state *ceval2 = &tstate->interp->ceval;
951-
if (_Py_atomic_load_relaxed_int32(&ceval2->pending.calls_to_do)) {
958+
if (_Py_atomic_load_relaxed_int32(&interp_ceval_state->pending.calls_to_do)) {
952959
if (make_pending_calls(tstate->interp) != 0) {
953960
return -1;
954961
}
955962
}
956963

957964
/* GIL drop request */
958-
if (_Py_atomic_load_relaxed_int32(&ceval2->gil_drop_request)) {
965+
if (_Py_atomic_load_relaxed_int32(&interp_ceval_state->gil_drop_request)) {
959966
/* Give another thread a chance */
960967
if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
961968
Py_FatalError("tstate mix-up");
962969
}
963-
drop_gil(ceval, ceval2, tstate);
970+
drop_gil(ceval, interp_ceval_state, tstate);
964971

965972
/* Other threads may run now */
966973

@@ -989,7 +996,7 @@ _Py_HandlePending(PyThreadState *tstate)
989996
// value. It prevents to interrupt the eval loop at every instruction if
990997
// the current Python thread cannot handle signals (if
991998
// _Py_ThreadCanHandleSignals() is false).
992-
COMPUTE_EVAL_BREAKER(tstate->interp, ceval, ceval2);
999+
COMPUTE_EVAL_BREAKER(tstate->interp, ceval, interp_ceval_state);
9931000
#endif
9941001

9951002
return 0;

0 commit comments

Comments
 (0)