Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Doc/library/gc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :func:`gc.ensure_disabled` context manager to temporarily disable
the garbage collector.
78 changes: 77 additions & 1 deletion Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
SakshamKapoor2911 marked this conversation as resolved.
} _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},
Comment thread
SakshamKapoor2911 marked this conversation as resolved.
{"__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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gc.ensure_disabled() without with would be irreversible?

@SakshamKapoor2911 SakshamKapoor2911 Jul 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct, and consistent with all Python context managers. threading.Lock().acquire() without with also leaves the lock held indefinitely. This is a known limitation: exit only fires via the with statement. Adding a tp_dealloc fallback to re-enable GC on garbage collection would cause surprising behavior (the GC silently re-enabling at an unpredictable time). The with statement is the only supported usage.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maurycy Do you think this is a reasonable tradeoff, or would you suggest I made modifications to prevent this?

"\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

Expand Down Expand Up @@ -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
Expand All @@ -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 */
};

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/globals-to-fix.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -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 -
Loading