Skip to content

Commit 3cc481b

Browse files
scoderpablogsalvstinner
authored
bpo-28254: Add a C-API for controlling the GC state (GH-25687)
Add new C-API functions to control the state of the garbage collector: PyGC_Enable(), PyGC_Disable(), PyGC_IsEnabled(), corresponding to the functions in the gc module. Co-authored-by: Pablo Galindo <[email protected]> Co-authored-by: Victor Stinner <[email protected]>
1 parent baecfbd commit 3cc481b

File tree

7 files changed

+152
-7
lines changed

7 files changed

+152
-7
lines changed

Doc/c-api/gcsupport.rst

+43
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,46 @@ if the object is immutable.
173173
this method (don't just call :c:func:`Py_DECREF` on a reference). The
174174
collector will call this method if it detects that this object is involved
175175
in a reference cycle.
176+
177+
178+
Controlling the Garbage Collector State
179+
---------------------------------------
180+
181+
The C-API provides the following functions for controlling
182+
garbage collection runs.
183+
184+
.. c:function:: Py_ssize_t PyGC_Collect(void)
185+
186+
Perform a full garbage collection, if the garbage collector is enabled.
187+
(Note that :func:`gc.collect` runs it unconditionally.)
188+
189+
Returns the number of collected + unreachable objects which cannot
190+
be collected.
191+
If the garbage collector is disabled or already collecting,
192+
returns ``0`` immediately.
193+
Errors during garbage collection are passed to :data:`sys.unraisablehook`.
194+
This function does not raise exceptions.
195+
196+
197+
.. c:function:: int PyGC_Enable(void)
198+
199+
Enable the garbage collector: similar to :func:`gc.enable`.
200+
Returns the previous state, 0 for disabled and 1 for enabled.
201+
202+
.. versionadded:: 3.10
203+
204+
205+
.. c:function:: int PyGC_Disable(void)
206+
207+
Disable the garbage collector: similar to :func:`gc.disable`.
208+
Returns the previous state, 0 for disabled and 1 for enabled.
209+
210+
.. versionadded:: 3.10
211+
212+
213+
.. c:function:: int PyGC_IsEnabled(void)
214+
215+
Query the state of the garbage collector: similar to :func:`gc.isenabled`.
216+
Returns the current state, 0 for disabled and 1 for enabled.
217+
218+
.. versionadded:: 3.10

Doc/data/stable_abi.dat

+3
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,9 @@ PyFrame_GetLineNumber
268268
PyFrozenSet_New
269269
PyFrozenSet_Type
270270
PyGC_Collect
271+
PyGC_Disable
272+
PyGC_Enable
273+
PyGC_IsEnabled
271274
PyGILState_Ensure
272275
PyGILState_GetThisThreadState
273276
PyGILState_Release

Doc/whatsnew/3.10.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,13 @@ New Features
16971697
singleton or the ``False`` singleton.
16981698
(Contributed by Victor Stinner in :issue:`43753`.)
16991699
1700+
* Add new functions to quickly control the garbage collector from C code:
1701+
:c:func:`PyGC_Enable()`,
1702+
:c:func:`PyGC_Disable()`,
1703+
:c:func:`PyGC_IsEnabled()`.
1704+
These functions allow to activate, deactivate and query the state of the garbage collector from C code without
1705+
having to import the :mod:`gc` module.
1706+
17001707
Porting to Python 3.10
17011708
----------------------
17021709

Include/objimpl.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,12 @@ PyAPI_FUNC(PyVarObject *) _PyObject_NewVar(PyTypeObject *, Py_ssize_t);
150150
* ==========================
151151
*/
152152

153-
/* C equivalent of gc.collect() which ignores the state of gc.enabled. */
153+
/* C equivalent of gc.collect(). */
154154
PyAPI_FUNC(Py_ssize_t) PyGC_Collect(void);
155+
/* C API for controlling the state of the garbage collector */
156+
PyAPI_FUNC(int) PyGC_Enable(void);
157+
PyAPI_FUNC(int) PyGC_Disable(void);
158+
PyAPI_FUNC(int) PyGC_IsEnabled(void);
155159

156160
/* Test if a type has a GC head */
157161
#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add new C-API functions to control the state of the garbage collector:
2+
:c:func:`PyGC_Enable()`, :c:func:`PyGC_Disable()`, :c:func:`PyGC_IsEnabled()`,
3+
corresponding to the functions in the :mod:`gc` module.

Modules/_testcapimodule.c

+62
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,67 @@ test_sizeof_c_types(PyObject *self, PyObject *Py_UNUSED(ignored))
144144
#endif
145145
}
146146

147+
static PyObject*
148+
test_gc_control(PyObject *self, PyObject *Py_UNUSED(ignored))
149+
{
150+
int orig_enabled = PyGC_IsEnabled();
151+
const char* msg = "ok";
152+
int old_state;
153+
154+
old_state = PyGC_Enable();
155+
msg = "Enable(1)";
156+
if (old_state != orig_enabled) {
157+
goto failed;
158+
}
159+
msg = "IsEnabled(1)";
160+
if (!PyGC_IsEnabled()) {
161+
goto failed;
162+
}
163+
164+
old_state = PyGC_Disable();
165+
msg = "disable(2)";
166+
if (!old_state) {
167+
goto failed;
168+
}
169+
msg = "IsEnabled(2)";
170+
if (PyGC_IsEnabled()) {
171+
goto failed;
172+
}
173+
174+
old_state = PyGC_Enable();
175+
msg = "enable(3)";
176+
if (old_state) {
177+
goto failed;
178+
}
179+
msg = "IsEnabled(3)";
180+
if (!PyGC_IsEnabled()) {
181+
goto failed;
182+
}
183+
184+
if (!orig_enabled) {
185+
old_state = PyGC_Disable();
186+
msg = "disable(4)";
187+
if (old_state) {
188+
goto failed;
189+
}
190+
msg = "IsEnabled(4)";
191+
if (PyGC_IsEnabled()) {
192+
goto failed;
193+
}
194+
}
195+
196+
Py_RETURN_NONE;
197+
198+
failed:
199+
/* Try to clean up if we can. */
200+
if (orig_enabled) {
201+
PyGC_Enable();
202+
} else {
203+
PyGC_Disable();
204+
}
205+
PyErr_Format(TestError, "GC control failed in %s", msg);
206+
return NULL;
207+
}
147208

148209
static PyObject*
149210
test_list_api(PyObject *self, PyObject *Py_UNUSED(ignored))
@@ -5544,6 +5605,7 @@ static PyMethodDef TestMethods[] = {
55445605
{"PyDateTime_DATE_GET", test_PyDateTime_DATE_GET, METH_O},
55455606
{"PyDateTime_TIME_GET", test_PyDateTime_TIME_GET, METH_O},
55465607
{"PyDateTime_DELTA_GET", test_PyDateTime_DELTA_GET, METH_O},
5608+
{"test_gc_control", test_gc_control, METH_NOARGS},
55475609
{"test_list_api", test_list_api, METH_NOARGS},
55485610
{"test_dict_iteration", test_dict_iteration, METH_NOARGS},
55495611
{"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS},

Modules/gcmodule.c

+29-6
Original file line numberDiff line numberDiff line change
@@ -1484,8 +1484,7 @@ static PyObject *
14841484
gc_enable_impl(PyObject *module)
14851485
/*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/
14861486
{
1487-
GCState *gcstate = get_gc_state();
1488-
gcstate->enabled = 1;
1487+
PyGC_Enable();
14891488
Py_RETURN_NONE;
14901489
}
14911490

@@ -1499,8 +1498,7 @@ static PyObject *
14991498
gc_disable_impl(PyObject *module)
15001499
/*[clinic end generated code: output=97d1030f7aa9d279 input=8c2e5a14e800d83b]*/
15011500
{
1502-
GCState *gcstate = get_gc_state();
1503-
gcstate->enabled = 0;
1501+
PyGC_Disable();
15041502
Py_RETURN_NONE;
15051503
}
15061504

@@ -1514,8 +1512,7 @@ static int
15141512
gc_isenabled_impl(PyObject *module)
15151513
/*[clinic end generated code: output=1874298331c49130 input=30005e0422373b31]*/
15161514
{
1517-
GCState *gcstate = get_gc_state();
1518-
return gcstate->enabled;
1515+
return PyGC_IsEnabled();
15191516
}
15201517

15211518
/*[clinic input]
@@ -2053,6 +2050,32 @@ PyInit_gc(void)
20532050
return PyModuleDef_Init(&gcmodule);
20542051
}
20552052

2053+
/* C API for controlling the state of the garbage collector */
2054+
int
2055+
PyGC_Enable(void)
2056+
{
2057+
GCState *gcstate = get_gc_state();
2058+
int old_state = gcstate->enabled;
2059+
gcstate->enabled = 1;
2060+
return old_state;
2061+
}
2062+
2063+
int
2064+
PyGC_Disable(void)
2065+
{
2066+
GCState *gcstate = get_gc_state();
2067+
int old_state = gcstate->enabled;
2068+
gcstate->enabled = 0;
2069+
return old_state;
2070+
}
2071+
2072+
int
2073+
PyGC_IsEnabled(void)
2074+
{
2075+
GCState *gcstate = get_gc_state();
2076+
return gcstate->enabled;
2077+
}
2078+
20562079
/* Public API to invoke gc.collect() from C */
20572080
Py_ssize_t
20582081
PyGC_Collect(void)

0 commit comments

Comments
 (0)