Skip to main content

Include/internal/pycore_runtime.h

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

pycore_runtime.h declares _PyRuntimeState, the one-per-process struct that acts as the root of the entire CPython runtime. There is exactly one instance of it: the global _PyRuntime variable defined in Python/pylifecycle.c. Everything the interpreter touches, including the list of sub-interpreters, the signal queue, the atexit registry, and the main-thread lock, hangs off this single struct.

The file also defines the three coarse initialization flags (preinitialized, core_initialized, initialized) that guard the startup sequence in Py_InitializeFromConfig. Code that runs before Py_Initialize completes must check these flags before calling any API that assumes a live runtime. In gopy the equivalent gate is the package init ordering: the state, objects, and pythonrun packages must be fully initialized before the first RunString call.

A third concern carried here is _Py_IDENTIFIER, the mechanism that pre-interns frequently-used string names (like "__name__" or "__init__") at startup so repeated attribute lookups avoid calling PyUnicode_InternInPlace on every call. gopy replaces this with objects.NewStr whose intern cache is managed by the Go runtime.

Map

LinesSymbolRolegopy
1-40include guard + forward decls#include chain for pycore_atomic.h, pycore_gil.h, pycore_interp.h; forward-declares _PyRuntimeState.n/a
41-100struct pyruntimestate top fieldspreinitialized, core_initialized, initialized flags; _PyRuntimeState.xidregistry (cross-interpreter data); finalizing thread pointer; open_code_hook.pythonrun/runstring.go
101-180_PyRuntimeState.interpreters sub-structmutex, next_id, head (linked list of PyInterpreterState), main pointer to the main interpreter.pythonrun/runstring.go
181-230_PyRuntimeState.signals sub-structis_tripped flag, handlers[NSIG] array, wakeup socket fd, main_thread id.n/a (signals.go in vm/)
231-280_PyRuntimeState.atexit sub-structcallbacks list of (func, args, kwargs) triples, mutex protecting them.n/a
281-350_PyRuntimeState_GetFinalizing / _PyRuntimeState_SetFinalizingAtomic getter/setter for the finalizing field; non-zero means Py_Finalize is running.n/a
351-420_Py_IDENTIFIER / _PyRuntime declaration_Py_IDENTIFIER(name) macro expands to a static _Py_Identifier struct; _PyRuntime is declared extern.objects/str.go intern cache

Reading

_PyRuntimeState layout (lines 41 to 180)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_runtime.h#L41-180

struct pyruntimestate {
/* 1 if Py_PreInitialize() has completed, 0 otherwise. */
int preinitialized;
/* 1 if _PyRuntime_Initialize() / Py_InitializeFromConfig() partial
core init done. */
int core_initialized;
/* 1 if Py_Initialize() completed fully. */
int initialized;

/* Thread that called Py_Finalize(), NULL otherwise. Used to detect
re-entrant finalization and to guard daemon threads. */
PyThreadState *finalizing;
unsigned long finalizing_id;

struct {
PyThread_type_lock mutex;
/* Linked list of all live PyInterpreterState objects. */
PyInterpreterState *head;
PyInterpreterState *main;
int64_t next_id;
} interpreters;

struct {
/* Set to 1 by the signal handler, cleared by _PyEval_SignalReceived. */
Py_atomic_int is_tripped;
PyOS_sighandler_t handlers[NSIG];
/* Wakeup file descriptor written by the signal handler. */
struct {
int fd;
int warn_on_full_buffer;
} wakeup;
unsigned long main_thread;
} signals;

struct {
struct _atexit_callback *callbacks;
PyThread_type_lock mutex;
} atexit;

/* Hook called by io.open() and open(). */
PyObject *(*open_code_hook)(PyObject *path, void *userData);
void *open_code_userdata;
};

The three flags (preinitialized, core_initialized, initialized) form a state machine. Code in Objects/ and Python/ that runs before full initialization checks _PyRuntime.initialized before calling APIs like PyErr_SetString or PyObject_Call. The startup sequence in Py_InitializeFromConfig advances through the three stages in order, never skipping one.

The interpreters sub-struct owns the linked list of all live PyInterpreterState objects. The head field points to the most recently created interpreter; main always points to the very first one created during Py_Initialize. next_id is a monotonically increasing counter: each new interpreter gets next_id++ as its id field, which Python code can read via interpreters.get_current() in the _interpreters module.

Initialized flags lifecycle (lines 41 to 100)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_runtime.h#L41-100

/* Sequence enforced in Py_InitializeFromConfig:
1. _PyRuntime_Initialize() -> preinitialized = 1
2. _Py_InitializeCore() -> core_initialized = 1
3. _Py_InitializeMain() -> initialized = 1
*/
static inline int
_PyRuntime_IsInitialized(void) {
return _PyRuntime.initialized;
}

A crash inside PyErr_SetString during very early startup is often caused by calling an API before core_initialized = 1. CPython guards against this with _Py_IsInitialized() checks scattered through Objects/. The sequence must be respected by any embedder that calls low-level internal APIs directly rather than going through Py_InitializeFromConfig.

In gopy, the equivalent gate is the package init graph. The objects package initializes the built-in type hierarchy before pythonrun can call RunString. There is no explicit preinitialized flag; instead, Go's package-level var initialization provides the ordering guarantee.

_PyRuntimeState_GetFinalizing (lines 281 to 350)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_runtime.h#L281-350

static inline PyThreadState *
_PyRuntimeState_GetFinalizing(PyRuntimeState *runtime) {
return (PyThreadState *)_Py_atomic_load_ptr_relaxed(&runtime->_finalizing);
}

static inline void
_PyRuntimeState_SetFinalizing(PyRuntimeState *runtime, PyThreadState *tstate) {
_Py_atomic_store_ptr_relaxed(&runtime->_finalizing, (void *)tstate);
}

SetFinalizing is called by Py_FinalizeEx before tearing down interpreters. Any thread that calls _PyThreadState_MustExit after this point will see a non-NULL finalizing and exit its eval loop rather than continuing to execute bytecodes. This prevents daemon threads from running arbitrary Python after atexit callbacks have fired and modules have begun unloading.

gopy mirror

gopy has no _PyRuntimeState struct. The global runtime state that CPython packs into this one struct is spread across Go package-level variables: the imp package owns sys.modules; the gil package owns the eval-breaker and the GIL; pythonrun drives the initialization sequence.

The initialized flag is replaced by Go's package-level init ordering. The atexit callbacks list is not yet ported (spec 1700 scope). Signal handling (signals sub-struct) is partially mapped in vm/gil/signals.go via gil.BreakerSignalsPending.