[Python-checkins] bpo-28254: Add a C-API for controlling the GC state (GH-25687)

vstinner webhook-mailer at python.org
Wed Apr 28 12:12:30 EDT 2021


https://github.com/python/cpython/commit/3cc481b9de43c234889c8010e7da3af7c0f42319
commit: 3cc481b9de43c234889c8010e7da3af7c0f42319
branch: master
author: scoder <stefan_ml at behnel.de>
committer: vstinner <vstinner at python.org>
date: 2021-04-28T18:12:16+02:00
summary:

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 <Pablogsal at gmail.com>
Co-authored-by: Victor Stinner <vstinner at python.org>

files:
A Misc/NEWS.d/next/C API/2021-04-28-12-33-44.bpo-28254.a2561e.rst
M Doc/c-api/gcsupport.rst
M Doc/data/stable_abi.dat
M Doc/whatsnew/3.10.rst
M Include/objimpl.h
M Modules/_testcapimodule.c
M Modules/gcmodule.c

diff --git a/Doc/c-api/gcsupport.rst b/Doc/c-api/gcsupport.rst
index eee114c19d5904..55ed9d4f7fad48 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 cdc7160250243b..491a5fbb96f5a5 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 2d8bb285fe981b..37c1b8a0cb2ebc 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 1408d051ba7efe..689c42b7475c89 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 00000000000000..015acc9803d474
--- /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 db62aea421c806..26ebacba642a4f 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 d6b51426c4e2d8..e5e5aa3287b0d6 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)



More information about the Python-checkins mailing list