GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Free-threading race betwen two Py_SETREF · Issue #161714 · python/cpython

Free-threading race betwen two Py_SETREF #161714

New issue
Copy link
New issue
Copy link
Open
Open
Free-threading race betwen two Py_SETREF#161714
Copy link
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-free-threadingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@cmaloney
cmaloney
opened on Nov 9, 2026
Issue body actions

Bug report

This can be triggered with existing Python objects that use Py_SETREF or Py_XSETREF such as Exceptions (although slightly contrived):

import threading

e = Exception()

def worker():
    for _ in range(1_000_000):
        e.__init__(1)

threads = [threading.Thread(target=worker) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Gives on a 3.16 free-threaded debug build:

../cpython/Include/refcount.h:617: _Py_NegativeRefcount: Assertion failed: object has negative ref count
Enable tracemalloc to get the memory block allocation traceback

object address  : 0x2000204c3b0
object refcount : 0
object type     : 0x5623737ad5c8
object type name: tuple
object repr     : <refcnt 0 at 0x2000204c3b0>

Fatal Python error: _PyObject_AssertFailed: _PyObject_AssertFailed
Python runtime state: initialized

Stack (most recent call first):
  File "/<home>/projects/python/setref_bug.py", line 8 in worker
  File "/<home>/projects/python/cpython/Lib/threading.py", line 1161 in run
  File "/<home>/projects/python/cpython/Lib/threading.py", line 1219 in _bootstrap_inner
  File "/<home>/projects/python/cpython/Lib/threading.py", line 1181 in _bootstrap
[1]    397225 abort (core dumped)  ./build/python setref_bug.py

This arrives because Py_SETREF copies the ref and stores a new new one as two separate non-atomic steps. So two threads can do:

T1: old = *slot
T2: old = *slot      // same object as T1
T1: *slot = a
T2: *slot = b        // a's reference is lost, so a leaks
T1: Py_XDECREF(old)
T2: Py_XDECREF(old)  // the slot's one reference to old is dropped twice

I ran into this trying to build a cache which swapped an old value for a new value with Py_SETREF and finding it sometimes resulted in a double free when two threads swapped the value simultaneously (And ended up thinking they had the last reference).

CPython versions tested on:

3.16, CPython main branch

Operating systems tested on:

Linux

Activity

  1. cmaloney
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
    topic-free-threading
    on Nov 9, 2026
  2. colesbury commented on Nov 9, 2026

    @colesbury
    colesbury
    on Nov 9, 2026
    Contributor
    More actions

    Concurrent Py_SETREF(dst, src) / Py_XSETREF calls on the same dst are not intended to be thread-safe.

    Separately, we also generally don't try to make concurrent __init__() on the same object thread-safe unless there's some code in the wild that actually does this. We may revisit that in the future, but for now, it seems like it would add code complexity and performance regressions for little benefit.

  3. cmaloney commented on Nov 10, 2026

    @cmaloney
    cmaloney
    on Nov 10, 2026
    ContributorAuthor
    More actions

    I'm okay with that as a documented limitation.

    Having an atomic version that is opt-in would make lazy thread-safe caches significantly easier to write: init to Py_None or NULL then once make a cache dictionary just Py_SETREF_THREADSAFE or Py_XSETREF_THREADSAFE. Open to a helper like that?

    Across CPython currently there's quite a few descriptors which individual elements use these (ex. property___set_name___impl) where locking the whole object for the single setter likely doesn't make sense but having two threads setting .name concurrently not crash the interpreter would be nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)topic-free-threadingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.