summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/gcsupport.rst43
-rw-r--r--Doc/data/stable_abi.dat3
-rw-r--r--Doc/whatsnew/3.10.rst7
-rw-r--r--Include/objimpl.h6
-rw-r--r--Misc/NEWS.d/next/C API/2021-04-28-12-33-44.bpo-28254.a2561e.rst3
-rw-r--r--Modules/_testcapimodule.c62
-rw-r--r--Modules/gcmodule.c35
7 files changed, 152 insertions, 7 deletions
diff --git a/Doc/c-api/gcsupport.rst b/Doc/c-api/gcsupport.rst
index eee114c..55ed9d4 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 cdc7160..491a5fb 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 2d8bb28..37c1b8a 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 1408d05..689c42b 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 0000000..015acc9
--- /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 db62aea..26ebacb 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 d6b5142..e5e5aa3 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)