Skip to content

Commit

Permalink
[3.11] gh-104690: thread_run() checks for tstate dangling pointer (#1…
Browse files Browse the repository at this point in the history
…09056) (#109134)

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)
  • Loading branch information
vstinner committed Sep 8, 2023
1 parent 9dd28d2 commit b55cf2c
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 26 deletions.
4 changes: 4 additions & 0 deletions Include/internal/pycore_pystate.h
Expand Up @@ -61,6 +61,10 @@ _Py_ThreadCanHandlePendingCalls(void)
}


#ifndef NDEBUG
extern int _PyThreadState_CheckConsistency(PyThreadState *tstate);
#endif

/* Variable and macro for in-line access to current thread
and interpreter state */

Expand Down
7 changes: 5 additions & 2 deletions Modules/_threadmodule.c
Expand Up @@ -1076,9 +1076,12 @@ static void
thread_run(void *boot_raw)
{
struct bootstate *boot = (struct bootstate *) boot_raw;
PyThreadState *tstate;
PyThreadState *tstate = boot->tstate;

// gh-104690: If Python is being finalized and PyInterpreterState_Delete()
// was called, tstate becomes a dangling pointer.
assert(_PyThreadState_CheckConsistency(tstate));

tstate = boot->tstate;
tstate->thread_id = PyThread_get_thread_ident();
#ifdef PY_HAVE_THREAD_NATIVE_ID
tstate->native_thread_id = PyThread_get_thread_native_id();
Expand Down
26 changes: 6 additions & 20 deletions Python/ceval.c
Expand Up @@ -216,20 +216,6 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame);
"cannot access free variable '%s' where it is not associated with a" \
" value in enclosing scope"

#ifndef NDEBUG
/* Ensure that tstate is valid: sanity check for PyEval_AcquireThread() and
PyEval_RestoreThread(). Detect if tstate memory was freed. It can happen
when a thread continues to run after Python finalization, especially
daemon threads. */
static int
is_tstate_valid(PyThreadState *tstate)
{
assert(!_PyMem_IsPtrFreed(tstate));
assert(!_PyMem_IsPtrFreed(tstate->interp));
return 1;
}
#endif


/* This can set eval_breaker to 0 even though gil_drop_request became
1. We believe this is all right because the eval loop will release
Expand Down Expand Up @@ -464,7 +450,7 @@ PyEval_AcquireThread(PyThreadState *tstate)
void
PyEval_ReleaseThread(PyThreadState *tstate)
{
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));

_PyRuntimeState *runtime = tstate->interp->runtime;
PyThreadState *new_tstate = _PyThreadState_Swap(&runtime->gilstate, NULL);
Expand Down Expand Up @@ -671,7 +657,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
static int
handle_signals(PyThreadState *tstate)
{
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
return 0;
}
Expand Down Expand Up @@ -739,7 +725,7 @@ void
_Py_FinishPendingCalls(PyThreadState *tstate)
{
assert(PyGILState_Check());
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));

struct _pending_calls *pending = &tstate->interp->ceval.pending;

Expand All @@ -764,7 +750,7 @@ Py_MakePendingCalls(void)
assert(PyGILState_Check());

PyThreadState *tstate = _PyThreadState_GET();
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));

/* Python signal handler doesn't really queue a callback: it only signals
that a signal was received, see _PyEval_SignalReceived(). */
Expand Down Expand Up @@ -6947,7 +6933,7 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
int
_PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
{
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));
/* The caller must hold the GIL */
assert(PyGILState_Check());

Expand Down Expand Up @@ -6999,7 +6985,7 @@ PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
int
_PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
{
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));
/* The caller must hold the GIL */
assert(PyGILState_Check());

Expand Down
8 changes: 4 additions & 4 deletions Python/ceval_gil.h
Expand Up @@ -171,7 +171,7 @@ drop_gil(struct _ceval_runtime_state *ceval, struct _ceval_state *ceval2,
/* Not switched yet => wait */
if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
{
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));
RESET_GIL_DROP_REQUEST(tstate->interp);
/* NOTE: if COND_WAIT does not atomically start waiting when
releasing the mutex, another thread can run through, take
Expand Down Expand Up @@ -226,7 +226,7 @@ take_gil(PyThreadState *tstate)
PyThread_exit_thread();
}

assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));
PyInterpreterState *interp = tstate->interp;
struct _ceval_runtime_state *ceval = &interp->runtime->ceval;
struct _ceval_state *ceval2 = &interp->ceval;
Expand Down Expand Up @@ -268,7 +268,7 @@ take_gil(PyThreadState *tstate)
}
PyThread_exit_thread();
}
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));

SET_GIL_DROP_REQUEST(interp);
drop_requested = 1;
Expand Down Expand Up @@ -307,7 +307,7 @@ take_gil(PyThreadState *tstate)
drop_gil(ceval, ceval2, tstate);
PyThread_exit_thread();
}
assert(is_tstate_valid(tstate));
assert(_PyThreadState_CheckConsistency(tstate));

if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {
RESET_GIL_DROP_REQUEST(interp);
Expand Down
18 changes: 18 additions & 0 deletions Python/pystate.c
Expand Up @@ -2237,6 +2237,24 @@ _PyThreadState_PopFrame(PyThreadState *tstate, _PyInterpreterFrame * frame)
}


#ifndef NDEBUG
// Check that a Python thread state valid. In practice, this function is used
// on a Python debug build to check if 'tstate' is a dangling pointer, if the
// PyThreadState memory has been freed.
//
// Usage:
//
// assert(_PyThreadState_CheckConsistency(tstate));
int
_PyThreadState_CheckConsistency(PyThreadState *tstate)
{
assert(!_PyMem_IsPtrFreed(tstate));
assert(!_PyMem_IsPtrFreed(tstate->interp));
return 1;
}
#endif


#ifdef __cplusplus
}
#endif

0 comments on commit b55cf2c

Please sign in to comment.