diff --git a/Include/pystate.h b/Include/pystate.h index f16ffb8fd2a..819c1427a74 100644 --- a/Include/pystate.h +++ b/Include/pystate.h @@ -216,6 +216,7 @@ typedef struct _ts { struct _ts *next; PyInterpreterState *interp; + /* Borrowed reference to the current frame (it can be NULL) */ struct _frame *frame; int recursion_depth; char overflowed; /* The stack has overflowed. Allow 50 more calls diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-03-23-18-08-34.bpo-20526.NHNZIv.rst b/Misc/NEWS.d/next/Core and Builtins/2020-03-23-18-08-34.bpo-20526.NHNZIv.rst new file mode 100644 index 00000000000..c808b7608c6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-03-23-18-08-34.bpo-20526.NHNZIv.rst @@ -0,0 +1,3 @@ +Fix :c:func:`PyThreadState_Clear()`. ``PyThreadState.frame`` is a borrowed +reference, not a strong reference: ``PyThreadState_Clear()`` must not call +``Py_CLEAR(tstate->frame)``. diff --git a/Python/pystate.c b/Python/pystate.c index ebfe6ce65de..604e0279148 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -566,11 +566,19 @@ _PyState_ClearModules(void) void PyThreadState_Clear(PyThreadState *tstate) { - if (Py_VerboseFlag && tstate->frame != NULL) + if (Py_VerboseFlag && tstate->frame != NULL) { + /* bpo-20526: After the main thread calls + _PyRuntimeState_SetFinalizing() in Py_FinalizeEx(), threads must + exit when trying to take the GIL. If a thread exit in the middle of + _PyEval_EvalFrameDefault(), tstate->frame is not reset to its + previous value. It is more likely with daemon threads, but it can + happen with regular threads if threading._shutdown() fails + (ex: interrupted by CTRL+C). */ fprintf(stderr, "PyThreadState_Clear: warning: thread still has a frame\n"); + } - Py_CLEAR(tstate->frame); + /* Don't clear tstate->frame: it is a borrowed reference */ Py_CLEAR(tstate->dict); Py_CLEAR(tstate->async_exc);