diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index 65533e7e57adc33..90cc3ff23483b6a 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -35,6 +35,22 @@ The :mod:`!gc` module provides the following functions: Return ``True`` if automatic collection is enabled. +.. function:: ensure_disabled() + + Return a context manager that temporarily disables the garbage + collector. The collector is disabled at the start of the ``with`` + block and restored to its previous state on exit:: + + with gc.ensure_disabled(): + ... # GC is disabled during this block + + Nesting is supported — each level saves and restores its own state. + If the collector was already disabled before entering the block, it + remains disabled after exit. + + .. versionadded:: 3.16 + + .. function:: collect(generation=2) With no arguments, run a full collection. The optional argument *generation* diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 4c721c01a34f070..b52664ad32f46d0 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1680,6 +1680,38 @@ def test_threshold_zero(self): self.assertEqual(i, 50001) + def test_ensure_disabled(self): + # gc.ensure_disabled() context manager + self.assertTrue(gc.isenabled()) + with gc.ensure_disabled(): + self.assertFalse(gc.isenabled()) + self.assertTrue(gc.isenabled()) + + def test_ensure_disabled_nesting(self): + self.assertTrue(gc.isenabled()) + with gc.ensure_disabled(): + self.assertFalse(gc.isenabled()) + with gc.ensure_disabled(): + self.assertFalse(gc.isenabled()) + self.assertFalse(gc.isenabled()) + self.assertTrue(gc.isenabled()) + + def test_ensure_disabled_already_disabled(self): + gc.disable() + self.assertFalse(gc.isenabled()) + with gc.ensure_disabled(): + self.assertFalse(gc.isenabled()) + self.assertFalse(gc.isenabled()) + gc.enable() + + def test_ensure_disabled_exception(self): + self.assertTrue(gc.isenabled()) + with self.assertRaises(ValueError): + with gc.ensure_disabled(): + self.assertFalse(gc.isenabled()) + raise ValueError("test") + self.assertTrue(gc.isenabled()) + class PythonFinalizationTests(unittest.TestCase): def test_ast_fini(self): diff --git a/Misc/NEWS.d/next/Library/2026-07-31-15-35-00.gh-issue-75537.852b0b.rst b/Misc/NEWS.d/next/Library/2026-07-31-15-35-00.gh-issue-75537.852b0b.rst new file mode 100644 index 000000000000000..924744c8d3c060d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-31-15-35-00.gh-issue-75537.852b0b.rst @@ -0,0 +1,2 @@ +Add :func:`gc.ensure_disabled` context manager to temporarily disable +the garbage collector. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index e2df31556f3c372..6508e476acbe999 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -65,6 +65,76 @@ gc_isenabled_impl(PyObject *module) return PyGC_IsEnabled(); } + +/* Context manager to temporarily disable the garbage collector. */ + +typedef struct { + PyObject_HEAD + int old_state; +} _gc_ensure_disabled_state; + +static void +_gc_ensure_disabled_dealloc(PyObject *self) +{ + PyObject_Free(self); +} + +static PyObject * +_gc_ensure_disabled_enter(PyObject *self, PyObject *Py_UNUSED(args)) +{ + Py_RETURN_NONE; +} + +static PyObject * +_gc_ensure_disabled_exit(PyObject *self, PyObject *args) +{ + _gc_ensure_disabled_state *s = (_gc_ensure_disabled_state *)self; + if (s->old_state) { + PyGC_Enable(); + } + Py_RETURN_NONE; +} + +static PyMethodDef _gc_ensure_disabled_methods[] = { + {"__enter__", _gc_ensure_disabled_enter, METH_NOARGS, NULL}, + {"__exit__", _gc_ensure_disabled_exit, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject _GCEnsureDisabled_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "gc._ensure_disabled", + .tp_basicsize = sizeof(_gc_ensure_disabled_state), + .tp_dealloc = _gc_ensure_disabled_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = _gc_ensure_disabled_methods, +}; + + +PyDoc_STRVAR(gc_ensure_disabled__doc__, +"ensure_disabled() -> context manager\n" +"\n" +"Context manager to temporarily disable the garbage collector.\n" +"\n" +"At the start of the block the garbage collector is disabled.\n" +"On exit, it is restored to its previous state.\n" +"\n" +"Example:\n" +" with gc.ensure_disabled():\n" +" ... # GC is disabled during this block\n"); + +static PyObject * +gc_ensure_disabled(PyObject *module, PyObject *Py_UNUSED(args)) +{ + _gc_ensure_disabled_state *ctx = PyObject_New( + _gc_ensure_disabled_state, &_GCEnsureDisabled_Type); + if (ctx == NULL) { + return NULL; + } + ctx->old_state = PyGC_Disable(); + return (PyObject *)ctx; +} + /*[clinic input] gc.collect -> Py_ssize_t @@ -521,7 +591,8 @@ PyDoc_STRVAR(gc__doc__, "get_referents() -- Return the list of objects that an object refers to.\n" "freeze() -- Freeze all tracked objects and ignore them for future collections.\n" "unfreeze() -- Unfreeze all objects in the permanent generation.\n" -"get_freeze_count() -- Return the number of objects in the permanent generation.\n"); +"get_freeze_count() -- Return the number of objects in the permanent generation.\n" +"ensure_disabled() -- Context manager to temporarily disable the garbage collector.\n"); static PyMethodDef GcMethods[] = { GC_ENABLE_METHODDEF @@ -542,6 +613,7 @@ static PyMethodDef GcMethods[] = { GC_FREEZE_METHODDEF GC_UNFREEZE_METHODDEF GC_GET_FREEZE_COUNT_METHODDEF + {"ensure_disabled", gc_ensure_disabled, METH_NOARGS, gc_ensure_disabled__doc__}, {NULL, NULL} /* Sentinel */ }; @@ -550,6 +622,10 @@ gcmodule_exec(PyObject *module) { GCState *gcstate = get_gc_state(); + if (PyType_Ready(&_GCEnsureDisabled_Type) < 0) { + return -1; + } + /* garbage and callbacks are initialized by _PyGC_Init() early in * interpreter lifecycle. */ assert(gcstate->garbage != NULL); diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index db575d870be5c53..0d1bb2acc1bc3ab 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -454,3 +454,4 @@ Modules/rotatingtree.c - random_value - Modules/rotatingtree.c - random_mutex - Modules/socketmodule.c - accept4_works - Modules/socketmodule.c - sock_cloexec_works - +Modules/gcmodule.c - _GCEnsureDisabled_Type -