https://github.com/python/cpython/commit/44e92d4a5922dd8fa61f75834784c2772fdd34ab
commit: 44e92d4a5922dd8fa61f75834784c2772fdd34ab
branch: main
author: Neil Schemenauer <[email protected]>
committer: nascheme <[email protected]>
date: 2026-08-11T09:48:19-07:00
summary:

gh-155363: Fix QSBR slot leak on thread state creation failure (gh-155365)

In the free-threaded build, new_threadstate() reserves a QSBR thread-state
slot before it can still fail for other reasons, but the failure paths only
called free_threadstate(), which does not know about the reservation.
Restructure code so failure path doesn't leak.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-13-40-12.gh-issue-155363.Qk3Vt9.rst
M Include/internal/pycore_code.h
M Objects/codeobject.c
M Python/pystate.c

diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h
index 5b1fddbe15b98b8..32242f89b812e69 100644
--- a/Include/internal/pycore_code.h
+++ b/Include/internal/pycore_code.h
@@ -582,6 +582,10 @@ PyAPI_FUNC(_Py_CODEUNIT *) _PyCode_GetTLBC(PyCodeObject 
*co);
 // Returns the reserved index or -1 on error.
 extern int32_t _Py_ReserveTLBCIndex(PyInterpreterState *interp);
 
+// Release an index returned by _Py_ReserveTLBCIndex() that was never stored
+// in a PyThreadState.
+extern void _Py_UnreserveTLBCIndex(PyInterpreterState *interp, int32_t index);
+
 // Release the current thread's index into thread-local bytecode arrays
 extern void _Py_ClearTLBCIndex(_PyThreadStateImpl *tstate);
 
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-13-40-12.gh-issue-155363.Qk3Vt9.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-13-40-12.gh-issue-155363.Qk3Vt9.rst
new file mode 100644
index 000000000000000..52200bb9d2fd59f
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-13-40-12.gh-issue-155363.Qk3Vt9.rst
@@ -0,0 +1,4 @@
+Fix a leak in the :term:`free-threaded build` when creating a thread state
+fails after an internal QSBR slot has been reserved for it.  The slot could
+never be reclaimed, so the QSBR array grew without bound across repeated
+failures.
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index d7955cc7390a7ab..58811d63c7e318c 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -3314,14 +3314,20 @@ _Py_ReserveTLBCIndex(PyInterpreterState *interp)
 }
 
 void
-_Py_ClearTLBCIndex(_PyThreadStateImpl *tstate)
+_Py_UnreserveTLBCIndex(PyInterpreterState *interp, int32_t index)
 {
-    PyInterpreterState *interp = ((PyThreadState *)tstate)->interp;
     if (interp->config.tlbc_enabled) {
-        _PyIndexPool_FreeIndex(&interp->tlbc_indices, tstate->tlbc_index);
+        _PyIndexPool_FreeIndex(&interp->tlbc_indices, index);
     }
 }
 
+void
+_Py_ClearTLBCIndex(_PyThreadStateImpl *tstate)
+{
+    PyInterpreterState *interp = ((PyThreadState *)tstate)->interp;
+    _Py_UnreserveTLBCIndex(interp, tstate->tlbc_index);
+}
+
 static _PyCodeArray *
 _PyCodeArray_New(Py_ssize_t size)
 {
diff --git a/Python/pystate.c b/Python/pystate.c
index d10b38def32911d..646c157007d4ac1 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -1667,21 +1667,23 @@ new_threadstate(PyInterpreterState *interp, int whence)
         return NULL;
     }
 
-#ifdef Py_GIL_DISABLED
-    Py_ssize_t qsbr_idx = _Py_qsbr_reserve(interp);
-    if (qsbr_idx < 0) {
+#ifdef Py_STATS
+    // The PyStats structure is quite large and is allocated separated from
+    // tstate.
+    if (!_PyStats_ThreadInit(interp, tstate)) {
         free_threadstate(tstate);
         return NULL;
     }
+#endif
+#ifdef Py_GIL_DISABLED
     int32_t tlbc_idx = _Py_ReserveTLBCIndex(interp);
     if (tlbc_idx < 0) {
         free_threadstate(tstate);
         return NULL;
     }
-#endif
-#ifdef Py_STATS
-    // The PyStats structure is quite large and is allocated separated from 
tstate.
-    if (!_PyStats_ThreadInit(interp, tstate)) {
+    Py_ssize_t qsbr_idx = _Py_qsbr_reserve(interp);
+    if (qsbr_idx < 0) {
+        _Py_UnreserveTLBCIndex(interp, tlbc_idx);
         free_threadstate(tstate);
         return NULL;
     }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to