Commit 4f9a446f authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

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
parent b136f11f
...@@ -39,6 +39,7 @@ def _new_module(name): ...@@ -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: ...@@ -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,12 +157,16 @@ class _ModuleLockManager: ...@@ -159,12 +157,16 @@ 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: try:
lock = _module_locks[name]() lock = _module_locks[name]()
except KeyError: except KeyError:
pass lock = None
if lock is None: if lock is None:
if _thread is None: if _thread is None:
lock = _DummyModuleLock(name) lock = _DummyModuleLock(name)
...@@ -173,17 +175,19 @@ def _get_module_lock(name): ...@@ -173,17 +175,19 @@ def _get_module_lock(name):
def cb(_): def cb(_):
del _module_locks[name] del _module_locks[name]
_module_locks[name] = _weakref.ref(lock, cb) _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): ...@@ -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): ...@@ -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_): ...@@ -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()
if name not in sys.modules:
with _ModuleLockManager(name): with _ModuleLockManager(name):
if name not in sys.modules:
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, ...@@ -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);
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment