python / python/cpython

[Free-threading] `assert(!_Py_IsImmortal(op))` failure when BRC merge queue races with `sys.intern`

Open
#152,276 0 comments 1 reaction 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:

In the free-threaded build, there is a race condition where an object can be queued for Biased Reference Counting (BRC) merging, but become immortal before the queue is processed. This causes a fatal C-level assertion failure:

assert.h assertion failed at Objects/object.c:467 in Py_ssize_t _Py_ExplicitMergeRefcount(PyObject *, Py_ssize_t): !_Py_IsImmortal(op)

*** Check failure stack trace: ***
    @     _Py_ExplicitMergeRefcount
    @     merge_queued_objects
    @     _Py_brc_merge_refcounts
    @     _Py_HandlePending
    @     _PyEval_EvalFrameDefault

Reproduction. Fails when running under ASAN/TSAN and the free-threading build:

import threading
import sys
def test_race():
    # 1 million strings will trap Thread A inside map() for milliseconds,
    # giving Thread B plenty of time to queue references to strings that 
    # Thread A hasn't interned yet.
    strings = ["race_string_massive_" + str(j) for j in range(1000000)]
    
    # Give Thread B a copy of the list.
    shared = list(strings)
    
    def thread_b_func():
        # Thread B clears its list, reducing the shared refcount to 0.
        # Since Thread B is not the owner, they are placed in Thread A's merge queue.
        shared.clear()
    tb = threading.Thread(target=thread_b_func)
    tb.start()
    # Thread A executes entirely in C for milliseconds.
    # Strings are queued by Thread B, AND THEN made immortal by sys.intern!
    list(map(sys.intern, strings))
    tb.join()
if __name__ == "__main__":
    test_race()
CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs
  • gh-152277

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 with Objects/object.c at _Py_ExplicitMergeRefcount and trace the mentioned merge_queued_objects, _Py_brc_merge_refcounts, and _Py_HandlePending call path. Reproduce the race under ASAN or TSAN with the free-threaded build, then compare the behavior and resolution in linked PR gh-152277.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.