Free-threading race betwen two Py_SETREF

Ouverte
#161,714 3 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
5/5
Temps estimé
Plus d'une semaine
Accessibilité débutants
22/100
Type d'issue
Bug
Clarté
Plutôt claire
Activité
Active
Stack technique
c, python
Domaine
backend

Piste de recherche

The race is in the Py_SETREF and Py_XSETREF macros, which copy the old pointer, store the new one, and then decref in separate non-atomic steps. Run the reproducer with a free-threaded debug build and read the assertion at Include/refcount.h:617 to confirm the negative refcount on a tuple. Done would mean the reproducer runs clean under free-threading, which needs a design decision about atomic swap semantics first.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

interpreter-core topic-free-threading type-bug
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

Langage dominant
Python
Étoiles
78.2k
Forks
36.6k
Merge moyen
19 h 57 min
PR mergées (30 j)
630

Préparer son environnement

Ouvrir dans Codespaces

Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de python/cpython

Toutes les issues de python/cpython

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.