mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	bpo-31356: Add context manager to temporarily disable GC (GH-4224)
This commit is contained in:
		
							parent
							
								
									0cd6bca655
								
							
						
					
					
						commit
						72a0d218dc
					
				
					 5 changed files with 208 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -33,6 +33,34 @@ The :mod:`gc` module provides the following functions:
 | 
			
		|||
   Disable automatic garbage collection.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. class:: ensure_disabled()
 | 
			
		||||
 | 
			
		||||
   Return a context manager object that disables the garbage collector and reenables the previous
 | 
			
		||||
   state upon completion of the block. This is basically equivalent to::
 | 
			
		||||
 | 
			
		||||
     from gc import enable, disable, isenabled
 | 
			
		||||
 | 
			
		||||
     @contextmanager
 | 
			
		||||
     def ensure_disabled():
 | 
			
		||||
         was_enabled_previously = isenabled()
 | 
			
		||||
         gc.disable()
 | 
			
		||||
         yield
 | 
			
		||||
         if was_enabled_previously:
 | 
			
		||||
             gc.enable()
 | 
			
		||||
 | 
			
		||||
   And lets you write code like this::
 | 
			
		||||
 | 
			
		||||
     with ensure_disabled():
 | 
			
		||||
         run_some_timing()
 | 
			
		||||
 | 
			
		||||
     with ensure_disabled():
 | 
			
		||||
         # do_something_that_has_real_time_guarantees
 | 
			
		||||
         # such as a pair trade, robotic braking, etc
 | 
			
		||||
 | 
			
		||||
   without needing to explicitly enable and disable the garbage collector yourself.
 | 
			
		||||
   This context manager is implemented in C to assure atomicity, thread safety and speed.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. function:: isenabled()
 | 
			
		||||
 | 
			
		||||
   Returns true if automatic collection is enabled.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -116,6 +116,7 @@ struct _gc_runtime_state {
 | 
			
		|||
 | 
			
		||||
    int enabled;
 | 
			
		||||
    int debug;
 | 
			
		||||
    long disabled_threads;
 | 
			
		||||
    /* linked lists of container objects */
 | 
			
		||||
    struct gc_generation generations[NUM_GENERATIONS];
 | 
			
		||||
    PyGC_Head *generation0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import unittest
 | 
			
		||||
from test.support import (verbose, refcount_test, run_unittest,
 | 
			
		||||
                          strip_python_stderr, cpython_only, start_threads,
 | 
			
		||||
                          temp_dir, requires_type_collecting)
 | 
			
		||||
                          temp_dir, requires_type_collecting,reap_threads)
 | 
			
		||||
from test.support.script_helper import assert_python_ok, make_script
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,8 @@
 | 
			
		|||
import gc
 | 
			
		||||
import weakref
 | 
			
		||||
import threading
 | 
			
		||||
import warnings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from _testcapi import with_tp_del
 | 
			
		||||
| 
						 | 
				
			
			@ -1007,6 +1009,73 @@ def __del__(self):
 | 
			
		|||
            # empty __dict__.
 | 
			
		||||
            self.assertEqual(x, None)
 | 
			
		||||
 | 
			
		||||
    def test_ensure_disabled(self):
 | 
			
		||||
        original_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        with gc.ensure_disabled():
 | 
			
		||||
            inside_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        after_status = gc.isenabled()
 | 
			
		||||
        self.assertEqual(original_status, True)
 | 
			
		||||
        self.assertEqual(inside_status, False)
 | 
			
		||||
        self.assertEqual(after_status, True)
 | 
			
		||||
 | 
			
		||||
    def test_ensure_disabled_with_gc_disabled(self):
 | 
			
		||||
        gc.disable()
 | 
			
		||||
 | 
			
		||||
        original_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        with gc.ensure_disabled():
 | 
			
		||||
            inside_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        after_status = gc.isenabled()
 | 
			
		||||
        self.assertEqual(original_status, False)
 | 
			
		||||
        self.assertEqual(inside_status, False)
 | 
			
		||||
        self.assertEqual(after_status, False)
 | 
			
		||||
 | 
			
		||||
    @reap_threads
 | 
			
		||||
    def test_ensure_disabled_thread(self):
 | 
			
		||||
 | 
			
		||||
        thread_original_status = None
 | 
			
		||||
        thread_inside_status = None
 | 
			
		||||
        thread_after_status = None
 | 
			
		||||
 | 
			
		||||
        def disabling_thread():
 | 
			
		||||
            nonlocal thread_original_status
 | 
			
		||||
            nonlocal thread_inside_status
 | 
			
		||||
            nonlocal thread_after_status
 | 
			
		||||
            thread_original_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
            with gc.ensure_disabled():
 | 
			
		||||
                time.sleep(0.01)
 | 
			
		||||
                thread_inside_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
            thread_after_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        original_status = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        with warnings.catch_warnings(record=True) as w, gc.ensure_disabled():
 | 
			
		||||
            inside_status_before_thread = gc.isenabled()
 | 
			
		||||
            thread = threading.Thread(target=disabling_thread)
 | 
			
		||||
            thread.start()
 | 
			
		||||
            inside_status_after_thread = gc.isenabled()
 | 
			
		||||
 | 
			
		||||
        after_status = gc.isenabled()
 | 
			
		||||
        thread.join()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(len(w), 1)
 | 
			
		||||
        self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
 | 
			
		||||
        self.assertEqual("Garbage collector enabled while another thread is "
 | 
			
		||||
                         "inside gc.ensure_enabled", str(w[-1].message))
 | 
			
		||||
        self.assertEqual(original_status, True)
 | 
			
		||||
        self.assertEqual(inside_status_before_thread, False)
 | 
			
		||||
        self.assertEqual(thread_original_status, False)
 | 
			
		||||
        self.assertEqual(thread_inside_status, True)
 | 
			
		||||
        self.assertEqual(thread_after_status, False)
 | 
			
		||||
        self.assertEqual(inside_status_after_thread, False)
 | 
			
		||||
        self.assertEqual(after_status, True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_main():
 | 
			
		||||
    enabled = gc.isenabled()
 | 
			
		||||
    gc.disable()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
Add a new contextmanager to the gc module that temporarily disables the GC
 | 
			
		||||
and restores the previous state. The implementation is done in C to assure
 | 
			
		||||
atomicity and speed.
 | 
			
		||||
| 
						 | 
				
			
			@ -1067,6 +1067,10 @@ static PyObject *
 | 
			
		|||
gc_enable_impl(PyObject *module)
 | 
			
		||||
/*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/
 | 
			
		||||
{
 | 
			
		||||
    if(_PyRuntime.gc.disabled_threads){
 | 
			
		||||
        PyErr_WarnEx(PyExc_RuntimeWarning, "Garbage collector enabled while another "
 | 
			
		||||
            "thread is inside gc.ensure_enabled",1);
 | 
			
		||||
    }
 | 
			
		||||
    _PyRuntime.gc.enabled = 1;
 | 
			
		||||
    Py_RETURN_NONE;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1508,6 +1512,102 @@ static PyMethodDef GcMethods[] = {
 | 
			
		|||
    {NULL,      NULL}           /* Sentinel */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    PyObject_HEAD
 | 
			
		||||
    int previous_gc_state;
 | 
			
		||||
} ensure_disabled_object;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
ensure_disabled_object_dealloc(ensure_disabled_object *m_obj)
 | 
			
		||||
{
 | 
			
		||||
    Py_TYPE(m_obj)->tp_free((PyObject*)m_obj);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
ensure_disabled__enter__method(ensure_disabled_object *self, PyObject *args)
 | 
			
		||||
{
 | 
			
		||||
    PyGILState_STATE gstate = PyGILState_Ensure();
 | 
			
		||||
    ++_PyRuntime.gc.disabled_threads;
 | 
			
		||||
    self->previous_gc_state = _PyRuntime.gc.enabled;
 | 
			
		||||
    gc_disable_impl(NULL);
 | 
			
		||||
    PyGILState_Release(gstate);
 | 
			
		||||
    Py_RETURN_NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
ensure_disabled__exit__method(ensure_disabled_object *self, PyObject *args)
 | 
			
		||||
{
 | 
			
		||||
    PyGILState_STATE gstate = PyGILState_Ensure();
 | 
			
		||||
    --_PyRuntime.gc.disabled_threads;
 | 
			
		||||
    if(self->previous_gc_state){
 | 
			
		||||
        gc_enable_impl(NULL);
 | 
			
		||||
    }else{
 | 
			
		||||
        gc_disable_impl(NULL);
 | 
			
		||||
    }
 | 
			
		||||
    PyGILState_Release(gstate);
 | 
			
		||||
    Py_RETURN_NONE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct PyMethodDef ensure_disabled_object_methods[] = {
 | 
			
		||||
    {"__enter__",       (PyCFunction) ensure_disabled__enter__method,      METH_NOARGS},
 | 
			
		||||
    {"__exit__",        (PyCFunction) ensure_disabled__exit__method,       METH_VARARGS},
 | 
			
		||||
    {NULL,         NULL}       /* sentinel */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
new_disabled_obj(PyTypeObject *type, PyObject *args, PyObject *kwdict){
 | 
			
		||||
    ensure_disabled_object *self;
 | 
			
		||||
    self = (ensure_disabled_object *)type->tp_alloc(type, 0);
 | 
			
		||||
    return (PyObject *) self;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static PyTypeObject gc_ensure_disabled_type = {
 | 
			
		||||
    PyVarObject_HEAD_INIT(NULL, 0)
 | 
			
		||||
    "gc.ensure_disabled",                       /* tp_name */
 | 
			
		||||
    sizeof(ensure_disabled_object),             /* tp_size */
 | 
			
		||||
    0,                                          /* tp_itemsize */
 | 
			
		||||
    /* methods */
 | 
			
		||||
    (destructor) ensure_disabled_object_dealloc,/* tp_dealloc */
 | 
			
		||||
    0,                                          /* tp_print */
 | 
			
		||||
    0,                                          /* tp_getattr */
 | 
			
		||||
    0,                                          /* tp_setattr */
 | 
			
		||||
    0,                                          /* tp_reserved */
 | 
			
		||||
    0,                                          /* tp_repr */
 | 
			
		||||
    0,                                          /* tp_as_number */
 | 
			
		||||
    0,                                          /*tp_as_sequence*/
 | 
			
		||||
    0,                                          /*tp_as_mapping*/
 | 
			
		||||
    0,                                          /*tp_hash*/
 | 
			
		||||
    0,                                          /*tp_call*/
 | 
			
		||||
    0,                                          /*tp_str*/
 | 
			
		||||
    PyObject_GenericGetAttr,                    /*tp_getattro*/
 | 
			
		||||
    0,                                          /*tp_setattro*/
 | 
			
		||||
    0,                                          /*tp_as_buffer*/
 | 
			
		||||
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /*tp_flags*/
 | 
			
		||||
    0,                                          /*tp_doc*/
 | 
			
		||||
    0,                                          /* tp_traverse */
 | 
			
		||||
    0,                                          /* tp_clear */
 | 
			
		||||
    0,                                          /* tp_richcompare */
 | 
			
		||||
    0,                                          /* tp_weaklistoffset */
 | 
			
		||||
    0,                                          /* tp_iter */
 | 
			
		||||
    0,                                          /* tp_iternext */
 | 
			
		||||
    ensure_disabled_object_methods,             /* tp_methods */
 | 
			
		||||
    0,                                          /* tp_members */
 | 
			
		||||
    0,                                          /* tp_getset */
 | 
			
		||||
    0,                                          /* tp_base */
 | 
			
		||||
    0,                                          /* tp_dict */
 | 
			
		||||
    0,                                          /* tp_descr_get */
 | 
			
		||||
    0,                                          /* tp_descr_set */
 | 
			
		||||
    0,                                          /* tp_dictoffset */
 | 
			
		||||
    0,                                          /* tp_init */
 | 
			
		||||
    PyType_GenericAlloc,                        /* tp_alloc */
 | 
			
		||||
    new_disabled_obj,                           /* tp_new */
 | 
			
		||||
    PyObject_Del,                               /* tp_free */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static struct PyModuleDef gcmodule = {
 | 
			
		||||
    PyModuleDef_HEAD_INIT,
 | 
			
		||||
    "gc",              /* m_name */
 | 
			
		||||
| 
						 | 
				
			
			@ -1548,6 +1648,12 @@ PyInit_gc(void)
 | 
			
		|||
    if (PyModule_AddObject(m, "callbacks", _PyRuntime.gc.callbacks) < 0)
 | 
			
		||||
        return NULL;
 | 
			
		||||
 | 
			
		||||
    if (PyType_Ready(&gc_ensure_disabled_type) < 0)
 | 
			
		||||
        return NULL;
 | 
			
		||||
    if (PyModule_AddObject(m, "ensure_disabled", (PyObject*) &gc_ensure_disabled_type) < 0)
 | 
			
		||||
        return NULL;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define ADD_INT(NAME) if (PyModule_AddIntConstant(m, #NAME, NAME) < 0) return NULL
 | 
			
		||||
    ADD_INT(DEBUG_STATS);
 | 
			
		||||
    ADD_INT(DEBUG_COLLECTABLE);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue