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
Data race on instr->op.code in _Py_Specialize_Resume() with tlbc disabled (TSan CI failure in test_thread_local_bytecode) #157194
FAIL: test_no_copies_if_tlbc_disabled (test.test_thread_local_bytecode.TLBCTests.test_no_copies_if_tlbc_disabled)
assert_python_ok("-X", "tlbc=0", "-c", code)
AssertionError: Process return code is 66
stdout:
---
---
stderr:
---
---
The empty output and the return code are TSan: the job sets TSAN_OPTIONS=log_path=.../san_log, so the report goes to a file rather than stderr, and 66 is TSan's default exit code. The report is in the run's TSan-logs-free-threading artifact, as san_log.test_thread_local_bytecode.17102:
WARNING: ThreadSanitizer: data race (pid=17102)
Read of size 1 at 0x7fe6eaa078f8 by thread T2:
#0 _Py_Specialize_Resume Python/specialize.c:2957:43
#1 _PyEval_EvalFrameDefault Python/generated_cases.c.h:11357:17
...
#24 thread_run ./Modules/_threadmodule.c:388:21
Previous atomic write of size 1 at 0x7fe6eaa078f8 by main thread:
#0 _Py_atomic_compare_exchange_uint8 ./Include/cpython/pyatomic_gcc.h:105:10
#1 set_opcode Python/specialize.c:334:10
#2 unspecialize Python/specialize.c:383:10
#3 _Py_Specialize_Resume Python/specialize.c:2971:5
...
Both stacks are _Py_Specialize_Resume() on the same instruction, one from a worker thread and one from the main thread. The write side is atomic; the read side is not:
Everything else in specialize.c treats instr->op.code as an atomic location in the free-threaded build. set_opcode() writes it with _Py_atomic_compare_exchange_uint8(), and unspecialize() reads it with FT_ATOMIC_LOAD_UINT8_RELAXED(). That convention arrived with the helpers introduced in gh-115999 (9ce4fa0, "Introduce helpers for (un)specializing instructions").
The plain read above post-dates it: it was added in 2026 by 3d0824a (gh-127958, "Trace from RESUME in the JIT"). There is a second one of the same shape in _Py_Specialize_BinaryOp(), added in 2025 by 3893a92 (gh-100239):
The race needs two threads specializing the same instruction. In a free-threaded build each thread normally gets its own copy of the bytecode, so this does not happen; with -X tlbc=0 the copies are disabled and the bytecode is shared, which is exactly what test_no_copies_if_tlbc_disabled runs. That is why this test is the one that trips it.
Activity
gh-157194: Read the opcode atomically in the specializer (#157195)