[Python-checkins] bpo-44553 : Implement GC methods for types.Union (GH-26993)

serhiy-storchaka webhook-mailer at python.org
Sat Jul 3 08:12:15 EDT 2021


https://github.com/python/cpython/commit/1097384ce964dd63686b1aac706cd0fa764c2dc9
commit: 1097384ce964dd63686b1aac706cd0fa764c2dc9
branch: main
author: Ken Jin <28750310+Fidget-Spinner at users.noreply.github.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2021-07-03T15:12:11+03:00
summary:

bpo-44553 : Implement GC methods for types.Union (GH-26993)

files:
A Misc/NEWS.d/next/Core and Builtins/2021-07-02-22-54-41.bpo-44553.l9YqGg.rst
M Lib/test/test_types.py
M Objects/unionobject.c

diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index d3e315a196b51..ae7b17bd590e6 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -1,8 +1,9 @@
 # Python test set -- part 6, built-in types
 
-from test.support import run_with_locale
+from test.support import run_with_locale, cpython_only
 import collections.abc
 from collections import namedtuple
+import gc
 import inspect
 import pickle
 import locale
@@ -756,6 +757,23 @@ def __module__(self):
         with self.assertRaises(ZeroDivisionError):
             str | TypeVar()
 
+    @cpython_only
+    def test_or_type_operator_reference_cycle(self):
+        if not hasattr(sys, 'gettotalrefcount'):
+            self.skipTest('Cannot get total reference count.')
+        gc.collect()
+        before = sys.gettotalrefcount()
+        for _ in range(30):
+            T = typing.TypeVar('T')
+            U = int | list[T]
+            T.blah = U
+            del T
+            del U
+        gc.collect()
+        leeway = 15
+        self.assertLessEqual(sys.gettotalrefcount() - before, leeway,
+                             msg='Check for union reference leak.')
+
     def test_ellipsis_type(self):
         self.assertIsInstance(Ellipsis, types.EllipsisType)
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-02-22-54-41.bpo-44553.l9YqGg.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-02-22-54-41.bpo-44553.l9YqGg.rst
new file mode 100644
index 0000000000000..e97df02994806
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-02-22-54-41.bpo-44553.l9YqGg.rst	
@@ -0,0 +1,2 @@
+Implement GC methods for ``types.Union`` to break reference cycles and
+prevent memory leaks.
diff --git a/Objects/unionobject.c b/Objects/unionobject.c
index a66d61524dcfc..cc7181d2475ca 100644
--- a/Objects/unionobject.c
+++ b/Objects/unionobject.c
@@ -1,5 +1,6 @@
 // types.Union -- used to represent e.g. Union[int, str], int | str
 #include "Python.h"
+#include "pycore_object.h"  // _PyObject_GC_TRACK/UNTRACK
 #include "pycore_unionobject.h"
 #include "structmember.h"
 
@@ -14,10 +15,20 @@ unionobject_dealloc(PyObject *self)
 {
     unionobject *alias = (unionobject *)self;
 
+    _PyObject_GC_UNTRACK(self);
+
     Py_XDECREF(alias->args);
     Py_TYPE(self)->tp_free(self);
 }
 
+static int
+union_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    unionobject *alias = (unionobject *)self;
+    Py_VISIT(alias->args);
+    return 0;
+}
+
 static Py_hash_t
 union_hash(PyObject *self)
 {
@@ -437,8 +448,9 @@ PyTypeObject _Py_UnionType = {
     .tp_basicsize = sizeof(unionobject),
     .tp_dealloc = unionobject_dealloc,
     .tp_alloc = PyType_GenericAlloc,
-    .tp_free = PyObject_Del,
-    .tp_flags = Py_TPFLAGS_DEFAULT,
+    .tp_free = PyObject_GC_Del,
+    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    .tp_traverse = union_traverse,
     .tp_hash = union_hash,
     .tp_getattro = PyObject_GenericGetAttr,
     .tp_members = union_members,
@@ -472,15 +484,16 @@ _Py_Union(PyObject *args)
         }
     }
 
-    result = PyObject_New(unionobject, &_Py_UnionType);
+    result = PyObject_GC_New(unionobject, &_Py_UnionType);
     if (result == NULL) {
         return NULL;
     }
 
     result->args = dedup_and_flatten_args(args);
     if (result->args == NULL) {
-        Py_DECREF(result);
+        PyObject_GC_Del(result);
         return NULL;
     }
+    _PyObject_GC_TRACK(result);
     return (PyObject*)result;
 }



More information about the Python-checkins mailing list