Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c703b7b

Browse filesBrowse files
[3.11] gh-117178: Recover lazy loading of self-referential modules (GH-117179) (#117320)
Co-authored-by: Chris Markiewicz <effigies@gmail.com>
1 parent 370a7f1 commit c703b7b
Copy full SHA for c703b7b

File tree

Expand file treeCollapse file tree

3 files changed

+25
-6
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+25
-6
lines changed

‎Lib/importlib/util.py

Copy file name to clipboardExpand all lines: Lib/importlib/util.py
+5-6Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,11 @@ def __getattribute__(self, attr):
232232
# Only the first thread to get the lock should trigger the load
233233
# and reset the module's class. The rest can now getattr().
234234
if object.__getattribute__(self, '__class__') is _LazyModule:
235-
# The first thread comes here multiple times as it descends the
236-
# call stack. The first time, it sets is_loading and triggers
237-
# exec_module(), which will access module.__dict__, module.__name__,
238-
# and/or module.__spec__, reentering this method. These accesses
239-
# need to be allowed to proceed without triggering the load again.
240-
if loader_state['is_loading'] and attr.startswith('__') and attr.endswith('__'):
235+
# Reentrant calls from the same thread must be allowed to proceed without
236+
# triggering the load again.
237+
# exec_module() and self-referential imports are the primary ways this can
238+
# happen, but in any case we must return something to avoid deadlock.
239+
if loader_state['is_loading']:
241240
return object.__getattribute__(self, attr)
242241
loader_state['is_loading'] = True
243242

‎Lib/test/test_importlib/test_lazy.py

Copy file name to clipboardExpand all lines: Lib/test/test_importlib/test_lazy.py
+18Lines 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()
+2Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.