10000 [3.12] gh-117178: Recover lazy loading of self-referential modules (G… · python/cpython@552b264 · GitHub
[go: up one dir, main page]

Skip to content

Commit 552b264

Browse files
[3.12] gh-117178: Recover lazy loading of self-referential modules (GH-117179) (#117319)
Co-authored-by: Chris Markiewicz <effigies@gmail.com>
1 parent 8d42c57 commit 552b264

File tree

3 files changed

+25
-6
lines changed

3 files changed

+25
-6
lines changed

Lib/importlib/util.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,11 @@ def __getattribute__(self, attr):
178178
# Only the first thread to get the lock should trigger the load
179179
# and reset the module's class. The rest can now getattr().
180180
if object.__getattribute__(self, '__class__') is _LazyModule:
181-
# The first thread comes here multiple times as it descends the
182-
# call stack. The first time, it sets is_loading and triggers
183-
# exec_module(), which will access module.__dict__, module.__name__,
184-
# and/or module.__spec__, reentering this method. These accesses
185-
# need to be allowed to proceed without triggering the load again.
186-
if loader_state['is_loading'] and attr.startswith('__') and attr.endswith('__'):
181+
# Reentrant calls from the same thread must be allowed to proceed without
182+
# triggering the load again.
183+
# exec_module() and self-referential imports are the primary ways this can
184+
# happen, but in any case we must return something to avoid deadlock.
185+
if loader_state['is_loading']:
187186
return object.__getattribute__(self, attr)
188187
loader_state['is_loading'] = True
189188

Lib/test/test_importlib/test_lazy.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,24 @@ def access_module():
178178
# Or multiple load attempts
179179
self.assertEqual(loader.load_count, 1)
180180

181+
def test_lazy_self_referential_modules(self):
182+
# Directory modules with submodules that reference the parent can attempt to access
183+
# the parent module during a load. Verify that this common pattern works with lazy loading.
184+
# json is a good example in the stdlib.
185+
json_modules = [name for name in sys.modules if name.startswith('json')]
186+
with test_util.uncache(*json_modules):
187+
# Standard lazy loading, unwrapped
188+
spec = util.find_spec('json')
189+
loader = util.LazyLoader(spec.loader)
190+
spec.loader = loader
191+
module = util.module_from_spec(spec)
192+
sys.modules['json'] = module
193+
loader.exec_module(module)
194+
195+
# Trigger load with attribute lookup, ensure expected behavior
196+
test_load = module.loads('{}')
197+
self.assertEqual(test_load, {})
198+
181199

182200
if __name__ == '__main__':
183201
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix regression in lazy loading of self-referential modules, introduced in
2+
gh-114781.

0 commit comments

Comments
 (0)
0