mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-30891: Fix importlib _find_and_load() race condition (#2646)
* 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
This commit is contained in:
parent
b136f11f3a
commit
4f9a446f3f
3 changed files with 1557 additions and 1568 deletions
|
@ -39,6 +39,7 @@ def _new_module(name):
|
||||||
# Module-level locking ########################################################
|
# Module-level locking ########################################################
|
||||||
|
|
||||||
# A dict mapping module names to weakrefs of _ModuleLock instances
|
# A dict mapping module names to weakrefs of _ModuleLock instances
|
||||||
|
# Dictionary protected by the global import lock
|
||||||
_module_locks = {}
|
_module_locks = {}
|
||||||
# A dict mapping thread ids to _ModuleLock instances
|
# A dict mapping thread ids to _ModuleLock instances
|
||||||
_blocking_on = {}
|
_blocking_on = {}
|
||||||
|
@ -144,10 +145,7 @@ class _ModuleLockManager:
|
||||||
self._lock = None
|
self._lock = None
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
self._lock = _get_module_lock(self._name)
|
||||||
self._lock = _get_module_lock(self._name)
|
|
||||||
finally:
|
|
||||||
_imp.release_lock()
|
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
def __exit__(self, *args, **kwargs):
|
||||||
|
@ -159,31 +157,37 @@ class _ModuleLockManager:
|
||||||
def _get_module_lock(name):
|
def _get_module_lock(name):
|
||||||
"""Get or create the module lock for a given module name.
|
"""Get or create the module lock for a given module name.
|
||||||
|
|
||||||
Should only be called with the import lock taken."""
|
Acquire/release internally the global import lock to protect
|
||||||
lock = None
|
_module_locks."""
|
||||||
|
|
||||||
|
_imp.acquire_lock()
|
||||||
try:
|
try:
|
||||||
lock = _module_locks[name]()
|
try:
|
||||||
except KeyError:
|
lock = _module_locks[name]()
|
||||||
pass
|
except KeyError:
|
||||||
if lock is None:
|
lock = None
|
||||||
if _thread is None:
|
|
||||||
lock = _DummyModuleLock(name)
|
if lock is None:
|
||||||
else:
|
if _thread is None:
|
||||||
lock = _ModuleLock(name)
|
lock = _DummyModuleLock(name)
|
||||||
def cb(_):
|
else:
|
||||||
del _module_locks[name]
|
lock = _ModuleLock(name)
|
||||||
_module_locks[name] = _weakref.ref(lock, cb)
|
def cb(_):
|
||||||
|
del _module_locks[name]
|
||||||
|
_module_locks[name] = _weakref.ref(lock, cb)
|
||||||
|
finally:
|
||||||
|
_imp.release_lock()
|
||||||
|
|
||||||
return lock
|
return lock
|
||||||
|
|
||||||
|
|
||||||
def _lock_unlock_module(name):
|
def _lock_unlock_module(name):
|
||||||
"""Release the global import lock, and acquires then release the
|
"""Acquires then releases the module lock for a given module name.
|
||||||
module lock for a given module name.
|
|
||||||
This is used to ensure a module is completely initialized, in the
|
This is used to ensure a module is completely initialized, in the
|
||||||
event it is being imported by another thread.
|
event it is being imported by another thread.
|
||||||
|
"""
|
||||||
Should only be called with the import lock taken."""
|
|
||||||
lock = _get_module_lock(name)
|
lock = _get_module_lock(name)
|
||||||
_imp.release_lock()
|
|
||||||
try:
|
try:
|
||||||
lock.acquire()
|
lock.acquire()
|
||||||
except _DeadlockError:
|
except _DeadlockError:
|
||||||
|
@ -587,7 +591,6 @@ def _module_repr_from_spec(spec):
|
||||||
def _exec(spec, module):
|
def _exec(spec, module):
|
||||||
"""Execute the spec's specified module in an existing module's namespace."""
|
"""Execute the spec's specified module in an existing module's namespace."""
|
||||||
name = spec.name
|
name = spec.name
|
||||||
_imp.acquire_lock()
|
|
||||||
with _ModuleLockManager(name):
|
with _ModuleLockManager(name):
|
||||||
if sys.modules.get(name) is not module:
|
if sys.modules.get(name) is not module:
|
||||||
msg = 'module {!r} not in sys.modules'.format(name)
|
msg = 'module {!r} not in sys.modules'.format(name)
|
||||||
|
@ -670,7 +673,6 @@ def _load(spec):
|
||||||
clobbered.
|
clobbered.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_imp.acquire_lock()
|
|
||||||
with _ModuleLockManager(spec.name):
|
with _ModuleLockManager(spec.name):
|
||||||
return _load_unlocked(spec)
|
return _load_unlocked(spec)
|
||||||
|
|
||||||
|
@ -957,16 +959,16 @@ def _find_and_load_unlocked(name, import_):
|
||||||
|
|
||||||
def _find_and_load(name, import_):
|
def _find_and_load(name, import_):
|
||||||
"""Find and load the module."""
|
"""Find and load the module."""
|
||||||
_imp.acquire_lock()
|
with _ModuleLockManager(name):
|
||||||
if name not in sys.modules:
|
if name not in sys.modules:
|
||||||
with _ModuleLockManager(name):
|
|
||||||
return _find_and_load_unlocked(name, import_)
|
return _find_and_load_unlocked(name, import_)
|
||||||
|
|
||||||
module = sys.modules[name]
|
module = sys.modules[name]
|
||||||
if module is None:
|
if module is None:
|
||||||
_imp.release_lock()
|
|
||||||
message = ('import of {} halted; '
|
message = ('import of {} halted; '
|
||||||
'None in sys.modules'.format(name))
|
'None in sys.modules'.format(name))
|
||||||
raise ModuleNotFoundError(message, name=name)
|
raise ModuleNotFoundError(message, name=name)
|
||||||
|
|
||||||
_lock_unlock_module(name)
|
_lock_unlock_module(name)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
|
@ -1559,10 +1559,6 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
|
||||||
if (initializing == -1)
|
if (initializing == -1)
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
if (initializing > 0) {
|
if (initializing > 0) {
|
||||||
#ifdef WITH_THREAD
|
|
||||||
_PyImport_AcquireLock();
|
|
||||||
#endif
|
|
||||||
/* _bootstrap._lock_unlock_module() releases the import lock */
|
|
||||||
value = _PyObject_CallMethodIdObjArgs(interp->importlib,
|
value = _PyObject_CallMethodIdObjArgs(interp->importlib,
|
||||||
&PyId__lock_unlock_module, abs_name,
|
&PyId__lock_unlock_module, abs_name,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
3063
Python/importlib.h
3063
Python/importlib.h
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue