You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
PyCode_Addr2Line shouldn't lock in free threaded Python #148037
In #138664 we added a critical section on the code object in PyCode_Addr2Line to fix crashes involving profiling.
There are two problems with using a lock here:
Code objects are shared across threads and PyCode_Addr2Line is a frequently function, leading to contention during profiling.
This introduces a stop-the-world safe point in profiling code, which can cause bugs if the profiling code isn't expecting it. For example, PyTorch's profiler isn't robust to a stop-the-world event (or GIL release) happening during profiling, and this leads to crashes. The problem is essentially:
PyTorch assumes that after PyEval_SetProfileAllThreads(NULL, NULL); no threads are still in the PyTorch profile code. This is incorrect due to the PyCode_Addr2Line() critical section suspending the thread state in the middle of a profile
PyTorch frees a bunch of profiler state, but thread suspended on PyCode_Addr2Line() resumes and tries reading that state leading to a crash.
This is a bug in PyTorch, so I'm going to fix it there as well, but the critical section in PyCode_Addr2Line() makes it much more likely to encounter the bug.
Proposed fix to CPython
No critical section in PyCode_Addr2Line. Use atomic reads on co->_co_monitoring and co->_co_monitoring->lines
Use atomic stores to initialize co->_co_monitoring and co->_co_monitoring->lines and reorder the stores so that the contents are initialized before the pointer is stored.
I think update_instrumentation_data would also have to be modified to not mutate the existing _co_monitoring data so that concurrent readers can access fields without races, it should allocate a new _PyCoMonitoringData data every time instrumentation needs to be updated and old data should be freed with qsbr.
I think it might be simpler than that. As I understand it, once code->_co_monitoring->lines is initialized, it's no longer modified until the code object is deallocated.
So I think it would look something like:
Initialize_co_monitoring fields to NULL / zero before assigning to code->_co_monitoring
I created #148103 which removes the critical section. For initializing code->_co_monitoring->lines one complication is that initialize_lines calls _Py_GetBaseCodeUnit which internally may access code->_co_monitoring->lines which would be NULL as the lines is not stored in the code object yet. I am not 100% sure but I think this access should not happen because it only happens when opcode=INSTRUMENTED_LINE which should not be the case when initialize_lines is called.
Activity
kumaraditya303 commentedon Apr 4, 2026
I think
update_instrumentation_datawould also have to be modified to not mutate the existing_co_monitoringdata so that concurrent readers can access fields without races, it should allocate a new_PyCoMonitoringDatadata every time instrumentation needs to be updated and old data should be freed with qsbr.colesbury commentedon Apr 4, 2026
I think it might be simpler than that. As I understand it, once
code->_co_monitoring->linesis initialized, it's no longer modified until the code object is deallocated.So I think it would look something like:
_co_monitoringfields to NULL / zero before assigning tocode->_co_monitoringcode->_co_monitoring->linesI think the other parts of
update_instrumentation_datadon't need to be changed.I think it's also always safe, but less efficient, to fall back to the
_PyCode_CheckLineNumbercode path in_PyCode_Addr2Line.kumaraditya303 commentedon Apr 4, 2026
@colesbury Are you planning to work on this or should I work on this?
colesbury commentedon Apr 4, 2026
kumaraditya303 commentedon Apr 6, 2026
I created #148103 which removes the critical section. For initializing
code->_co_monitoring->linesone complication is thatinitialize_linescalls_Py_GetBaseCodeUnitwhich internally may accesscode->_co_monitoring->lineswhich would be NULL as the lines is not stored in the code object yet. I am not 100% sure but I think this access should not happen because it only happens whenopcode=INSTRUMENTED_LINEwhich should not be the case wheninitialize_linesis called.gh-148037: remove critical section from `PyCode_Addr2Line` (#148103)
[3.14] pythongh-148037: remove critical section from `PyCode_Addr2Lin…
[3.14] gh-148037: remove critical section from `PyCode_Addr2Line` (GH… (
colesbury commentedon Apr 14, 2026
I think this can be closed now
pythongh-148037: remove critical section from `PyCode_Addr2Line` (pyt…