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); } } }