Skip to content

Commit 30748d3

Browse files
authored
[3.12] gh-104690: thread_run() checks for tstate dangling pointer (#109056) (#109133)
gh-104690: thread_run() checks for tstate dangling pointer (#109056) thread_run() of _threadmodule.c now calls _PyThreadState_CheckConsistency() to check if tstate is a dangling pointer when Python is built in debug mode. Rename ceval_gil.c is_tstate_valid() to _PyThreadState_CheckConsistency() to reuse it in _threadmodule.c. (cherry picked from commit f63d378)
1 parent 9207c87 commit 30748d3

File tree

4 files changed

+35
-20
lines changed

4 files changed

+35
-20
lines changed

Include/internal/pycore_pystate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ extern _Py_thread_local PyThreadState *_Py_tss_tstate;
6868
#endif
6969
PyAPI_DATA(PyThreadState *) _PyThreadState_GetCurrent(void);
7070

71+
#ifndef NDEBUG
72+
extern int _PyThreadState_CheckConsistency(PyThreadState *tstate);
73+
#endif
74+
7175
/* Get the current Python thread state.
7276
7377
This function is unsafe: it does not check for error and it can return NULL.

Modules/_threadmodule.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,9 +1072,12 @@ static void
10721072
thread_run(void *boot_raw)
10731073
{
10741074
struct bootstate *boot = (struct bootstate *) boot_raw;
1075-
PyThreadState *tstate;
1075+
PyThreadState *tstate = boot->tstate;
1076+
1077+
// gh-104690: If Python is being finalized and PyInterpreterState_Delete()
1078+
// was called, tstate becomes a dangling pointer.
1079+
assert(_PyThreadState_CheckConsistency(tstate));
10761080

1077-
tstate = boot->tstate;
10781081
_PyThreadState_Bind(tstate);
10791082
PyEval_AcquireThread(tstate);
10801083
tstate->interp->threads.count++;

Python/ceval_gil.c

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,6 @@ UNSIGNAL_ASYNC_EXC(PyInterpreterState *interp)
162162
COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
163163
}
164164

165-
#ifndef NDEBUG
166-
/* Ensure that tstate is valid */
167-
static int
168-
is_tstate_valid(PyThreadState *tstate)
169-
{
170-
assert(!_PyMem_IsPtrFreed(tstate));
171-
assert(!_PyMem_IsPtrFreed(tstate->interp));
172-
return 1;
173-
}
174-
#endif
175165

176166
/*
177167
* Implementation of the Global Interpreter Lock (GIL).
@@ -324,7 +314,7 @@ drop_gil(struct _ceval_state *ceval, PyThreadState *tstate)
324314
/* Not switched yet => wait */
325315
if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
326316
{
327-
assert(is_tstate_valid(tstate));
317+
assert(_PyThreadState_CheckConsistency(tstate));
328318
RESET_GIL_DROP_REQUEST(tstate->interp);
329319
/* NOTE: if COND_WAIT does not atomically start waiting when
330320
releasing the mutex, another thread can run through, take
@@ -385,7 +375,7 @@ take_gil(PyThreadState *tstate)
385375
PyThread_exit_thread();
386376
}
387377

388-
assert(is_tstate_valid(tstate));
378+
assert(_PyThreadState_CheckConsistency(tstate));
389379
PyInterpreterState *interp = tstate->interp;
390380
struct _ceval_state *ceval = &interp->ceval;
391381
struct _gil_runtime_state *gil = ceval->gil;
@@ -426,7 +416,7 @@ take_gil(PyThreadState *tstate)
426416
}
427417
PyThread_exit_thread();
428418
}
429-
assert(is_tstate_valid(tstate));
419+
assert(_PyThreadState_CheckConsistency(tstate));
430420

431421
SET_GIL_DROP_REQUEST(interp);
432422
drop_requested = 1;
@@ -465,7 +455,7 @@ take_gil(PyThreadState *tstate)
465455
drop_gil(ceval, tstate);
466456
PyThread_exit_thread();
467457
}
468-
assert(is_tstate_valid(tstate));
458+
assert(_PyThreadState_CheckConsistency(tstate));
469459

470460
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
471461
RESET_GIL_DROP_REQUEST(interp);
@@ -673,7 +663,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
673663
void
674664
PyEval_ReleaseThread(PyThreadState *tstate)
675665
{
676-
assert(is_tstate_valid(tstate));
666+
assert(_PyThreadState_CheckConsistency(tstate));
677667

678668
PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL);
679669
if (new_tstate != tstate) {
@@ -871,7 +861,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
871861
static int
872862
handle_signals(PyThreadState *tstate)
873863
{
874-
assert(is_tstate_valid(tstate));
864+
assert(_PyThreadState_CheckConsistency(tstate));
875865
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
876866
return 0;
877867
}
@@ -977,7 +967,7 @@ void
977967
_Py_FinishPendingCalls(PyThreadState *tstate)
978968
{
979969
assert(PyGILState_Check());
980-
assert(is_tstate_valid(tstate));
970+
assert(_PyThreadState_CheckConsistency(tstate));
981971

982972
if (make_pending_calls(tstate->interp) < 0) {
983973
PyObject *exc = _PyErr_GetRaisedException(tstate);
@@ -1018,7 +1008,7 @@ Py_MakePendingCalls(void)
10181008
assert(PyGILState_Check());
10191009

10201010
PyThreadState *tstate = _PyThreadState_GET();
1021-
assert(is_tstate_valid(tstate));
1011+
assert(_PyThreadState_CheckConsistency(tstate));
10221012

10231013
/* Only execute pending calls on the main thread. */
10241014
if (!_Py_IsMainThread() || !_Py_IsMainInterpreter(tstate->interp)) {

Python/pystate.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,6 +2848,24 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
28482848
}
28492849

28502850

2851+
#ifndef NDEBUG
2852+
// Check that a Python thread state valid. In practice, this function is used
2853+
// on a Python debug build to check if 'tstate' is a dangling pointer, if the
2854+
// PyThreadState memory has been freed.
2855+
//
2856+
// Usage:
2857+
//
2858+
// assert(_PyThreadState_CheckConsistency(tstate));
2859+
int
2860+
_PyThreadState_CheckConsistency(PyThreadState *tstate)
2861+
{
2862+
assert(!_PyMem_IsPtrFreed(tstate));
2863+
assert(!_PyMem_IsPtrFreed(tstate->interp));
2864+
return 1;
2865+
}
2866+
#endif
2867+
2868+
28512869
#ifdef __cplusplus
28522870
}
28532871
#endif

0 commit comments

Comments
 (0)