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
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-40 | include guard + forward decls | #include chain for pycore_atomic.h, pycore_gil.h, pycore_interp.h; forward-declares _PyRuntimeState. | n/a |
| 41-100 | struct pyruntimestate top fields | preinitialized, core_initialized, initialized flags; _PyRuntimeState.xidregistry (cross-interpreter data); finalizing thread pointer; open_code_hook. | pythonrun/runstring.go |
| 101-180 | _PyRuntimeState.interpreters sub-struct | mutex, next_id, head (linked list of PyInterpreterState), main pointer to the main interpreter. | pythonrun/runstring.go |
| 181-230 | _PyRuntimeState.signals sub-struct | is_tripped flag, handlers[NSIG] array, wakeup socket fd, main_thread id. | n/a (signals.go in vm/) |
| 231-280 | _PyRuntimeState.atexit sub-struct | callbacks list of (func, args, kwargs) triples, mutex protecting them. | n/a |
| 281-350 | _PyRuntimeState_GetFinalizing / _PyRuntimeState_SetFinalizing | Atomic 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.