From 857bdba0ac66ed7963e9fca45ab1a5f7a32b4bfb Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 18 Feb 2025 18:02:42 -0500 Subject: gh-130094: Fix race conditions in `importlib` (gh-130101) Entries may be added or removed from `sys.meta_path` concurrently. For example, setuptools temporarily adds and removes the `distutils` finder from the beginning of the list. The local copy ensures that we don't skip over any entries. Some packages modify `sys.modules` during import. For example, `collections` inserts the entry for `collections.abc` into `sys.modules` during import. We need to ensure that we re-check `sys.modules` *after* the parent module is fully initialized. --- Lib/importlib/_bootstrap.py | 15 ++++++++++++--- .../2025-02-14-00-32-52.gh-issue-130094.m3EF9E.rst | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-02-14-00-32-52.gh-issue-130094.m3EF9E.rst diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index aa7efa1..f563526 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1244,6 +1244,9 @@ def _find_spec(name, path, target=None): raise ImportError("sys.meta_path is None, Python is likely " "shutting down") + # gh-130094: Copy sys.meta_path so that we have a consistent view of the + # list while iterating over it. + meta_path = list(meta_path) if not meta_path: _warnings.warn('sys.meta_path is empty', ImportWarning) @@ -1298,7 +1301,6 @@ def _sanity_check(name, package, level): _ERR_MSG_PREFIX = 'No module named ' -_ERR_MSG = _ERR_MSG_PREFIX + '{!r}' def _find_and_load_unlocked(name, import_): path = None @@ -1308,8 +1310,9 @@ def _find_and_load_unlocked(name, import_): if parent not in sys.modules: _call_with_frames_removed(import_, parent) # Crazy side-effects! - if name in sys.modules: - return sys.modules[name] + module = sys.modules.get(name) + if module is not None: + return module parent_module = sys.modules[parent] try: path = parent_module.__path__ @@ -1317,6 +1320,12 @@ def _find_and_load_unlocked(name, import_): msg = f'{_ERR_MSG_PREFIX}{name!r}; {parent!r} is not a package' raise ModuleNotFoundError(msg, name=name) from None parent_spec = parent_module.__spec__ + if getattr(parent_spec, '_initializing', False): + _call_with_frames_removed(import_, parent) + # Crazy side-effects (again)! + module = sys.modules.get(name) + if module is not None: + return module child = name.rpartition('.')[2] spec = _find_spec(name, path) if spec is None: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-14-00-32-52.gh-issue-130094.m3EF9E.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-14-00-32-52.gh-issue-130094.m3EF9E.rst new file mode 100644 index 0000000..15d5831 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-14-00-32-52.gh-issue-130094.m3EF9E.rst @@ -0,0 +1,2 @@ +Fix two race conditions involving concurrent imports that could lead to +spurious failures with :exc:`ModuleNotFoundError`. -- cgit v0.12