mirror of https://github.com/python/cpython
bpo-41984: GC track all user classes (GH-22701)
This commit is contained in:
parent
302b6166fb
commit
c13b847a6f
|
@ -16,6 +16,15 @@ except ImportError:
|
||||||
raise TypeError('requires _testcapi.with_tp_del')
|
raise TypeError('requires _testcapi.with_tp_del')
|
||||||
return C
|
return C
|
||||||
|
|
||||||
|
try:
|
||||||
|
from _testcapi import without_gc
|
||||||
|
except ImportError:
|
||||||
|
def without_gc(cls):
|
||||||
|
class C:
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
raise TypeError('requires _testcapi.without_gc')
|
||||||
|
return C
|
||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,9 +103,11 @@ class SimpleBase(NonGCSimpleBase):
|
||||||
assert self.id_ == id(self)
|
assert self.id_ == id(self)
|
||||||
|
|
||||||
|
|
||||||
|
@without_gc
|
||||||
class NonGC(NonGCSimpleBase):
|
class NonGC(NonGCSimpleBase):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
@without_gc
|
||||||
class NonGCResurrector(NonGCSimpleBase):
|
class NonGCResurrector(NonGCSimpleBase):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
@ -109,8 +120,14 @@ class NonGCResurrector(NonGCSimpleBase):
|
||||||
class Simple(SimpleBase):
|
class Simple(SimpleBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class SimpleResurrector(NonGCResurrector, SimpleBase):
|
# Can't inherit from NonGCResurrector, in case importing without_gc fails.
|
||||||
pass
|
class SimpleResurrector(SimpleBase):
|
||||||
|
|
||||||
|
def side_effect(self):
|
||||||
|
"""
|
||||||
|
Resurrect self by storing self in a class-wide list.
|
||||||
|
"""
|
||||||
|
self.survivors.append(self)
|
||||||
|
|
||||||
|
|
||||||
class TestBase:
|
class TestBase:
|
||||||
|
@ -178,6 +195,7 @@ class SimpleFinalizationTest(TestBase, unittest.TestCase):
|
||||||
self.assert_survivors([])
|
self.assert_survivors([])
|
||||||
self.assertIs(wr(), None)
|
self.assertIs(wr(), None)
|
||||||
|
|
||||||
|
@support.cpython_only
|
||||||
def test_non_gc(self):
|
def test_non_gc(self):
|
||||||
with SimpleBase.test():
|
with SimpleBase.test():
|
||||||
s = NonGC()
|
s = NonGC()
|
||||||
|
@ -191,6 +209,7 @@ class SimpleFinalizationTest(TestBase, unittest.TestCase):
|
||||||
self.assert_del_calls(ids)
|
self.assert_del_calls(ids)
|
||||||
self.assert_survivors([])
|
self.assert_survivors([])
|
||||||
|
|
||||||
|
@support.cpython_only
|
||||||
def test_non_gc_resurrect(self):
|
def test_non_gc_resurrect(self):
|
||||||
with SimpleBase.test():
|
with SimpleBase.test():
|
||||||
s = NonGCResurrector()
|
s = NonGCResurrector()
|
||||||
|
|
|
@ -582,9 +582,9 @@ class GCTests(unittest.TestCase):
|
||||||
self.assertTrue(gc.is_tracked(UserInt()))
|
self.assertTrue(gc.is_tracked(UserInt()))
|
||||||
self.assertTrue(gc.is_tracked([]))
|
self.assertTrue(gc.is_tracked([]))
|
||||||
self.assertTrue(gc.is_tracked(set()))
|
self.assertTrue(gc.is_tracked(set()))
|
||||||
self.assertFalse(gc.is_tracked(UserClassSlots()))
|
self.assertTrue(gc.is_tracked(UserClassSlots()))
|
||||||
self.assertFalse(gc.is_tracked(UserFloatSlots()))
|
self.assertTrue(gc.is_tracked(UserFloatSlots()))
|
||||||
self.assertFalse(gc.is_tracked(UserIntSlots()))
|
self.assertTrue(gc.is_tracked(UserIntSlots()))
|
||||||
|
|
||||||
def test_is_finalized(self):
|
def test_is_finalized(self):
|
||||||
# Objects not tracked by the always gc return false
|
# Objects not tracked by the always gc return false
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
The garbage collector now tracks all user-defined classes. Patch by Brandt
|
||||||
|
Bucher.
|
|
@ -3888,6 +3888,25 @@ with_tp_del(PyObject *self, PyObject *args)
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
without_gc(PyObject *Py_UNUSED(self), PyObject *obj)
|
||||||
|
{
|
||||||
|
PyTypeObject *tp = (PyTypeObject*)obj;
|
||||||
|
if (!PyType_Check(obj) || !PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
|
||||||
|
return PyErr_Format(PyExc_TypeError, "heap type expected, got %R", obj);
|
||||||
|
}
|
||||||
|
if (PyType_IS_GC(tp)) {
|
||||||
|
// Don't try this at home, kids:
|
||||||
|
tp->tp_flags -= Py_TPFLAGS_HAVE_GC;
|
||||||
|
tp->tp_free = PyObject_Del;
|
||||||
|
tp->tp_traverse = NULL;
|
||||||
|
tp->tp_clear = NULL;
|
||||||
|
}
|
||||||
|
assert(!PyType_IS_GC(tp));
|
||||||
|
Py_INCREF(obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef ml;
|
static PyMethodDef ml;
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -5805,6 +5824,7 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL},
|
{"meth_fastcall", (PyCFunction)(void(*)(void))meth_fastcall, METH_FASTCALL},
|
||||||
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS},
|
{"meth_fastcall_keywords", (PyCFunction)(void(*)(void))meth_fastcall_keywords, METH_FASTCALL|METH_KEYWORDS},
|
||||||
{"pynumber_tobase", pynumber_tobase, METH_VARARGS},
|
{"pynumber_tobase", pynumber_tobase, METH_VARARGS},
|
||||||
|
{"without_gc", without_gc, METH_O},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2612,10 +2612,10 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
slots = NULL;
|
slots = NULL;
|
||||||
|
|
||||||
/* Initialize tp_flags */
|
/* Initialize tp_flags */
|
||||||
|
// All heap types need GC, since we can create a reference cycle by storing
|
||||||
|
// an instance on one of its parents:
|
||||||
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
|
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |
|
||||||
Py_TPFLAGS_BASETYPE;
|
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC;
|
||||||
if (base->tp_flags & Py_TPFLAGS_HAVE_GC)
|
|
||||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
|
||||||
|
|
||||||
/* Initialize essential fields */
|
/* Initialize essential fields */
|
||||||
type->tp_as_async = &et->as_async;
|
type->tp_as_async = &et->as_async;
|
||||||
|
@ -2815,21 +2815,11 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
type->tp_dealloc = subtype_dealloc;
|
type->tp_dealloc = subtype_dealloc;
|
||||||
|
|
||||||
/* Enable GC unless this class is not adding new instance variables and
|
|
||||||
the base class did not use GC. */
|
|
||||||
if ((base->tp_flags & Py_TPFLAGS_HAVE_GC) ||
|
|
||||||
type->tp_basicsize > base->tp_basicsize)
|
|
||||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
|
||||||
|
|
||||||
/* Always override allocation strategy to use regular heap */
|
/* Always override allocation strategy to use regular heap */
|
||||||
type->tp_alloc = PyType_GenericAlloc;
|
type->tp_alloc = PyType_GenericAlloc;
|
||||||
if (type->tp_flags & Py_TPFLAGS_HAVE_GC) {
|
|
||||||
type->tp_free = PyObject_GC_Del;
|
type->tp_free = PyObject_GC_Del;
|
||||||
type->tp_traverse = subtype_traverse;
|
type->tp_traverse = subtype_traverse;
|
||||||
type->tp_clear = subtype_clear;
|
type->tp_clear = subtype_clear;
|
||||||
}
|
|
||||||
else
|
|
||||||
type->tp_free = PyObject_Del;
|
|
||||||
|
|
||||||
/* store type in class' cell if one is supplied */
|
/* store type in class' cell if one is supplied */
|
||||||
cell = _PyDict_GetItemIdWithError(dict, &PyId___classcell__);
|
cell = _PyDict_GetItemIdWithError(dict, &PyId___classcell__);
|
||||||
|
|
Loading…
Reference in New Issue