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/