Skip to content

Commit 78825e0

Browse files
committed
importlib: fix data race in imports (PyImport_ImportModuleLevelObject)
PyImport_ImportModuleLevelObject previously used the variable module.__spec__._initializing to check if a module is currently being initialized. This access could race with modifications to the module's dict. Instead, use the new md_initialized field on PyModule to check if the module is already initialized. This can mean that a duck-typed module doesn't get the benefit of the fast-path, but I think other than that the change should still preserve the important behavior.
1 parent f1e4742 commit 78825e0

13 files changed

+136
-76
lines changed

Include/internal/pycore_global_objects_fini_generated.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

+1
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ struct _Py_global_strings {
449449
STRUCT_FOR_ID(initial)
450450
STRUCT_FOR_ID(initial_bytes)
451451
STRUCT_FOR_ID(initial_value)
452+
STRUCT_FOR_ID(initialized)
452453
STRUCT_FOR_ID(initval)
453454
STRUCT_FOR_ID(inner_size)
454455
STRUCT_FOR_ID(input)

Include/internal/pycore_import.h

+1-5
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@ struct _import_runtime_state {
2222
Modules are added there and looked up in _imp.find_extension(). */
2323
PyObject *extensions;
2424
/* The global import lock. */
25-
struct {
26-
PyThread_type_lock mutex;
27-
unsigned long thread;
28-
int level;
29-
} lock;
25+
_PyRecursiveMutex lock;
3026
struct {
3127
int import_level;
3228
_PyTime_t accumulated;

Include/internal/pycore_moduleobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ typedef struct {
1616
PyObject *md_weaklist;
1717
// for logging purposes after md_dict is cleared
1818
PyObject *md_name;
19+
int md_initialized;
1920
} PyModuleObject;
2021

2122
static inline PyModuleDef* _PyModule_GetDef(PyObject *mod) {

Include/internal/pycore_runtime_init.h

-5
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ extern "C" {
3535
}, \
3636
.parser = _parser_runtime_state_INIT, \
3737
.imports = { \
38-
.lock = { \
39-
.mutex = NULL, \
40-
.thread = PYTHREAD_INVALID_THREAD_ID, \
41-
.level = 0, \
42-
}, \
4338
.find_and_load = { \
4439
.header = 1, \
4540
}, \

Include/internal/pycore_runtime_init_generated.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/moduleobject.h

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ PyAPI_FUNC(PyObject *) PyModule_GetFilenameObject(PyObject *);
3030
#ifndef Py_LIMITED_API
3131
PyAPI_FUNC(void) _PyModule_Clear(PyObject *);
3232
PyAPI_FUNC(void) _PyModule_ClearDict(PyObject *);
33+
PyAPI_FUNC(int) _PyModule_IsInitialized(PyObject *);
34+
PyAPI_FUNC(void) _PyModule_SetInitialized(PyObject *self, int initialized);
3335
PyAPI_FUNC(int) _PyModuleSpec_IsInitializing(PyObject *);
3436
#endif
3537
PyAPI_FUNC(PyModuleDef*) PyModule_GetDef(PyObject*);

Lib/importlib/_bootstrap.py

+2
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ def _load_unlocked(spec):
671671
# (otherwise an optimization shortcut in import.c becomes
672672
# wrong).
673673
spec._initializing = True
674+
_imp.module_initialized(module, False)
674675
try:
675676
sys.modules[spec.name] = module
676677
try:
@@ -692,6 +693,7 @@ def _load_unlocked(spec):
692693
# their own.
693694
module = sys.modules.pop(spec.name)
694695
sys.modules[spec.name] = module
696+
_imp.module_initialized(module, True)
695697
_verbose_message('import {!r} # {!r}', spec.name, spec.loader)
696698
finally:
697699
spec._initializing = False

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,7 @@ def get_gen(): yield 1
14691469
check(int(PyLong_BASE**2-1), vsize('') + 2*self.longdigit)
14701470
check(int(PyLong_BASE**2), vsize('') + 3*self.longdigit)
14711471
# module
1472-
check(unittest, size('PnPPP'))
1472+
check(unittest, size('PnPPPi'))
14731473
# None
14741474
check(None, size(''))
14751475
# NotImplementedType

Objects/moduleobject.c

+23-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ PyModuleDef_Init(PyModuleDef* def)
5151
return (PyObject*)def;
5252
}
5353

54+
int
55+
_PyModule_IsInitialized(PyObject *self)
56+
{
57+
assert(PyModule_Check(self));
58+
PyModuleObject *mod = (PyModuleObject*)self;
59+
return _Py_atomic_load_int(&mod->md_initialized);
60+
}
61+
62+
void
63+
_PyModule_SetInitialized(PyObject *self, int initialized)
64+
{
65+
assert(PyModule_Check(self));
66+
PyModuleObject *mod = (PyModuleObject*)self;
67+
_Py_atomic_store_int(&mod->md_initialized, initialized);
68+
}
69+
5470
static int
5571
module_init_dict(PyModuleObject *mod, PyObject *md_dict,
5672
PyObject *name, PyObject *doc)
@@ -70,7 +86,9 @@ module_init_dict(PyModuleObject *mod, PyObject *md_dict,
7086
if (PyDict_SetItem(md_dict, &_Py_ID(__spec__), Py_None) != 0)
7187
return -1;
7288
if (PyUnicode_CheckExact(name)) {
73-
Py_XSETREF(mod->md_name, Py_NewRef(name));
89+
Py_INCREF(name);
90+
PyUnicode_InternInPlace(&name);
91+
Py_XSETREF(mod->md_name, name);
7492
}
7593

7694
return 0;
@@ -111,6 +129,7 @@ PyModule_NewObject(PyObject *name)
111129
PyModuleObject *m = new_module_notrack(&PyModule_Type);
112130
if (m == NULL)
113131
return NULL;
132+
m->md_initialized = 1;
114133
if (module_init_dict(m, m->md_dict, name, NULL) != 0)
115134
goto fail;
116135
PyObject_GC_Track(m);
@@ -125,7 +144,7 @@ PyObject *
125144
PyModule_New(const char *name)
126145
{
127146
PyObject *nameobj, *module;
128-
nameobj = PyUnicode_FromString(name);
147+
nameobj = PyUnicode_InternFromString(name);
129148
if (nameobj == NULL)
130149
return NULL;
131150
module = PyModule_NewObject(nameobj);
@@ -254,6 +273,7 @@ _PyModule_CreateInitialized(PyModuleDef* module, int module_api_version)
254273
}
255274
}
256275
m->md_def = module;
276+
m->md_initialized = 1;
257277
return (PyObject*)m;
258278
}
259279

@@ -274,6 +294,7 @@ PyModule_FromDefAndSpec2(PyModuleDef* def, PyObject *spec, int module_api_versio
274294
if (nameobj == NULL) {
275295
return NULL;
276296
}
297+
PyUnicode_InternInPlace(&nameobj);
277298
name = PyUnicode_AsUTF8(nameobj);
278299
if (name == NULL) {
279300
goto error;

Python/clinic/import.c.h

+62-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)