Skip to content

Commit 1097384

Browse files
bpo-44553 : Implement GC methods for types.Union (GH-26993)
1 parent 01331f1 commit 1097384

File tree

3 files changed

+38
-5
lines changed

3 files changed

+38
-5
lines changed

Lib/test/test_types.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Python test set -- part 6, built-in types
22

3-
from test.support import run_with_locale
3+
from test.support import run_with_locale, cpython_only
44
import collections.abc
55
from collections import namedtuple
6+
import gc
67
import inspect
78
import pickle
89
import locale
@@ -756,6 +757,23 @@ def __module__(self):
756757
with self.assertRaises(ZeroDivisionError):
757758
str | TypeVar()
758759

760+
@cpython_only
761+
def test_or_type_operator_reference_cycle(self):
762+
if not hasattr(sys, 'gettotalrefcount'):
763+
self.skipTest('Cannot get total reference count.')
764+
gc.collect()
765+
before = sys.gettotalrefcount()
766+
for _ in range(30):
767+
T = typing.TypeVar('T')
768+
U = int | list[T]
769+
T.blah = U
770+
del T
771+
del U
772+
gc.collect()
773+
leeway = 15
774+
self.assertLessEqual(sys.gettotalrefcount() - before, leeway,
775+
msg='Check for union reference leak.')
776+
759777
def test_ellipsis_type(self):
760778
self.assertIsInstance(Ellipsis, types.EllipsisType)
761779

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement GC methods for ``types.Union`` to break reference cycles and
2+
prevent memory leaks.

Objects/unionobject.c

+17-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// types.Union -- used to represent e.g. Union[int, str], int | str
22
#include "Python.h"
3+
#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK
34
#include "pycore_unionobject.h"
45
#include "structmember.h"
56

@@ -14,10 +15,20 @@ unionobject_dealloc(PyObject *self)
1415
{
1516
unionobject *alias = (unionobject *)self;
1617

18+
_PyObject_GC_UNTRACK(self);
19+
1720
Py_XDECREF(alias->args);
1821
Py_TYPE(self)->tp_free(self);
1922
}
2023

24+
static int
25+
union_traverse(PyObject *self, visitproc visit, void *arg)
26+
{
27+
unionobject *alias = (unionobject *)self;
28+
Py_VISIT(alias->args);
29+
return 0;
30+
}
31+
2132
static Py_hash_t
2233
union_hash(PyObject *self)
2334
{
@@ -437,8 +448,9 @@ PyTypeObject _Py_UnionType = {
437448
.tp_basicsize = sizeof(unionobject),
438449
.tp_dealloc = unionobject_dealloc,
439450
.tp_alloc = PyType_GenericAlloc,
440-
.tp_free = PyObject_Del,
441-
.tp_flags = Py_TPFLAGS_DEFAULT,
451+
.tp_free = PyObject_GC_Del,
452+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
453+
.tp_traverse = union_traverse,
442454
.tp_hash = union_hash,
443455
.tp_getattro = PyObject_GenericGetAttr,
444456
.tp_members = union_members,
@@ -472,15 +484,16 @@ _Py_Union(PyObject *args)
472484
}
473485
}
474486

475-
result = PyObject_New(unionobject, &_Py_UnionType);
487+
result = PyObject_GC_New(unionobject, &_Py_UnionType);
476488
if (result == NULL) {
477489
return NULL;
478490
}
479491

480492
result->args = dedup_and_flatten_args(args);
481493
if (result->args == NULL) {
482-
Py_DECREF(result);
494+
PyObject_GC_Del(result);
483495
return NULL;
484496
}
497+
_PyObject_GC_TRACK(result);
485498
return (PyObject*)result;
486499
}

0 commit comments

Comments
 (0)