gh-131253: free-threaded build support for pystats (gh-137189)

Allow the --enable-pystats build option to be used with free-threading.  The
stats are now stored on a per-interpreter basis, rather than process global.
For free-threaded builds, the stats structure is allocated per-thread and
then periodically merged into the per-interpreter stats structure (on thread
exit or when the reporting function is called). Most of the pystats related
code has be moved into the file Python/pystats.c.
This commit is contained in:
Neil Schemenauer 2025-11-03 11:36:37 -08:00 committed by GitHub
parent cf1a2c1ee4
commit c98c5b3449
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1269 additions and 485 deletions

View file

@ -21,6 +21,7 @@
#include "pycore_runtime.h" // _PyRuntime
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
#include "pycore_stackref.h" // Py_STACKREF_DEBUG
#include "pycore_stats.h" // FT_STAT_WORLD_STOP_INC()
#include "pycore_time.h" // _PyTime_Init()
#include "pycore_uop.h" // UOP_BUFFER_SIZE
#include "pycore_uniqueid.h" // _PyObject_FinalizePerThreadRefcounts()
@ -465,6 +466,12 @@ alloc_interpreter(void)
static void
free_interpreter(PyInterpreterState *interp)
{
#ifdef Py_STATS
if (interp->pystats_struct) {
PyMem_RawFree(interp->pystats_struct);
interp->pystats_struct = NULL;
}
#endif
// The main interpreter is statically allocated so
// should not be freed.
if (interp != &_PyRuntime._main_interpreter) {
@ -1407,6 +1414,9 @@ static void
free_threadstate(_PyThreadStateImpl *tstate)
{
PyInterpreterState *interp = tstate->base.interp;
#ifdef Py_STATS
_PyStats_ThreadFini(tstate);
#endif
// The initial thread state of the interpreter is allocated
// as part of the interpreter state so should not be freed.
if (tstate == &interp->_initial_thread) {
@ -1535,6 +1545,13 @@ new_threadstate(PyInterpreterState *interp, int whence)
return NULL;
}
#endif
#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
/* We serialize concurrent creation to protect global state. */
HEAD_LOCK(interp->runtime);
@ -1846,6 +1863,9 @@ _PyThreadState_DeleteCurrent(PyThreadState *tstate)
_Py_EnsureTstateNotNULL(tstate);
#ifdef Py_GIL_DISABLED
_Py_qsbr_detach(((_PyThreadStateImpl *)tstate)->qsbr);
#endif
#ifdef Py_STATS
_PyStats_Detach((_PyThreadStateImpl *)tstate);
#endif
current_fast_clear(tstate->interp->runtime);
tstate_delete_common(tstate, 1); // release GIL as part of call
@ -2020,6 +2040,10 @@ tstate_deactivate(PyThreadState *tstate)
assert(tstate_is_bound(tstate));
assert(tstate->_status.active);
#if Py_STATS
_PyStats_Detach((_PyThreadStateImpl *)tstate);
#endif
tstate->_status.active = 0;
// We do not unbind the gilstate tstate here.
@ -2123,6 +2147,10 @@ _PyThreadState_Attach(PyThreadState *tstate)
_PyCriticalSection_Resume(tstate);
}
#ifdef Py_STATS
_PyStats_Attach((_PyThreadStateImpl *)tstate);
#endif
#if defined(Py_DEBUG)
errno = err;
#endif
@ -2272,6 +2300,7 @@ stop_the_world(struct _stoptheworld_state *stw)
stw->thread_countdown = 0;
stw->stop_event = (PyEvent){0}; // zero-initialize (unset)
stw->requester = _PyThreadState_GET(); // may be NULL
FT_STAT_WORLD_STOP_INC();
_Py_FOR_EACH_STW_INTERP(stw, i) {
_Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {