Skip to content

Commit 2af21a1

Browse files
authored
Merge pull request #14 from LazyImportsCabal/fix
Fix bug in specialization and make reification atomic
2 parents cdec6a6 + 2e19765 commit 2af21a1

File tree

2 files changed

+30
-5
lines changed

2 files changed

+30
-5
lines changed

Python/import.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3712,20 +3712,25 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
37123712
assert(PyLazyImport_CheckExact(lazy_import));
37133713

37143714
PyLazyImportObject *lz = (PyLazyImportObject *)lazy_import;
3715+
PyInterpreterState *interp = tstate->interp;
3716+
3717+
// Acquire the global import lock to serialize reification
3718+
_PyImport_AcquireLock(interp);
37153719

37163720
// Check if we are already importing this module, if so, then we want to return an error
37173721
// that indicates we've hit a cycle which will indicate the value isn't yet available.
3718-
PyInterpreterState *interp = tstate->interp;
37193722
PyObject *importing = interp->imports.lazy_importing_modules;
37203723
if (importing == NULL) {
37213724
importing = interp->imports.lazy_importing_modules = PySet_New(NULL);
37223725
if (importing == NULL) {
3726+
_PyImport_ReleaseLock(interp);
37233727
return NULL;
37243728
}
37253729
}
37263730

37273731
int is_loading = PySet_Contains(importing, lazy_import);
37283732
if (is_loading < 0) {
3733+
_PyImport_ReleaseLock(interp);
37293734
return NULL;
37303735
} else if (is_loading == 1) {
37313736
PyObject *name = _PyLazyImport_GetName(lazy_import);
@@ -3735,8 +3740,10 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
37353740
PyErr_SetImportErrorSubclass(PyExc_ImportCycleError, errmsg, lz->lz_from, NULL);
37363741
Py_XDECREF(errmsg);
37373742
Py_XDECREF(name);
3743+
_PyImport_ReleaseLock(interp);
37383744
return NULL;
37393745
} else if (PySet_Add(importing, lazy_import) < 0) {
3746+
_PyImport_ReleaseLock(interp);
37403747
goto error;
37413748
}
37423749

@@ -3889,6 +3896,9 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import)
38893896
obj = NULL;
38903897
}
38913898

3899+
// Release the global import lock
3900+
_PyImport_ReleaseLock(interp);
3901+
38923902
Py_XDECREF(fromlist);
38933903
Py_XDECREF(import_func);
38943904
return obj;
@@ -4125,14 +4135,25 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, PyObject *builtin
41254135
PyObject *child = NULL;
41264136
PyObject *parent_module = NULL;
41274137
PyObject *parent_dict = NULL;
4128-
PyObject *lazy_modules = tstate->interp->imports.lazy_modules;
4138+
4139+
// Acquire import lock to safely initialize lazy_modules if needed
4140+
// This prevents a race where multiple threads could create different dicts
4141+
PyInterpreterState *interp = tstate->interp;
4142+
_PyImport_AcquireLock(interp);
4143+
4144+
PyObject *lazy_modules = interp->imports.lazy_modules;
41294145
if (lazy_modules == NULL) {
4130-
lazy_modules = tstate->interp->imports.lazy_modules = PyDict_New();
4146+
lazy_modules = interp->imports.lazy_modules = PyDict_New();
41314147
if (lazy_modules == NULL) {
4148+
_PyImport_ReleaseLock(interp);
41324149
return -1;
41334150
}
41344151
}
41354152

4153+
// Release the lock - we only needed it for initialization
4154+
// The dict operations below are thread-safe on their own
4155+
_PyImport_ReleaseLock(interp);
4156+
41364157
Py_INCREF(name);
41374158
while (true) {
41384159
Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, PyUnicode_GET_LENGTH(name), -1);

Python/specialize.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,10 +1708,14 @@ specialize_load_global_lock_held(
17081708
goto fail;
17091709
}
17101710
PyObject *value = NULL;
1711-
if (PyDict_GetItemRef(globals, name, &value) < 0 ||
1712-
(value != NULL && PyLazyImport_CheckExact(value))) {
1711+
if (PyDict_GetItemRef(globals, name, &value) < 0) {
1712+
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_EXPECTED_ERROR);
1713+
goto fail;
1714+
}
1715+
if (value != NULL && PyLazyImport_CheckExact(value)) {
17131716
SPECIALIZATION_FAIL(LOAD_GLOBAL, SPEC_FAIL_ATTR_MODULE_LAZY_VALUE);
17141717
Py_DECREF(value);
1718+
goto fail;
17151719
}
17161720
Py_XDECREF(value);
17171721
Py_ssize_t index = _PyDictKeys_StringLookup(globals_keys, name);

0 commit comments

Comments
 (0)