[3.15] gh-155363: Fix QSBR slot leak on thread state creation failure (gh-155365) (#155579)

hugovk <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/dd4bd4c293694c34b098aecb4f19b02961528909
commit: dd4bd4c293694c34b098aecb4f19b02961528909
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2026-08-22T12:39:14+03:00
summary:

[3.15] gh-155363: Fix QSBR slot leak on thread state creation failure (gh-155365) (#155579)

Co-authored-by: Neil Schemenauer <[email protected]>
Co-authored-by: Kumar Aditya <[email protected]>

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 03036020b1cb1ae..d4c96b36e3c8b5b 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -3313,14 +3313,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 8349df1b573952d..d79006639ab4529 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -1638,21 +1638,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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.