Skip to content

Commit af13832

Browse files
[3.15] gh-159739: Release replaced type alias values (GH-160167) (#160242)
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 18dd3f0 commit af13832

3 files changed

Lines changed: 34 additions & 4 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 (
@@ -131,6 +132,24 @@ def more_generic[T, *Ts, **P]():
131132
T, Ts, P = more_generic.__type_params__
132133
self.assertEqual(alias.__value__, tuple[Callable[P, tuple[T, *Ts]], Callable[P2, tuple[T2, *Ts2]]])
133134

135+
def test_reentrant_value_does_not_leak(self):
136+
class Value:
137+
pass
138+
139+
refs = []
140+
141+
def compute():
142+
value = Value()
143+
refs.append(weakref.ref(value))
144+
if len(refs) == 1:
145+
Alias.__value__
146+
return value
147+
148+
type Alias = compute()
149+
self.assertIs(Alias.__value__, refs[0]())
150+
gc_collect()
151+
self.assertIsNone(refs[1]())
152+
134153
def test_subscripting(self):
135154
type NonGeneric = int
136155
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: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,14 +1899,23 @@ typealias_dealloc(PyObject *self)
18991899
static PyObject *
19001900
typealias_get_value(typealiasobject *ta)
19011901
{
1902-
if (ta->value != NULL) {
1903-
return Py_NewRef(ta->value);
1902+
PyObject *result;
1903+
Py_BEGIN_CRITICAL_SECTION(ta);
1904+
result = Py_XNewRef(ta->value);
1905+
Py_END_CRITICAL_SECTION();
1906+
if (result != NULL) {
1907+
return result;
19041908
}
1905-
PyObject *result = PyObject_CallNoArgs(ta->compute_value);
1909+
result = PyObject_CallNoArgs(ta->compute_value);
19061910
if (result == NULL) {
19071911
return NULL;
19081912
}
1913+
PyObject *old_value;
1914+
Py_BEGIN_CRITICAL_SECTION(ta);
1915+
old_value = ta->value;
19091916
ta->value = Py_NewRef(result);
1917+
Py_END_CRITICAL_SECTION();
1918+
Py_XDECREF(old_value);
19101919
return result;
19111920
}
19121921

0 commit comments

Comments
 (0)