diff --git a/Objects/typeobject.c b/Objects/typeobject.c
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -437,7 +437,8 @@
static int mro_internal(PyTypeObject *);
static int compatible_for_assignment(PyTypeObject *, PyTypeObject *, char *);
static int add_subclass(PyTypeObject*, PyTypeObject*);
-static void remove_subclass(PyTypeObject *, PyTypeObject *);
+static void remove_subclass(PyTypeObject *, PyTypeObject *, int exact);
+static void remove_all_subclasses(PyTypeObject *type, PyObject *bases, int exact);
static void update_all_slots(PyTypeObject *);
typedef int (*update_callback)(PyTypeObject *, void *);
@@ -578,14 +579,8 @@
/* for now, sod that: just remove from all old_bases,
add to all new_bases */
- for (i = PyTuple_GET_SIZE(old_bases) - 1; i >= 0; i--) {
- ob = PyTuple_GET_ITEM(old_bases, i);
- if (PyType_Check(ob)) {
- remove_subclass(
- (PyTypeObject*)ob, type);
- }
- }
-
+ remove_all_subclasses(type, old_bases, 1);
+
for (i = PyTuple_GET_SIZE(value) - 1; i >= 0; i--) {
ob = PyTuple_GET_ITEM(value, i);
if (PyType_Check(ob)) {
@@ -2709,6 +2704,11 @@
/* Assert this is a heap-allocated type object */
assert(type->tp_flags & Py_TPFLAGS_HEAPTYPE);
_PyObject_GC_UNTRACK(type);
+ /* call this here before clearing weakrefs. However,
+ * the wearkefs may still be cleared if this is coming
+ * from a gc.collect()
+ */
+ remove_all_subclasses(type, type->tp_bases, 0);
PyObject_ClearWeakRefs((PyObject *)type);
et = (PyHeapTypeObject *)type;
Py_XDECREF(type->tp_base);
@@ -4314,12 +4314,15 @@
return -1;
}
+/* subclasses are kept as weakrefs to avoid creating a reference cycle
+ * between parents and children. Children still manually remove their stale
+ * weakref from the parent when they die.
+ */
static int
add_subclass(PyTypeObject *base, PyTypeObject *type)
{
- Py_ssize_t i;
int result;
- PyObject *list, *ref, *newobj;
+ PyObject *list, *newobj;
list = base->tp_subclasses;
if (list == NULL) {
@@ -4329,23 +4332,24 @@
}
assert(PyList_Check(list));
newobj = PyWeakref_NewRef((PyObject *)type, NULL);
- i = PyList_GET_SIZE(list);
- while (--i >= 0) {
- ref = PyList_GET_ITEM(list, i);
- assert(PyWeakref_CheckRef(ref));
- if (PyWeakref_GET_OBJECT(ref) == Py_None)
- return PyList_SetItem(list, i, newobj);
- }
result = PyList_Append(list, newobj);
Py_DECREF(newobj);
return result;
}
+/* when called with 'exact==1', this function is trying to remove a
+ * non-stale weakref from a parent class, and must therefore not
+ * give up until it finds it.
+ * when 'exact==0', it is only removing stale, or just about to be stale
+ * weakrefs, and therefore it is enough to remove any one it finds.
+ * logic dictates that even a stale weakref will be the correct one,
+ * because it is called for every child class deletion.
+ */
static void
-remove_subclass(PyTypeObject *base, PyTypeObject *type)
+remove_subclass(PyTypeObject *base, PyTypeObject *type, int exact)
{
Py_ssize_t i;
- PyObject *list, *ref;
+ PyObject *list, *ref, *target;
list = base->tp_subclasses;
if (list == NULL) {
@@ -4356,10 +4360,39 @@
while (--i >= 0) {
ref = PyList_GET_ITEM(list, i);
assert(PyWeakref_CheckRef(ref));
- if (PyWeakref_GET_OBJECT(ref) == (PyObject*)type) {
+ target = PyWeakref_GET_OBJECT(ref);
+ if (target == Py_None || target == (PyObject*)type) {
+ /* swap the item with the last one */
+ Py_ssize_t j = PyList_GET_SIZE(list)-1;
+ if (i != j) {
+ PyObject *tmp = PyList_GET_ITEM(list, j);
+ PyList_SET_ITEM(list, j, ref) ;
+ PyList_SET_ITEM(list, i, tmp);
+ }
/* this can't fail, right? */
- PySequence_DelItem(list, i);
- return;
+ PySequence_DelItem(list, j);
+ /* if we are in non-exact mode, then finding
+ * any stale weakref is sufficient.
+ * this avoid visiting the entire list for each
+ * child class deletion. Recent child classes
+ * are expected to be near the end of the list.
+ */
+ if (target == (PyObject*)type || !exact)
+ return;
+ }
+ }
+}
+
+/* see meaning of 'exact' in the above function */
+static void
+remove_all_subclasses(PyTypeObject *type, PyObject *bases, int exact)
+{
+ int i;
+ if (bases) {
+ for(i = 0; i < PyTuple_GET_SIZE(bases); i++) {
+ PyObject *base = PyTuple_GET_ITEM(bases, i);
+ if (PyType_Check(base))
+ remove_subclass((PyTypeObject*)base, type, exact);
}
}
}