Skip to content

Commit 44e92d4

Browse files
authored
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.
1 parent 42a18e1 commit 44e92d4

4 files changed

Lines changed: 26 additions & 10 deletions

File tree

Include/internal/pycore_code.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,10 @@ PyAPI_FUNC(_Py_CODEUNIT *) _PyCode_GetTLBC(PyCodeObject *co);
582582
// Returns the reserved index or -1 on error.
583583
extern int32_t _Py_ReserveTLBCIndex(PyInterpreterState *interp);
584584

585+
// Release an index returned by _Py_ReserveTLBCIndex() that was never stored
586+
// in a PyThreadState.
587+
extern void _Py_UnreserveTLBCIndex(PyInterpreterState *interp, int32_t index);
588+
585589
// Release the current thread's index into thread-local bytecode arrays
586590
extern void _Py_ClearTLBCIndex(_PyThreadStateImpl *tstate);
587591

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a leak in the :term:`free-threaded build` when creating a thread state
2+
fails after an internal QSBR slot has been reserved for it. The slot could
3+
never be reclaimed, so the QSBR array grew without bound across repeated
4+
failures.

Objects/codeobject.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,14 +3314,20 @@ _Py_ReserveTLBCIndex(PyInterpreterState *interp)
33143314
}
33153315

33163316
void
3317-
_Py_ClearTLBCIndex(_PyThreadStateImpl *tstate)
3317+
_Py_UnreserveTLBCIndex(PyInterpreterState *interp, int32_t index)
33183318
{
3319-
PyInterpreterState *interp = ((PyThreadState *)tstate)->interp;
33203319
if (interp->config.tlbc_enabled) {
3321-
_PyIndexPool_FreeIndex(&interp->tlbc_indices, tstate->tlbc_index);
3320+
_PyIndexPool_FreeIndex(&interp->tlbc_indices, index);
33223321
}
33233322
}
33243323

3324+
void
3325+
_Py_ClearTLBCIndex(_PyThreadStateImpl *tstate)
3326+
{
3327+
PyInterpreterState *interp = ((PyThreadState *)tstate)->interp;
3328+
_Py_UnreserveTLBCIndex(interp, tstate->tlbc_index);
3329+
}
3330+
33253331
static _PyCodeArray *
33263332
_PyCodeArray_New(Py_ssize_t size)
33273333
{

Python/pystate.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,21 +1667,23 @@ new_threadstate(PyInterpreterState *interp, int whence)
16671667
return NULL;
16681668
}
16691669

1670-
#ifdef Py_GIL_DISABLED
1671-
Py_ssize_t qsbr_idx = _Py_qsbr_reserve(interp);
1672-
if (qsbr_idx < 0) {
1670+
#ifdef Py_STATS
1671+
// The PyStats structure is quite large and is allocated separated from
1672+
// tstate.
1673+
if (!_PyStats_ThreadInit(interp, tstate)) {
16731674
free_threadstate(tstate);
16741675
return NULL;
16751676
}
1677+
#endif
1678+
#ifdef Py_GIL_DISABLED
16761679
int32_t tlbc_idx = _Py_ReserveTLBCIndex(interp);
16771680
if (tlbc_idx < 0) {
16781681
free_threadstate(tstate);
16791682
return NULL;
16801683
}
1681-
#endif
1682-
#ifdef Py_STATS
1683-
// The PyStats structure is quite large and is allocated separated from tstate.
1684-
if (!_PyStats_ThreadInit(interp, tstate)) {
1684+
Py_ssize_t qsbr_idx = _Py_qsbr_reserve(interp);
1685+
if (qsbr_idx < 0) {
1686+
_Py_UnreserveTLBCIndex(interp, tlbc_idx);
16851687
free_threadstate(tstate);
16861688
return NULL;
16871689
}

0 commit comments

Comments
 (0)