summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-03-28 11:15:16 (GMT)
committerGitHub <noreply@github.com>2024-03-28 11:15:16 (GMT)
commit552b2646b75d079e9b31d4e800fbb2234624ed44 (patch)
treec38d67350c2c50072c6e117d497b32702d38334c
parent8d42c57789b11f838da40cdc3f129b2115368dd9 (diff)
downloadcpython-552b2646b75d079e9b31d4e800fbb2234624ed44.zip
cpython-552b2646b75d079e9b31d4e800fbb2234624ed44.tar.gz
cpython-552b2646b75d079e9b31d4e800fbb2234624ed44.tar.bz2
[3.12] gh-117178: Recover lazy loading of self-referential modules (GH-117179) (#117319)
Co-authored-by: Chris Markiewicz <effigies@gmail.com>
-rw-r--r--Lib/importlib/util.py11
-rw-r--r--Lib/test/test_importlib/test_lazy.py18
-rw-r--r--Misc/NEWS.d/next/Library/2024-03-23-14-26-18.gh-issue-117178.vTisTG.rst2
3 files changed, 25 insertions, 6 deletions
diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py
index f1bd064..3743e6a 100644
--- a/Lib/importlib/util.py
+++ b/Lib/importlib/util.py
@@ -178,12 +178,11 @@ class _LazyModule(types.ModuleType):
# Only the first thread to get the lock should trigger the load
# and reset the module's class. The rest can now getattr().
if object.__getattribute__(self, '__class__') is _LazyModule:
- # The first thread comes here multiple times as it descends the
- # call stack. The first time, it sets is_loading and triggers
- # exec_module(), which will access module.__dict__, module.__name__,
- # and/or module.__spec__, reentering this method. These accesses
- # need to be allowed to proceed without triggering the load again.
- if loader_state['is_loading'] and attr.startswith('__') and attr.endswith('__'):
+ # Reentrant calls from the same thread must be allowed to proceed without
+ # triggering the load again.
+ # exec_module() and self-referential imports are the primary ways this can
+ # happen, but in any case we must return something to avoid deadlock.
+ if loader_state['is_loading']:
return object.__getattribute__(self, attr)
loader_state['is_loading'] = True
diff --git a/Lib/test/test_importlib/test_lazy.py b/Lib/test/test_importlib/test_lazy.py
index 38ab219..4d2cc4e 100644
--- a/Lib/test/test_importlib/test_lazy.py
+++ b/Lib/test/test_importlib/test_lazy.py
@@ -178,6 +178,24 @@ class LazyLoaderTests(unittest.TestCase):
# Or multiple load attempts
self.assertEqual(loader.load_count, 1)
+ def test_lazy_self_referential_modules(self):
+ # Directory modules with submodules that reference the parent can attempt to access
+ # the parent module during a load. Verify that this common pattern works with lazy loading.
+ # json is a good example in the stdlib.
+ json_modules = [name for name in sys.modules if name.startswith('json')]
+ with test_util.uncache(*json_modules):
+ # Standard lazy loading, unwrapped
+ spec = util.find_spec('json')
+ loader = util.LazyLoader(spec.loader)
+ spec.loader = loader
+ module = util.module_from_spec(spec)
+ sys.modules['json'] = module
+ loader.exec_module(module)
+
+ # Trigger load with attribute lookup, ensure expected behavior
+ test_load = module.loads('{}')
+ self.assertEqual(test_load, {})
+
if __name__ == '__main__':
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2024-03-23-14-26-18.gh-issue-117178.vTisTG.rst b/Misc/NEWS.d/next/Library/2024-03-23-14-26-18.gh-issue-117178.vTisTG.rst
new file mode 100644
index 0000000..f9c53eb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-03-23-14-26-18.gh-issue-117178.vTisTG.rst
@@ -0,0 +1,2 @@
+Fix regression in lazy loading of self-referential modules, introduced in
+gh-114781.