Uh oh!
There was an error while loading. Please reload this page.
gh-142349: Fix refcount corruption in lazy import specialization - #144733
Conversation
3007570 to
239c717CompareUh oh!
There was an error while loading. Please reload this page.
Yhg1s
left a comment
There was a problem hiding this comment.
I don't think this is good enough for a free-threaded build. The error case happens when _PyImport_LoadLazyImportTstate is called with a borrowed reference. The GIL won't be protecting that borrowed reference in a free-threaded build, so the INCREF already happens too late. We need the caller to acquire an owned reference instead, in a safe way. But... where does this happen? As far as I can tell the callers all own the reference they pass.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
pablogsal
commented
Feb 12, 2026
If that were true then there would not be crashes no? |
Yhg1s
commented
Feb 12, 2026
Unless the problem is actually something different, yes :) I would like to know which it is. |
ca6f0f9 to
2fe3763Compare_PyDict_LookupIndexAndValue() returns a borrowed reference via _Py_dict_lookup(), but specialize_load_global_lock_held() called Py_DECREF(value) on it when bailing out for lazy imports. Each time the adaptive counter fired while a lazy import was still in globals, this stole one reference from the dict's object. With 8+ threads racing through LOAD_GLOBAL during concurrent lazy import resolution, enough triggers accumulated to drive the refcount to zero while the dict and other threads still referenced the object, causing use-after-free.
2fe3763 to
c7defe2Comparepablogsal
commented
Feb 12, 2026
Narrator: and the problem was indeed something different. Kind of |
Uh oh!
There was an error while loading. Please reload this page.
python#144733) Remove spurious Py_DECREF on borrowed ref in LOAD_GLOBAL specialization _PyDict_LookupIndexAndValue() returns a borrowed reference via _Py_dict_lookup(), but specialize_load_global_lock_held() called Py_DECREF(value) on it when bailing out for lazy imports. Each time the adaptive counter fired while a lazy import was still in globals, this stole one reference from the dict's object. With 8+ threads racing through LOAD_GLOBAL during concurrent lazy import resolution, enough triggers accumulated to drive the refcount to zero while the dict and other threads still referenced the object, causing use-after-free.
Add a regression test ensuring that circular lazy imports raise a proper error (ImportCycleError) instead of crashing with a segfault. The crash was fixed in pythongh-144733 but no test was added at the time.
Add a regression test ensuring that circular lazy imports raise a proper error (ImportCycleError) instead of crashing with a segfault. The crash was fixed in pythongh-144733 but no test was added at the time.
python#144733) Remove spurious Py_DECREF on borrowed ref in LOAD_GLOBAL specialization _PyDict_LookupIndexAndValue() returns a borrowed reference via _Py_dict_lookup(), but specialize_load_global_lock_held() called Py_DECREF(value) on it when bailing out for lazy imports. Each time the adaptive counter fired while a lazy import was still in globals, this stole one reference from the dict's object. With 8+ threads racing through LOAD_GLOBAL during concurrent lazy import resolution, enough triggers accumulated to drive the refcount to zero while the dict and other threads still referenced the object, causing use-after-free.
…) (python#144838) Add a regression test ensuring that circular lazy imports raise a proper error (ImportCycleError) instead of crashing with a segfault. The crash was fixed in pythongh-144733 but no test was added at the time.
python#144733) Remove spurious Py_DECREF on borrowed ref in LOAD_GLOBAL specialization _PyDict_LookupIndexAndValue() returns a borrowed reference via _Py_dict_lookup(), but specialize_load_global_lock_held() called Py_DECREF(value) on it when bailing out for lazy imports. Each time the adaptive counter fired while a lazy import was still in globals, this stole one reference from the dict's object. With 8+ threads racing through LOAD_GLOBAL during concurrent lazy import resolution, enough triggers accumulated to drive the refcount to zero while the dict and other threads still referenced the object, causing use-after-free.
…) (python#144838) Add a regression test ensuring that circular lazy imports raise a proper error (ImportCycleError) instead of crashing with a segfault. The crash was fixed in pythongh-144733 but no test was added at the time.
_PyDict_LookupIndexAndValue() returns a borrowed reference via _Py_dict_lookup(), but specialize_load_global_lock_held() called Py_DECREF(value) on it when bailing out for lazy imports. Each time the adaptive counter fired while a lazy import was still in globals, this stole one reference from the dict's object. With 8+ threads racing through LOAD_GLOBAL during concurrent lazy import resolution, enough triggers accumulated to drive the refcount to zero while the dict and other threads still referenced the object, causing use-after-free.