Skip to content

Commit fe6e686

Browse files
authored
bpo-30891: Fix importlib _find_and_load() race condition (#2646) (#2651)
* Rewrite importlib _get_module_lock(): it is now responsible to hold the imp lock directly. * _find_and_load() now holds the module lock to check if name is in sys.modules to prevent a race condition (cherry picked from commit 4f9a446)
1 parent 044e156 commit fe6e686

File tree

3 files changed

+1548
-1558
lines changed

3 files changed

+1548
-1558
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def _new_module(name):
3939
# Module-level locking ########################################################
4040

4141
# A dict mapping module names to weakrefs of _ModuleLock instances
42+
# Dictionary protected by the global import lock
4243
_module_locks = {}
4344
# A dict mapping thread ids to _ModuleLock instances
4445
_blocking_on = {}
@@ -144,10 +145,7 @@ def __init__(self, name):
144145
self._lock = None
145146

146147
def __enter__(self):
147-
try:
148-
self._lock = _get_module_lock(self._name)
149-
finally:
150-
_imp.release_lock()
148+
self._lock = _get_module_lock(self._name)
151149
self._lock.acquire()
152150

153151
def __exit__(self, *args, **kwargs):
@@ -159,31 +157,37 @@ def __exit__(self, *args, **kwargs):
159157
def _get_module_lock(name):
160158
"""Get or create the module lock for a given module name.
161159
162-
Should only be called with the import lock taken."""
163-
lock = None
160+
Acquire/release internally the global import lock to protect
161+
_module_locks."""
162+
163+
_imp.acquire_lock()
164164
try:
165-
lock = _module_locks[name]()
166-
except KeyError:
167-
pass
168-
if lock is None:
169-
if _thread is None:
170-
lock = _DummyModuleLock(name)
171-
else:
172-
lock = _ModuleLock(name)
173-
def cb(_):
174-
del _module_locks[name]
175-
_module_locks[name] = _weakref.ref(lock, cb)
165+
try:
166+
lock = _module_locks[name]()
167+
except KeyError:
168+
lock = None
169+
170+
if lock is None:
171+
if _thread is None:
172+
lock = _DummyModuleLock(name)
173+
else:
174+
lock = _ModuleLock(name)
175+
def cb(_):
176+
del _module_locks[name]
177+
_module_locks[name] = _weakref.ref(lock, cb)
178+
finally:
179+
_imp.release_lock()
180+
176181
return lock
177182

183+
178184
def _lock_unlock_module(name):
179-
"""Release the global import lock, and acquires then release the
180-
module lock for a given module name.
185+
"""Acquires then releases the module lock for a given module name.
186+
181187
This is used to ensure a module is completely initialized, in the
182188
event it is being imported by another thread.
183-
184-
Should only be called with the import lock taken."""
189+
"""
185190
lock = _get_module_lock(name)
186-
_imp.release_lock()
187191
try:
188192
lock.acquire()
189193
except _DeadlockError:
@@ -587,7 +591,6 @@ def _module_repr_from_spec(spec):
587591
def _exec(spec, module):
588592
"""Execute the spec's specified module in an existing module's namespace."""
589593
name = spec.name
590-
_imp.acquire_lock()
591594
with _ModuleLockManager(name):
592595
if sys.modules.get(name) is not module:
593596
msg = 'module {!r} not in sys.modules'.format(name)
@@ -670,7 +673,6 @@ def _load(spec):
670673
clobbered.
671674
672675
"""
673-
_imp.acquire_lock()
674676
with _ModuleLockManager(spec.name):
675677
return _load_unlocked(spec)
676678

@@ -957,16 +959,16 @@ def _find_and_load_unlocked(name, import_):
957959

958960
def _find_and_load(name, import_):
959961
"""Find and load the module."""
960-
_imp.acquire_lock()
961-
if name not in sys.modules:
962-
with _ModuleLockManager(name):
962+
with _ModuleLockManager(name):
963+
if name not in sys.modules:
963964
return _find_and_load_unlocked(name, import_)
965+
964966
module = sys.modules[name]
965967
if module is None:
966-
_imp.release_lock()
967968
message = ('import of {} halted; '
968969
'None in sys.modules'.format(name))
969970
raise ModuleNotFoundError(message, name=name)
971+
970972
_lock_unlock_module(name)
971973
return module
972974

Python/import.c

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,10 +1560,6 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
15601560
if (initializing == -1)
15611561
PyErr_Clear();
15621562
if (initializing > 0) {
1563-
#ifdef WITH_THREAD
1564-
_PyImport_AcquireLock();
1565-
#endif
1566-
/* _bootstrap._lock_unlock_module() releases the import lock */
15671563
value = _PyObject_CallMethodIdObjArgs(interp->importlib,
15681564
&PyId__lock_unlock_module, abs_name,
15691565
NULL);

0 commit comments

Comments
 (0)