-
-
Notifications
You must be signed in to change notification settings - Fork 34.1k
gh-142349: Implement PEP 810 - Explicit lazy imports #142351
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
Conversation
…s on bad usage of lazy
Highlight lazy imports in the new REPL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The LOAD_GLOBAL specialization path in specialize_load_global_lock_held() called Py_DECREF(value) on the result of _PyDict_LookupIndexAndValue(), which returns a borrowed reference via _Py_dict_lookup(). This spurious DECREF silently corrupted the reference count of lazy import objects each time the adaptive specialization counter triggered while the lazy import was still present in the globals dict. This happens during concurrent lazy import reification with 8 or more threads in practice. The reason the thread count mattered is that with more threads racing through the barrier, more of them got a turn on the GIL to execute the LOAD_GLOBAL instruction (and trigger its adaptive specialization counter) while the first thread was still inside the import machinery resolving the lazy import. Each such trigger stole one reference from the lazy import object. With 2-7 threads, typically only 1-2 specialization triggers fired before the import completed, so the object survived. With 8+ threads, enough triggers accumulated (3 or more) to drive the reference count to zero while other threads still held pointers to the object, causing use-after-free. The fix removes the erroneous Py_DECREF from the specialization guard. The module attribute specialization path (specialize_module_load_attr) correctly handled the same case without a DECREF and served as confirmation that the LOAD_GLOBAL path was wrong. This commit also adds the lz_resolved field to PyLazyImportObject, which caches the resolved module after the first thread completes the import. Subsequent threads that were blocked on the import lock can then return the cached result immediately rather than re-importing. The _PyImport_LoadLazyImportTstate function now takes a strong reference to the lazy import at entry (to keep it alive across the GIL release inside _PyImport_AcquireLock) and releases it at every exit path, and a double-release of the import lock in the PySet_Add error path has also been corrected.
📚 Documentation preview 📚: https://cpython-previews--142351.org.readthedocs.build/