cpython/Include/internal/pycore_runtime.h
Gregory P. Smith 64afa947f4
gh-146302: make Py_IsInitialized() thread-safe and reflect true init completion (GH-146303)
## Summary

- Move the `runtime->initialized = 1` store from before `site.py` import to the end of `init_interp_main()`, so `Py_IsInitialized()` only returns true after initialization has fully completed
- Access `initialized` and `core_initialized` through new inline accessors using acquire/release atomics, to also protect from data race undefined behavior
- `PySys_AddAuditHook()` now uses the accessor, so with the flag move it correctly skips audit hook invocation during all init phases (matching the documented "after runtime initialization" behavior) ... We could argue that running these earlier would be good even if the intent was never explicitly expressed, but that'd be its own issue.

## Motivation

`Py_IsInitialized()` returned 1 while `Py_InitializeEx()` was still running — specifically, before `site.py` had been imported. See https://github.com/PyO3/pyo3/issues/5900 where a second thread could acquire the GIL and start executing Python with an incomplete `sys.path` because `site.py` hadn't finished.

The flag was also a plain `int` with no atomic operations, making concurrent reads a C-standard data race, though unlikely to manifest.

## Regression test:

The added test properly fails on `main` with `ERROR: Py_IsInitialized() was true during site import`.

---

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:54:23 +00:00

86 lines
2.5 KiB
C

#ifndef Py_INTERNAL_RUNTIME_H
#define Py_INTERNAL_RUNTIME_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif
#include "pycore_runtime_structs.h" // _PyRuntimeState
/* API */
// Export _PyRuntime for shared extensions which use it in static inline
// functions for best performance, like _Py_IsMainThread() or _Py_ID().
// It's also made accessible for debuggers and profilers.
PyAPI_DATA(_PyRuntimeState) _PyRuntime;
extern PyStatus _PyRuntimeState_Init(_PyRuntimeState *runtime);
extern void _PyRuntimeState_Fini(_PyRuntimeState *runtime);
#ifdef HAVE_FORK
extern PyStatus _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime);
#endif
/* Initialize _PyRuntimeState.
Return NULL on success, or return an error message on failure. */
extern PyStatus _PyRuntime_Initialize(void);
extern void _PyRuntime_Finalize(void);
static inline PyThreadState*
_PyRuntimeState_GetFinalizing(_PyRuntimeState *runtime) {
return (PyThreadState*)_Py_atomic_load_ptr_relaxed(&runtime->_finalizing);
}
static inline unsigned long
_PyRuntimeState_GetFinalizingID(_PyRuntimeState *runtime) {
return _Py_atomic_load_ulong_relaxed(&runtime->_finalizing_id);
}
static inline void
_PyRuntimeState_SetFinalizing(_PyRuntimeState *runtime, PyThreadState *tstate) {
_Py_atomic_store_ptr_relaxed(&runtime->_finalizing, tstate);
if (tstate == NULL) {
_Py_atomic_store_ulong_relaxed(&runtime->_finalizing_id, 0);
}
else {
// XXX Re-enable this assert once gh-109860 is fixed.
//assert(tstate->thread_id == PyThread_get_thread_ident());
_Py_atomic_store_ulong_relaxed(&runtime->_finalizing_id,
tstate->thread_id);
}
}
// Atomic so a thread that reads initialized=1 observes all writes
// from the initialization sequence (gh-146302).
static inline int
_PyRuntimeState_GetCoreInitialized(_PyRuntimeState *runtime) {
return _Py_atomic_load_int(&runtime->core_initialized);
}
static inline void
_PyRuntimeState_SetCoreInitialized(_PyRuntimeState *runtime, int initialized) {
_Py_atomic_store_int(&runtime->core_initialized, initialized);
}
static inline int
_PyRuntimeState_GetInitialized(_PyRuntimeState *runtime) {
return _Py_atomic_load_int(&runtime->initialized);
}
static inline void
_PyRuntimeState_SetInitialized(_PyRuntimeState *runtime, int initialized) {
_Py_atomic_store_int(&runtime->initialized, initialized);
}
#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_RUNTIME_H */