Skip to content

Commit 1eda370

Browse files
gpsheadclaude
andcommitted
gh-146302: make Py_IsInitialized() thread-safe and reflect true init completion
Py_IsInitialized() previously returned 1 before Py_InitializeEx() had finished, because the runtime flag was set before site.py was imported. This caused a race in embedders like PyO3 where a second thread could observe an initialized interpreter before sys.path was fully configured. Move the initialized=1 store to the end of init_interp_main(), after site import, lazy imports, tier 2 optimizer, and dict watcher setup. Access both `initialized` and `core_initialized` through new inline accessors that use acquire/release atomics, eliminating the C-standard data race and ensuring correct visibility on weakly-ordered architectures. Fix PySys_AddAuditHook() to check core_initialized (not initialized) when deciding whether a thread state is available, since tstate exists after core init completes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e36f8db commit 1eda370

File tree

8 files changed

+64
-22
lines changed

8 files changed

+64
-22
lines changed

Doc/c-api/interp-lifecycle.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,11 @@ Initializing and finalizing the interpreter
410410
(zero) if not. After :c:func:`Py_FinalizeEx` is called, this returns false until
411411
:c:func:`Py_Initialize` is called again.
412412
413+
.. versionchanged:: next
414+
This function no longer returns true until initialization has fully
415+
completed, including import of the :mod:`site` module. Previously it
416+
could return true while :c:func:`Py_Initialize` was still running.
417+
413418
414419
.. c:function:: int Py_IsFinalizing()
415420

Include/internal/pycore_runtime.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,29 @@ _PyRuntimeState_SetFinalizing(_PyRuntimeState *runtime, PyThreadState *tstate) {
5656
}
5757
}
5858

59+
// Use acquire/release (not relaxed) because these are publication flags:
60+
// when a reader sees 1, all prior initialization writes must be visible.
61+
62+
static inline int
63+
_PyRuntimeState_GetCoreInitialized(_PyRuntimeState *runtime) {
64+
return _Py_atomic_load_int_acquire(&runtime->core_initialized);
65+
}
66+
67+
static inline void
68+
_PyRuntimeState_SetCoreInitialized(_PyRuntimeState *runtime, int initialized) {
69+
_Py_atomic_store_int_release(&runtime->core_initialized, initialized);
70+
}
71+
72+
static inline int
73+
_PyRuntimeState_GetInitialized(_PyRuntimeState *runtime) {
74+
return _Py_atomic_load_int_acquire(&runtime->initialized);
75+
}
76+
77+
static inline void
78+
_PyRuntimeState_SetInitialized(_PyRuntimeState *runtime, int initialized) {
79+
_Py_atomic_store_int_release(&runtime->initialized, initialized);
80+
}
81+
5982

6083
#ifdef __cplusplus
6184
}

Include/internal/pycore_runtime_structs.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,18 @@ struct pyruntimestate {
158158
/* Is Python preinitialized? Set to 1 by Py_PreInitialize() */
159159
int preinitialized;
160160

161-
/* Is Python core initialized? Set to 1 by _Py_InitializeCore() */
161+
/* Is Python core initialized? Set to 1 by _Py_InitializeCore().
162+
163+
Use _PyRuntimeState_GetCoreInitialized() and
164+
_PyRuntimeState_SetCoreInitialized() to access it,
165+
don't access it directly. */
162166
int core_initialized;
163167

164-
/* Is Python fully initialized? Set to 1 by Py_Initialize() */
168+
/* Is Python fully initialized? Set to 1 by Py_Initialize().
169+
170+
Use _PyRuntimeState_GetInitialized() and
171+
_PyRuntimeState_SetInitialized() to access it,
172+
don't access it directly. */
165173
int initialized;
166174

167175
/* Set by Py_FinalizeEx(). Only reset to NULL if Py_Initialize()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:c:func:`Py_IsInitialized` no longer returns true until initialization has
2+
fully completed, including import of the :mod:`site` module. The underlying
3+
runtime flags now use atomic operations with acquire/release memory ordering.

Python/preconfig.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ _PyPreConfig_Write(const PyPreConfig *src_config)
928928
return status;
929929
}
930930

931-
if (_PyRuntime.core_initialized) {
931+
if (_PyRuntimeState_GetCoreInitialized(&_PyRuntime)) {
932932
/* bpo-34008: Calling this functions after Py_Initialize() ignores
933933
the new configuration. */
934934
return _PyStatus_OK();

Python/pylifecycle.c

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,13 @@ int (*_PyOS_mystrnicmp_hack)(const char *, const char *, Py_ssize_t) = \
166166
int
167167
_Py_IsCoreInitialized(void)
168168
{
169-
return _PyRuntime.core_initialized;
169+
return _PyRuntimeState_GetCoreInitialized(&_PyRuntime);
170170
}
171171

172172
int
173173
Py_IsInitialized(void)
174174
{
175-
return _PyRuntime.initialized;
175+
return _PyRuntimeState_GetInitialized(&_PyRuntime);
176176
}
177177

178178

@@ -491,7 +491,7 @@ static PyStatus
491491
pycore_init_runtime(_PyRuntimeState *runtime,
492492
const PyConfig *config)
493493
{
494-
if (runtime->initialized) {
494+
if (_PyRuntimeState_GetInitialized(runtime)) {
495495
return _PyStatus_ERR("main interpreter already initialized");
496496
}
497497

@@ -984,7 +984,7 @@ pyinit_config(_PyRuntimeState *runtime,
984984
}
985985

986986
/* Only when we get here is the runtime core fully initialized */
987-
runtime->core_initialized = 1;
987+
_PyRuntimeState_SetCoreInitialized(runtime, 1);
988988
return _PyStatus_OK();
989989
}
990990

@@ -1217,7 +1217,7 @@ init_interp_main(PyThreadState *tstate)
12171217
* or pure Python code in the standard library won't work.
12181218
*/
12191219
if (is_main_interp) {
1220-
interp->runtime->initialized = 1;
1220+
_PyRuntimeState_SetInitialized(interp->runtime, 1);
12211221
}
12221222
return _PyStatus_OK();
12231223
}
@@ -1329,8 +1329,6 @@ init_interp_main(PyThreadState *tstate)
13291329
Py_XDECREF(warnings_module);
13301330
}
13311331
Py_XDECREF(warnoptions);
1332-
1333-
interp->runtime->initialized = 1;
13341332
}
13351333

13361334
if (config->site_import) {
@@ -1426,6 +1424,10 @@ init_interp_main(PyThreadState *tstate)
14261424

14271425
assert(!_PyErr_Occurred(tstate));
14281426

1427+
if (is_main_interp) {
1428+
_PyRuntimeState_SetInitialized(interp->runtime, 1);
1429+
}
1430+
14291431
return _PyStatus_OK();
14301432
}
14311433

@@ -1445,11 +1447,11 @@ static PyStatus
14451447
pyinit_main(PyThreadState *tstate)
14461448
{
14471449
PyInterpreterState *interp = tstate->interp;
1448-
if (!interp->runtime->core_initialized) {
1450+
if (!_PyRuntimeState_GetCoreInitialized(interp->runtime)) {
14491451
return _PyStatus_ERR("runtime core not initialized");
14501452
}
14511453

1452-
if (interp->runtime->initialized) {
1454+
if (_PyRuntimeState_GetInitialized(interp->runtime)) {
14531455
return pyinit_main_reconfigure(tstate);
14541456
}
14551457

@@ -1505,7 +1507,7 @@ Py_InitializeEx(int install_sigs)
15051507
}
15061508
_PyRuntimeState *runtime = &_PyRuntime;
15071509

1508-
if (runtime->initialized) {
1510+
if (_PyRuntimeState_GetInitialized(runtime)) {
15091511
/* bpo-33932: Calling Py_Initialize() twice does nothing. */
15101512
return;
15111513
}
@@ -2210,7 +2212,7 @@ _Py_Finalize(_PyRuntimeState *runtime)
22102212
int status = 0;
22112213

22122214
/* Bail out early if already finalized (or never initialized). */
2213-
if (!runtime->initialized) {
2215+
if (!_PyRuntimeState_GetInitialized(runtime)) {
22142216
return status;
22152217
}
22162218

@@ -2245,8 +2247,8 @@ _Py_Finalize(_PyRuntimeState *runtime)
22452247
when they attempt to take the GIL (ex: PyEval_RestoreThread()). */
22462248
_PyInterpreterState_SetFinalizing(tstate->interp, tstate);
22472249
_PyRuntimeState_SetFinalizing(runtime, tstate);
2248-
runtime->initialized = 0;
2249-
runtime->core_initialized = 0;
2250+
_PyRuntimeState_SetInitialized(runtime, 0);
2251+
_PyRuntimeState_SetCoreInitialized(runtime, 0);
22502252

22512253
// XXX Call something like _PyImport_Disable() here?
22522254

@@ -2472,7 +2474,7 @@ new_interpreter(PyThreadState **tstate_p,
24722474
}
24732475
_PyRuntimeState *runtime = &_PyRuntime;
24742476

2475-
if (!runtime->initialized) {
2477+
if (!_PyRuntimeState_GetInitialized(runtime)) {
24762478
return _PyStatus_ERR("Py_Initialize must be called first");
24772479
}
24782480

@@ -3312,10 +3314,10 @@ fatal_error_dump_runtime(int fd, _PyRuntimeState *runtime)
33123314
_Py_DumpHexadecimal(fd, (uintptr_t)finalizing, sizeof(finalizing) * 2);
33133315
PUTS(fd, ")");
33143316
}
3315-
else if (runtime->initialized) {
3317+
else if (_PyRuntimeState_GetInitialized(runtime)) {
33163318
PUTS(fd, "initialized");
33173319
}
3318-
else if (runtime->core_initialized) {
3320+
else if (_PyRuntimeState_GetCoreInitialized(runtime)) {
33193321
PUTS(fd, "core initialized");
33203322
}
33213323
else if (runtime->preinitialized) {

Python/pystate.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ init_runtime(_PyRuntimeState *runtime,
330330
{
331331
assert(!runtime->preinitializing);
332332
assert(!runtime->preinitialized);
333-
assert(!runtime->core_initialized);
334-
assert(!runtime->initialized);
333+
assert(!_PyRuntimeState_GetCoreInitialized(runtime));
334+
assert(!_PyRuntimeState_GetInitialized(runtime));
335335
assert(!runtime->_initialized);
336336

337337
runtime->open_code_hook = open_code_hook;

Python/sysmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Data members:
3333
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
3434
#include "pycore_pymem.h" // _PyMem_DefaultRawFree()
3535
#include "pycore_pystate.h" // _PyThreadState_GET()
36+
#include "pycore_runtime.h" // _PyRuntimeState_GetCoreInitialized()
3637
#include "pycore_pystats.h" // _Py_PrintSpecializationStats()
3738
#include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags()
3839
#include "pycore_sysmodule.h" // export _PySys_GetSizeOf()
@@ -471,7 +472,7 @@ PySys_AddAuditHook(Py_AuditHookFunction hook, void *userData)
471472
PySys_AddAuditHook() can be called before Python is initialized. */
472473
_PyRuntimeState *runtime = &_PyRuntime;
473474
PyThreadState *tstate;
474-
if (runtime->initialized) {
475+
if (_PyRuntimeState_GetCoreInitialized(runtime)) {
475476
tstate = _PyThreadState_GET();
476477
}
477478
else {

0 commit comments

Comments
 (0)