Skip to content

Commit ef844c8

Browse files
[3.14] gh-159739: Release replaced type alias values (GH-160167) (#160243)
A type alias can be evaluated again before an earlier evaluation finishes, either recursively or in another thread. The later completion overwrites the cached value without releasing it, permanently leaking that value; synchronize cache access and release the replaced value outside the lock. Backport to 3.15 and 3.14. (cherry picked from commit d36a3f6) Co-authored-by: Jelle Zijlstra <jelle@openai.com>
1 parent cd04626 commit ef844c8

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

‎Lib/test/test_type_aliases.py‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import pickle
22
import types
33
import unittest
4-
from test.support import check_syntax_error, run_code
4+
import weakref
5+
from test.support import check_syntax_error, gc_collect, run_code
56
from test.typinganndata import mod_generics_cache
67

78
from typing import (
@@ -124,6 +125,24 @@ def more_generic[T, *Ts, **P]():
124125
T, Ts, P = more_generic.__type_params__
125126
self.assertEqual(alias.__value__, tuple[Callable[P, tuple[T, *Ts]], Callable[P2, tuple[T2, *Ts2]]])
126127

128+
def test_reentrant_value_does_not_leak(self):
129+
class Value:
130+
pass
131+
132+
refs = []
133+
134+
def compute():
135+
value = Value()
136+
refs.append(weakref.ref(value))
137+
if len(refs) == 1:
138+
Alias.__value__
139+
return value
140+
141+
type Alias = compute()
142+
self.assertIs(Alias.__value__, refs[0]())
143+
gc_collect()
144+
self.assertIsNone(refs[1]())
145+
127146
def test_subscripting(self):
128147
type NonGeneric = int
129148
type Generic[A] = dict[A, A]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a reference leak when the value of a type alias is evaluated
2+
recursively or concurrently.

‎Objects/typevarobject.c‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,14 +1858,23 @@ typealias_dealloc(PyObject *self)
18581858
static PyObject *
18591859
typealias_get_value(typealiasobject *ta)
18601860
{
1861-
if (ta->value != NULL) {
1862-
return Py_NewRef(ta->value);
1863-
}
1864-
PyObject *result = PyObject_CallNoArgs(ta->compute_value);
1861+
PyObject *result;
1862+
Py_BEGIN_CRITICAL_SECTION(ta);
1863+
result = Py_XNewRef(ta->value);
1864+
Py_END_CRITICAL_SECTION();
1865+
if (result != NULL) {
1866+
return result;
1867+
}
1868+
result = PyObject_CallNoArgs(ta->compute_value);
18651869
if (result == NULL) {
18661870
return NULL;
18671871
}
1872+
PyObject *old_value;
1873+
Py_BEGIN_CRITICAL_SECTION(ta);
1874+
old_value = ta->value;
18681875
ta->value = Py_NewRef(result);
1876+
Py_END_CRITICAL_SECTION();
1877+
Py_XDECREF(old_value);
18691878
return result;
18701879
}
18711880

0 commit comments

Comments
 (0)