From 3cc481b9de43c234889c8010e7da3af7c0f42319 Mon Sep 17 00:00:00 2001 From: scoder Date: Wed, 28 Apr 2021 18:12:16 +0200 Subject: [PATCH] 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 Co-authored-by: Victor Stinner --- Doc/c-api/gcsupport.rst | 43 +++++++++++++ Doc/data/stable_abi.dat | 3 + Doc/whatsnew/3.10.rst | 7 +++ Include/objimpl.h | 6 +- .../2021-04-28-12-33-44.bpo-28254.a2561e.rst | 3 + Modules/_testcapimodule.c | 62 +++++++++++++++++++ Modules/gcmodule.c | 35 +++++++++-- 7 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2021-04-28-12-33-44.bpo-28254.a2561e.rst diff --git a/Doc/c-api/gcsupport.rst b/Doc/c-api/gcsupport.rst index eee114c19d5..55ed9d4f7fa 100644 --- a/Doc/c-api/gcsupport.rst +++ b/Doc/c-api/gcsupport.rst @@ -173,3 +173,46 @@ if the object is immutable. this method (don't just call :c:func:`Py_DECREF` on a reference). The collector will call this method if it detects that this object is involved in a reference cycle. + + +Controlling the Garbage Collector State +--------------------------------------- + +The C-API provides the following functions for controlling +garbage collection runs. + +.. c:function:: Py_ssize_t PyGC_Collect(void) + + Perform a full garbage collection, if the garbage collector is enabled. + (Note that :func:`gc.collect` runs it unconditionally.) + + Returns the number of collected + unreachable objects which cannot + be collected. + If the garbage collector is disabled or already collecting, + returns ``0`` immediately. + Errors during garbage collection are passed to :data:`sys.unraisablehook`. + This function does not raise exceptions. + + +.. c:function:: int PyGC_Enable(void) + + Enable the garbage collector: similar to :func:`gc.enable`. + Returns the previous state, 0 for disabled and 1 for enabled. + + .. versionadded:: 3.10 + + +.. c:function:: int PyGC_Disable(void) + + Disable the garbage collector: similar to :func:`gc.disable`. + Returns the previous state, 0 for disabled and 1 for enabled. + + .. versionadded:: 3.10 + + +.. c:function:: int PyGC_IsEnabled(void) + + Query the state of the garbage collector: similar to :func:`gc.isenabled`. + Returns the current state, 0 for disabled and 1 for enabled. + + .. versionadded:: 3.10 diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index cdc71602502..491a5fbb96f 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -268,6 +268,9 @@ PyFrame_GetLineNumber PyFrozenSet_New PyFrozenSet_Type PyGC_Collect +PyGC_Disable +PyGC_Enable +PyGC_IsEnabled PyGILState_Ensure PyGILState_GetThisThreadState PyGILState_Release diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 2d8bb285fe9..37c1b8a0cb2 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1697,6 +1697,13 @@ New Features singleton or the ``False`` singleton. (Contributed by Victor Stinner in :issue:`43753`.) +* Add new functions to quickly control the garbage collector from C code: + :c:func:`PyGC_Enable()`, + :c:func:`PyGC_Disable()`, + :c:func:`PyGC_IsEnabled()`. + These functions allow to activate, deactivate and query the state of the garbage collector from C code without + having to import the :mod:`gc` module. + Porting to Python 3.10 ---------------------- diff --git a/Include/objimpl.h b/Include/objimpl.h index 1408d051ba7..689c42b7475 100644 --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -150,8 +150,12 @@ PyAPI_FUNC(PyVarObject *) _PyObject_NewVar(PyTypeObject *, Py_ssize_t); * ========================== */ -/* C equivalent of gc.collect() which ignores the state of gc.enabled. */ +/* C equivalent of gc.collect(). */ PyAPI_FUNC(Py_ssize_t) PyGC_Collect(void); +/* C API for controlling the state of the garbage collector */ +PyAPI_FUNC(int) PyGC_Enable(void); +PyAPI_FUNC(int) PyGC_Disable(void); +PyAPI_FUNC(int) PyGC_IsEnabled(void); /* Test if a type has a GC head */ #define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC) diff --git a/Misc/NEWS.d/next/C API/2021-04-28-12-33-44.bpo-28254.a2561e.rst b/Misc/NEWS.d/next/C API/2021-04-28-12-33-44.bpo-28254.a2561e.rst new file mode 100644 index 00000000000..015acc9803d --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-04-28-12-33-44.bpo-28254.a2561e.rst @@ -0,0 +1,3 @@ +Add new C-API functions to control the state of the garbage collector: +:c:func:`PyGC_Enable()`, :c:func:`PyGC_Disable()`, :c:func:`PyGC_IsEnabled()`, +corresponding to the functions in the :mod:`gc` module. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index db62aea421c..26ebacba642 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -144,6 +144,67 @@ test_sizeof_c_types(PyObject *self, PyObject *Py_UNUSED(ignored)) #endif } +static PyObject* +test_gc_control(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + int orig_enabled = PyGC_IsEnabled(); + const char* msg = "ok"; + int old_state; + + old_state = PyGC_Enable(); + msg = "Enable(1)"; + if (old_state != orig_enabled) { + goto failed; + } + msg = "IsEnabled(1)"; + if (!PyGC_IsEnabled()) { + goto failed; + } + + old_state = PyGC_Disable(); + msg = "disable(2)"; + if (!old_state) { + goto failed; + } + msg = "IsEnabled(2)"; + if (PyGC_IsEnabled()) { + goto failed; + } + + old_state = PyGC_Enable(); + msg = "enable(3)"; + if (old_state) { + goto failed; + } + msg = "IsEnabled(3)"; + if (!PyGC_IsEnabled()) { + goto failed; + } + + if (!orig_enabled) { + old_state = PyGC_Disable(); + msg = "disable(4)"; + if (old_state) { + goto failed; + } + msg = "IsEnabled(4)"; + if (PyGC_IsEnabled()) { + goto failed; + } + } + + Py_RETURN_NONE; + +failed: + /* Try to clean up if we can. */ + if (orig_enabled) { + PyGC_Enable(); + } else { + PyGC_Disable(); + } + PyErr_Format(TestError, "GC control failed in %s", msg); + return NULL; +} static PyObject* test_list_api(PyObject *self, PyObject *Py_UNUSED(ignored)) @@ -5544,6 +5605,7 @@ static PyMethodDef TestMethods[] = { {"PyDateTime_DATE_GET", test_PyDateTime_DATE_GET, METH_O}, {"PyDateTime_TIME_GET", test_PyDateTime_TIME_GET, METH_O}, {"PyDateTime_DELTA_GET", test_PyDateTime_DELTA_GET, METH_O}, + {"test_gc_control", test_gc_control, METH_NOARGS}, {"test_list_api", test_list_api, METH_NOARGS}, {"test_dict_iteration", test_dict_iteration, METH_NOARGS}, {"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS}, diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index d6b51426c4e..e5e5aa3287b 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1484,8 +1484,7 @@ static PyObject * gc_enable_impl(PyObject *module) /*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/ { - GCState *gcstate = get_gc_state(); - gcstate->enabled = 1; + PyGC_Enable(); Py_RETURN_NONE; } @@ -1499,8 +1498,7 @@ static PyObject * gc_disable_impl(PyObject *module) /*[clinic end generated code: output=97d1030f7aa9d279 input=8c2e5a14e800d83b]*/ { - GCState *gcstate = get_gc_state(); - gcstate->enabled = 0; + PyGC_Disable(); Py_RETURN_NONE; } @@ -1514,8 +1512,7 @@ static int gc_isenabled_impl(PyObject *module) /*[clinic end generated code: output=1874298331c49130 input=30005e0422373b31]*/ { - GCState *gcstate = get_gc_state(); - return gcstate->enabled; + return PyGC_IsEnabled(); } /*[clinic input] @@ -2053,6 +2050,32 @@ PyInit_gc(void) return PyModuleDef_Init(&gcmodule); } +/* C API for controlling the state of the garbage collector */ +int +PyGC_Enable(void) +{ + GCState *gcstate = get_gc_state(); + int old_state = gcstate->enabled; + gcstate->enabled = 1; + return old_state; +} + +int +PyGC_Disable(void) +{ + GCState *gcstate = get_gc_state(); + int old_state = gcstate->enabled; + gcstate->enabled = 0; + return old_state; +} + +int +PyGC_IsEnabled(void) +{ + GCState *gcstate = get_gc_state(); + return gcstate->enabled; +} + /* Public API to invoke gc.collect() from C */ Py_ssize_t PyGC_Collect(void)