Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34.1k
Closed
Description
When a module import fails, other threads can receive a stale module reference that is no longer insys.modules.
Race condition
- Thread 1 starts importing module X, adds it to
sys.modules - Thread 2 checks
sys.modules, finds module X, takes the "skip lock" fast path - Thread 1's import fails, removes module X from
sys.modules - Thread 2 returns the stale module reference (not in
sys.modules)
Affected code paths
Lib/importlib/_bootstrap.py:_find_and_load()lines 1268-1282Python/import.c:PyImport_GetModule()Python/import.c:PyImport_ImportModuleLevelObject()
All three have an optimization that skips locking when a module appears already loaded, but none verify the module is still valid after checking_initializing.
Reproducer
importsysimportthreadingimporttempfileimportoswithtempfile.TemporaryDirectory()astmpdir:sys.path.insert(0,tmpdir)# Module that partially initializes then failswithopen(os.path.join(tmpdir,"failing_mod.py"),"w")asf:f.write("import time; time.sleep(0.05); raise RuntimeError('fail')")results= []defdo_import(delay):importtime;time.sleep(delay)try:mod=__import__("failing_mod")if"failing_mod"notinsys.modules:results.append("RACE: got module not in sys.modules")exceptRuntimeError:results.append("ok")t1=threading.Thread(target=do_import,args=(0,))t2=threading.Thread(target=do_import,args=(0.01,))t1.start();t2.start();t1.join();t2.join()print(results)# May show "RACE" on affected builds