python / python/cpython

Data race on instr->op.code in _Py_Specialize_Resume() with tlbc disabled (TSan CI failure in test_thread_local_bytecode)

Open
#157,194 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

interpreter-core topic-free-threading type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Bug report

Bug description:

The "Sanitizers / TSan (free-threading)" job of 90eb9f4bcd failed in test_thread_local_bytecode: https://github.com/python/cpython/actions/runs/34236688928/job/102096192233

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:

https://github.com/python/cpython/blob/main/Python/specialize.c#L2957

    if (tstate->tracing == 0 && instr->op.code == RESUME) {

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 (9ce4fa0719d, "Introduce helpers for (un)specializing instructions").

The plain read above post-dates it: it was added in 2026 by 3d0824aef26 (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 3893a92d956 (gh-100239):

https://github.com/python/cpython/blob/main/Python/specialize.c#L2351

    if (instr->op.code == BINARY_OP_EXTEND) {

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.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux (CI)

Linked PRs
  • gh-157195

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in Python/specialize.c at _Py_Specialize_Resume() around line 2957 and _Py_Specialize_BinaryOp() around line 2351, comparing nearby atomic accesses and the TSan report. Run test_thread_local_bytecode.TLBCTests.test_no_copies_if_tlbc_disabled with TSan; done means the shared-instruction paths no longer report a race.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.