-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
gh-114763: Protect lazy loading modules from attribute access race #114781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
d57f4eb
3db717c
5424d0c
96c08c5
3642ddc
46238e5
6accf0c
bb2292f
57fe084
40383a0
6322dd6
023d65d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -171,36 +171,49 @@ class _LazyModule(types.ModuleType): | |
|
|
||
| def __getattribute__(self, attr): | ||
| """Trigger the load of the module and return the attribute.""" | ||
| # All module metadata must be garnered from __spec__ in order to avoid | ||
| # using mutated values. | ||
| # Stop triggering this method. | ||
| self.__class__ = types.ModuleType | ||
| # Get the original name to make sure no object substitution occurred | ||
| # in sys.modules. | ||
| original_name = self.__spec__.name | ||
| # Figure out exactly what attributes were mutated between the creation | ||
| # of the module and now. | ||
| attrs_then = self.__spec__.loader_state['__dict__'] | ||
| attrs_now = self.__dict__ | ||
| attrs_updated = {} | ||
| for key, value in attrs_now.items(): | ||
| # Code that set the attribute may have kept a reference to the | ||
| # assigned object, making identity more important than equality. | ||
| if key not in attrs_then: | ||
| attrs_updated[key] = value | ||
| elif id(attrs_now[key]) != id(attrs_then[key]): | ||
| attrs_updated[key] = value | ||
| self.__spec__.loader.exec_module(self) | ||
| # If exec_module() was used directly there is no guarantee the module | ||
| # object was put into sys.modules. | ||
| if original_name in sys.modules: | ||
| if id(self) != id(sys.modules[original_name]): | ||
| raise ValueError(f"module object for {original_name!r} " | ||
| "substituted in sys.modules during a lazy " | ||
| "load") | ||
| # Update after loading since that's what would happen in an eager | ||
| # loading situation. | ||
| self.__dict__.update(attrs_updated) | ||
| __spec__ = object.__getattribute__(self, '__spec__') | ||
| loader_state = __spec__.loader_state | ||
| with loader_state['lock']: | ||
| if object.__getattribute__(self, '__class__') is _LazyModule: | ||
| # exec_module() will access dunder attributes, so we use a reentrant | ||
| # lock and an event to prevent infinite recursion. | ||
| if loader_state['is_loading'].is_set() and attr[:2] == attr[-2:] == '__': | ||
|
brettcannon marked this conversation as resolved.
Outdated
effigies marked this conversation as resolved.
Outdated
|
||
| return object.__getattribute__(self, attr) | ||
| loader_state['is_loading'].set() | ||
|
effigies marked this conversation as resolved.
Outdated
|
||
|
|
||
| __dict__ = object.__getattribute__(self, '__dict__') | ||
|
|
||
| # All module metadata must be garnered from __spec__ in order to avoid | ||
|
effigies marked this conversation as resolved.
Outdated
|
||
| # using mutated values. | ||
| # Get the original name to make sure no object substitution occurred | ||
| # in sys.modules. | ||
| original_name = __spec__.name | ||
| # Figure out exactly what attributes were mutated between the creation | ||
| # of the module and now. | ||
| attrs_then = loader_state['__dict__'] | ||
| attrs_now = __dict__ | ||
| attrs_updated = {} | ||
| for key, value in attrs_now.items(): | ||
| # Code that set the attribute may have kept a reference to the | ||
|
effigies marked this conversation as resolved.
Outdated
|
||
| # assigned object, making identity more important than equality. | ||
| if key not in attrs_then: | ||
| attrs_updated[key] = value | ||
| elif id(attrs_now[key]) != id(attrs_then[key]): | ||
| attrs_updated[key] = value | ||
| __spec__.loader.exec_module(self) | ||
| # If exec_module() was used directly there is no guarantee the module | ||
| # object was put into sys.modules. | ||
| if original_name in sys.modules: | ||
| if id(self) != id(sys.modules[original_name]): | ||
| raise ValueError(f"module object for {original_name!r} " | ||
| "substituted in sys.modules during a lazy " | ||
| "load") | ||
| # Update after loading since that's what would happen in an eager | ||
| # loading situation. | ||
| __dict__.update(attrs_updated) | ||
| # Finally, stop triggering this method. | ||
| self.__class__ = types.ModuleType | ||
|
|
||
| return getattr(self, attr) | ||
|
|
||
| def __delattr__(self, attr): | ||
|
|
@@ -235,6 +248,8 @@ def create_module(self, spec): | |
|
|
||
| def exec_module(self, module): | ||
| """Make the module load lazily.""" | ||
| import threading | ||
|
brettcannon marked this conversation as resolved.
Outdated
|
||
|
|
||
| module.__spec__.loader = self.loader | ||
| module.__loader__ = self.loader | ||
| # Don't need to worry about deep-copying as trying to set an attribute | ||
|
|
@@ -244,5 +259,7 @@ def exec_module(self, module): | |
| loader_state = {} | ||
| loader_state['__dict__'] = module.__dict__.copy() | ||
| loader_state['__class__'] = module.__class__ | ||
| loader_state['lock'] = threading.RLock() | ||
| loader_state['is_loading'] = threading.Event() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, can we use a Using an additional
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, good catch. I will switch to a bool and push later today. |
||
| module.__spec__.loader_state = loader_state | ||
| module.__class__ = _LazyModule | ||
Uh oh!
There was an error while loading. Please reload this page.