Closed
Description
Bug report
Bug description:
Attempting to access an attribute of a lazily-loaded module causes the module's __class__
to be reset before its attributes have been populated.
import importlib.util import sys import threading import time # Lazy load http spec = importlib.util.find_spec("http") module = importlib.util.module_from_spec(spec) http = sys.modules["http"] = module loader = importlib.util.LazyLoader(spec.loader) loader.exec_module(module) def check(): time.sleep(0.2) return http.HTTPStatus.ACCEPTED == 202 def multicheck(): for _ in range(10): threading.Thread(target=check).start() if sys.argv[1:] == ["single"]: check() else: multicheck()
The issue is here:
Lines 168 to 177 in 6de8aa3
When attempting to access an attribute, the module's __dict__
is not updated until after __class__
is reset. If other threads attempt to access between these two points, then an attribute lookup can fail.
Assuming this is considered a bug, the two fixes I can think of are:
- A module-scoped lock that is used to protect
__getattribute__
's critical section. Theself.__class__ = type.ModuleType
would need to be moved below__dict__.update()
, which in turn would mean thatself.__spec__
andself.__dict__
would need to change toobject.__getattribute__(self, ...)
lookups to avoid recursion. - A module-scoped dictionary of locks, one-per-
_LazyModule
. Here, additional work would be needed to remove no-longer-needed locks without creating another critical section where a thread enters_LazyModule.__getattribute__
but looks up its lock after it is removed by the first thread.
My suspicion is that one lock is enough, so I would suggest going with 1.
CPython versions tested on:
3.8, 3.10, 3.11, 3.12
Operating systems tested on:
Linux