mirror of https://github.com/python/cpython
bpo-46417: remove_subclass() clears tp_subclasses (GH-30793)
The remove_subclass() function now deletes the dictionary when removing the last subclass (if the dictionary becomes empty) to save memory: set PyTypeObject.tp_subclasses to NULL. remove_subclass() is called when a type is deallocated. _PyType_GetSubclasses() no longer holds a reference to tp_subclasses: its loop cannot modify tp_subclasses.
This commit is contained in:
parent
f1c6ae3270
commit
2d03b73cc9
|
@ -4923,6 +4923,23 @@ order (MRO) for bases """
|
||||||
cls.lst = [2**i for i in range(10000)]
|
cls.lst = [2**i for i in range(10000)]
|
||||||
X.descr
|
X.descr
|
||||||
|
|
||||||
|
def test_remove_subclass(self):
|
||||||
|
# bpo-46417: when the last subclass of a type is deleted,
|
||||||
|
# remove_subclass() clears the internal dictionary of subclasses:
|
||||||
|
# set PyTypeObject.tp_subclasses to NULL. remove_subclass() is called
|
||||||
|
# when a type is deallocated.
|
||||||
|
class Parent:
|
||||||
|
pass
|
||||||
|
self.assertEqual(Parent.__subclasses__(), [])
|
||||||
|
|
||||||
|
class Child(Parent):
|
||||||
|
pass
|
||||||
|
self.assertEqual(Parent.__subclasses__(), [Child])
|
||||||
|
|
||||||
|
del Child
|
||||||
|
gc.collect()
|
||||||
|
self.assertEqual(Parent.__subclasses__(), [])
|
||||||
|
|
||||||
|
|
||||||
class DictProxyTests(unittest.TestCase):
|
class DictProxyTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -4137,16 +4137,17 @@ _PyType_GetSubclasses(PyTypeObject *self)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hold a strong reference to tp_subclasses while iterating on it
|
PyObject *subclasses = self->tp_subclasses; // borrowed ref
|
||||||
PyObject *dict = Py_XNewRef(self->tp_subclasses);
|
if (subclasses == NULL) {
|
||||||
if (dict == NULL) {
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
assert(PyDict_CheckExact(dict));
|
assert(PyDict_CheckExact(subclasses));
|
||||||
|
// The loop cannot modify tp_subclasses, there is no need
|
||||||
|
// to hold a strong reference (use a borrowed reference).
|
||||||
|
|
||||||
Py_ssize_t i = 0;
|
Py_ssize_t i = 0;
|
||||||
PyObject *ref; // borrowed ref
|
PyObject *ref; // borrowed ref
|
||||||
while (PyDict_Next(dict, &i, NULL, &ref)) {
|
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
|
||||||
assert(PyWeakref_CheckRef(ref));
|
assert(PyWeakref_CheckRef(ref));
|
||||||
PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref
|
PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref
|
||||||
if (obj == Py_None) {
|
if (obj == Py_None) {
|
||||||
|
@ -4154,12 +4155,10 @@ _PyType_GetSubclasses(PyTypeObject *self)
|
||||||
}
|
}
|
||||||
assert(PyType_Check(obj));
|
assert(PyType_Check(obj));
|
||||||
if (PyList_Append(list, obj) < 0) {
|
if (PyList_Append(list, obj) < 0) {
|
||||||
Py_CLEAR(list);
|
Py_DECREF(list);
|
||||||
goto done;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done:
|
|
||||||
Py_DECREF(dict);
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6568,6 +6567,13 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
Py_XDECREF(key);
|
Py_XDECREF(key);
|
||||||
|
|
||||||
|
if (PyDict_Size(dict) == 0) {
|
||||||
|
// Delete the dictionary to save memory. _PyStaticType_Dealloc()
|
||||||
|
// callers also test if tp_subclasses is NULL to check if a static type
|
||||||
|
// has no subclass.
|
||||||
|
Py_CLEAR(base->tp_subclasses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
Loading…
Reference in New Issue