Skip to content

Commit 2d26449

Browse files
gh-94673: Always Finalize Static Builtin Types (#95153)
Static builtin types are finalized by calling _PyStaticType_Dealloc(). Before this change, we were skipping finalizing such a type if it still had subtypes (i.e. its tp_subclasses hadn't been cleared yet). The problem is that types hold several heap objects, which leak if we skip the type's finalization. This change addresses that. For context, there's an old comment (from e9e3eab) that says the following: // If a type still has subtypes, it cannot be deallocated. // A subtype can inherit attributes and methods of its parent type, // and a type must no longer be used once it's deallocated. However, it isn't clear that is actually still true. Clearing tp_dict should mean it isn't a problem. Furthermore, the only subtypes that might still be around come from extension modules that didn't clean them up when unloaded (i.e. extensions that do not implement multi-phase initialization, AKA PEP 489). Those objects are already leaking, so this change doesn't change anything in that regard. Instead, this change means more objects gets cleaned up that before.
1 parent a15ae19 commit 2d26449

File tree

2 files changed

+82
-24
lines changed

2 files changed

+82
-24
lines changed

Objects/typeobject.c

+80-23
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound);
6565
static int
6666
slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
6767

68+
static inline PyTypeObject * subclass_from_ref(PyObject *ref);
69+
6870
/*
6971
* finds the beginning of the docstring's introspection signature.
7072
* if present, returns a pointer pointing to the first '('.
@@ -309,12 +311,11 @@ PyType_Modified(PyTypeObject *type)
309311
Py_ssize_t i = 0;
310312
PyObject *ref;
311313
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
312-
assert(PyWeakref_CheckRef(ref));
313-
PyObject *obj = PyWeakref_GET_OBJECT(ref);
314-
if (obj == Py_None) {
314+
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
315+
if (subclass == NULL) {
315316
continue;
316317
}
317-
PyType_Modified(_PyType_CAST(obj));
318+
PyType_Modified(subclass);
318319
}
319320
}
320321

@@ -4211,23 +4212,48 @@ type_dealloc_common(PyTypeObject *type)
42114212
}
42124213

42134214

4214-
void
4215-
_PyStaticType_Dealloc(PyTypeObject *type)
4215+
static void
4216+
clear_static_tp_subclasses(PyTypeObject *type)
42164217
{
4217-
// If a type still has subtypes, it cannot be deallocated.
4218-
// A subtype can inherit attributes and methods of its parent type,
4219-
// and a type must no longer be used once it's deallocated.
4220-
if (type->tp_subclasses != NULL) {
4218+
if (type->tp_subclasses == NULL) {
42214219
return;
42224220
}
42234221

4222+
/* Normally it would be a problem to finalize the type if its
4223+
tp_subclasses wasn't cleared first. However, this is only
4224+
ever called at the end of runtime finalization, so we can be
4225+
more liberal in cleaning up. If the given type still has
4226+
subtypes at this point then some extension module did not
4227+
correctly finalize its objects.
4228+
4229+
We can safely obliterate such subtypes since the extension
4230+
module and its objects won't be used again, except maybe if
4231+
the runtime were re-initialized. In that case the sticky
4232+
situation would only happen if the module were re-imported
4233+
then and only if the subtype were stored in a global and only
4234+
if that global were not overwritten during import. We'd be
4235+
fine since the extension is otherwise unsafe and unsupported
4236+
in that situation, and likely problematic already.
4237+
4238+
In any case, this situation means at least some memory is
4239+
going to leak. This mostly only affects embedding scenarios.
4240+
*/
4241+
4242+
// For now we just clear tp_subclasses.
4243+
4244+
Py_CLEAR(type->tp_subclasses);
4245+
}
4246+
4247+
void
4248+
_PyStaticType_Dealloc(PyTypeObject *type)
4249+
{
42244250
type_dealloc_common(type);
42254251

42264252
Py_CLEAR(type->tp_dict);
42274253
Py_CLEAR(type->tp_bases);
42284254
Py_CLEAR(type->tp_mro);
42294255
Py_CLEAR(type->tp_cache);
4230-
// type->tp_subclasses is NULL
4256+
clear_static_tp_subclasses(type);
42314257

42324258
// PyObject_ClearWeakRefs() raises an exception if Py_REFCNT() != 0
42334259
if (Py_REFCNT(type) == 0) {
@@ -4296,14 +4322,12 @@ _PyType_GetSubclasses(PyTypeObject *self)
42964322
Py_ssize_t i = 0;
42974323
PyObject *ref; // borrowed ref
42984324
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
4299-
assert(PyWeakref_CheckRef(ref));
4300-
PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref
4301-
if (obj == Py_None) {
4325+
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
4326+
if (subclass == NULL) {
43024327
continue;
43034328
}
4304-
assert(PyType_Check(obj));
43054329

4306-
if (PyList_Append(list, obj) < 0) {
4330+
if (PyList_Append(list, _PyObject_CAST(subclass)) < 0) {
43074331
Py_DECREF(list);
43084332
return NULL;
43094333
}
@@ -6708,6 +6732,42 @@ add_all_subclasses(PyTypeObject *type, PyObject *bases)
67086732
return res;
67096733
}
67106734

6735+
static inline PyTypeObject *
6736+
subclass_from_ref(PyObject *ref)
6737+
{
6738+
assert(PyWeakref_CheckRef(ref));
6739+
PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref
6740+
assert(obj != NULL);
6741+
if (obj == Py_None) {
6742+
return NULL;
6743+
}
6744+
assert(PyType_Check(obj));
6745+
return _PyType_CAST(obj);
6746+
}
6747+
6748+
static PyObject *
6749+
get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
6750+
{
6751+
PyObject *key = PyLong_FromVoidPtr((void *) type);
6752+
if (key != NULL) {
6753+
return key;
6754+
}
6755+
PyErr_Clear();
6756+
6757+
/* This basically means we're out of memory.
6758+
We fall back to manually traversing the values. */
6759+
Py_ssize_t i = 0;
6760+
PyObject *ref; // borrowed ref
6761+
while (PyDict_Next((PyObject *)base->tp_subclasses, &i, &key, &ref)) {
6762+
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
6763+
if (subclass == type) {
6764+
return Py_NewRef(key);
6765+
}
6766+
}
6767+
/* It wasn't found. */
6768+
return NULL;
6769+
}
6770+
67116771
static void
67126772
remove_subclass(PyTypeObject *base, PyTypeObject *type)
67136773
{
@@ -6717,8 +6777,8 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
67176777
}
67186778
assert(PyDict_CheckExact(subclasses));
67196779

6720-
PyObject *key = PyLong_FromVoidPtr((void *) type);
6721-
if (key == NULL || PyDict_DelItem(subclasses, key)) {
6780+
PyObject *key = get_subclasses_key(type, base);
6781+
if (key != NULL && PyDict_DelItem(subclasses, key)) {
67226782
/* This can happen if the type initialization errored out before
67236783
the base subclasses were updated (e.g. a non-str __qualname__
67246784
was passed in the type dict). */
@@ -8811,13 +8871,10 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
88118871
Py_ssize_t i = 0;
88128872
PyObject *ref;
88138873
while (PyDict_Next(subclasses, &i, NULL, &ref)) {
8814-
assert(PyWeakref_CheckRef(ref));
8815-
PyObject *obj = PyWeakref_GET_OBJECT(ref);
8816-
assert(obj != NULL);
8817-
if (obj == Py_None) {
8874+
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
8875+
if (subclass == NULL) {
88188876
continue;
88198877
}
8820-
PyTypeObject *subclass = _PyType_CAST(obj);
88218878

88228879
/* Avoid recursing down into unaffected classes */
88238880
PyObject *dict = subclass->tp_dict;

Python/pylifecycle.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -1672,9 +1672,10 @@ finalize_interp_types(PyInterpreterState *interp)
16721672
_PyLong_FiniTypes(interp);
16731673
_PyThread_FiniType(interp);
16741674
_PyErr_FiniTypes(interp);
1675-
_PyTypes_Fini(interp);
16761675
_PyTypes_FiniTypes(interp);
16771676

1677+
_PyTypes_Fini(interp);
1678+
16781679
// Call _PyUnicode_ClearInterned() before _PyDict_Fini() since it uses
16791680
// a dict internally.
16801681
_PyUnicode_ClearInterned(interp);

0 commit comments

Comments
 (0)