Skip to main content

Include/internal/pycore_pystate.h

Source:

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_pystate.h

pycore_pystate.h declares the per-thread interpreter state (PyThreadState) and the inline accessors used throughout the eval loop.

Map

LinesSymbolRole
1-60PyThreadState layoutframe, exc_info, interp, thread_id, tracing hooks
61-120_PyThreadState_GETFast inline accessor for current PyThreadState *
121-200_PyErr_StackItemPer-frame exception info (replaces tstate->exc_type in 3.11)
201-280Thread state listDoubly-linked list of all thread states for the interpreter
281-350_Py_atomic accessors_Py_atomic_load_relaxed, _Py_atomic_store

Reading

PyThreadState key fields

// CPython: Include/internal/pycore_pystate.h:58 PyThreadState
struct _ts {
/* Doubly-linked list of all thread states in this interpreter */
struct _ts *prev;
struct _ts *next;

PyInterpreterState *interp;

/* Current executing frame */
struct _PyInterpreterFrame *current_frame;

/* Exception currently being handled (_PyErr_StackItem chain) */
_PyErr_StackItem *exc_info;

/* Tracing: set_trace(), set_profile() */
Py_tracefunc c_tracefunc;
Py_tracefunc c_profilefunc;
PyObject *c_traceobj;
PyObject *c_profileobj;

/* Thread identity */
unsigned long thread_id; /* from PyThread_get_thread_ident */
uint64_t native_thread_id;

/* Pending calls: Py_AddPendingCall */
int async_exc;
PyObject *async_exc_obj;

/* Reference to the GIL (free-threaded: per-thread refcount mode) */
int py_recursion_remaining;
int c_recursion_remaining;
};

_PyThreadState_GET

// CPython: Include/internal/pycore_pystate.h:82 _PyThreadState_GET
static inline PyThreadState *
_PyThreadState_GET(void)
{
/* tstate_current is an atomic pointer in _PyRuntime.gilstate */
return (PyThreadState *)
_Py_atomic_load_relaxed(&_PyRuntime.gilstate.tstate_current);
}

This is the hot path used by PyErr_SetString, LOAD_FAST, and almost every CPython internal function.

_PyErr_StackItem

// CPython: Include/internal/pycore_pystate.h:140 _PyErr_StackItem
typedef struct _err_stackitem {
/* Exception currently being handled in this frame */
PyObject *exc_value;
struct _err_stackitem *previous_item;
} _PyErr_StackItem;

Python 3.11 moved per-frame exception state here from PyThreadState. Each PUSH_EXC_INFO / POP_EXC_INFO instruction pushes/pops one item. sys.exc_info() reads tstate->exc_info->exc_value.

Thread state list

// CPython: Include/internal/pycore_pystate.h:220
/* PyInterpreterState.threads is a linked list:
head: most recently created thread state
On thread creation: prepend new tstate
On thread exit: remove from list
Protected by: interpreter->threads.mutex */

Recursion limits

// CPython: Include/internal/pycore_pystate.h:260
int py_recursion_remaining; /* decremented on Python-level calls */
int c_recursion_remaining; /* decremented on C-to-Python transitions */
/* When py_recursion_remaining hits 0: RecursionError */
/* sys.setrecursionlimit sets the initial value */

Two separate limits prevent C extension stack overflow from masking Python RecursionError.

gopy notes

gopy's PyThreadState is vm.ThreadState in vm/tstate.go. _PyThreadState_GET is vm.GetCurrentThreadState() which reads a goroutine-local value via sync.Map keyed by goroutine ID. _PyErr_StackItem is vm.ErrStackItem in vm/exceptions.go. py_recursion_remaining is tstate.RecursionDepth compared against tstate.Interp.RecursionLimit.