python / python/cpython

LIST_APPEND and SET_ADD opcodes safety in Free Threading

Aberta
#152,288 5 comentários 0 reações 0 responsáveis Ver no GitHub

Ninguém assumiu esta issue ainda.

topic-free-threading
Linguagem predominante
Python
Estrelas
77.2k
Forks
36k
Métricas de merge de PRs
Métricas de PR pendentes

Descrição

Hi,

While reviewing PR gh-152273, I moved _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so); check to set_add_entry_takeref() to check for bugs. I discovered that SET_ADD opcode calls _PySet_AddTakeRef() without getting the set critical section. That's surprising and IMO it would deserve adding a comment to explain why it's safe to omit locking here.

LIST_APPEND has a similar design (also omit locking).


The following code uses the evil gc.get_objects() function to call set.add() in a different thread while the main thread is building a set:

import dis
import gc
import threading

GLOBAL_SET = None

def worker():
    EVENT.wait()
    obj = GLOBAL_SET
    for i in range(100):
        # Call set.add() which uses the critical section
        obj.add(i)


MARKER = b"MARKER".decode()
EVENT = threading.Event()

class EvilHash:
    def __init__(self, hash_value):
        self.hash_value = hash_value

    def __hash__(self):
        global GLOBAL_SET, EVENT
        if GLOBAL_SET is None:
            # Invoke the evil gc.get_objects()!
            for obj in gc.get_objects():
                if isinstance(obj, set) and MARKER in obj:
                    GLOBAL_SET = obj
                    EVENT.set()
        return self.hash_value

    def __repr__(self):
        return f"EvilHash({self.hash_value})"

def func():
    build_set = {
        # Use *list so following items are added by SET_ADD opcode
        *[MARKER],
        # Added by SET_ADD which calls _PySet_AddTakeRef()
        # without the critical section
        EvilHash(0), EvilHash(1), EvilHash(2), EvilHash(3), EvilHash(4),
        EvilHash(5)}

    print(build_set)
    print("Length:", len(build_set))
    if len(build_set) != 107:
        raise Exception("race condition!")

thread = threading.Thread(target=worker)
thread.start()
func()
thread.join()
#dis.dis(func)

I expected the code to fail randomly, but so far I failed to trigger a race condition on Free Threading. Running the code on Python built with --with-thread-sanitizer doesn't show any warning.

Guia de contribuição

Abrir o guia de contribuição

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Direção de pesquisa

Comece rastreando SET_ADD e LIST_APPEND por _PySet_AddTakeRef() e set_add_entry_takeref(), usando o reproducer com threads fornecido como verificação comportamental. Execute-o em um free-threaded build, incluindo o thread-sanitized build, e determine se omitir o critical section é seguro. Considera-se concluído quando a justificativa de segurança estiver estabelecida e qualquer explicação ou correção necessária estiver contemplada.

Escrita pelo modelo de indexação a partir do texto da issue.

Avaliação

Stack de tecnologia
python
Domínio
backend
Tipo de issue
Bug
Dificuldade
4/5
Tempo estimado
3-5 dias
Status de atividade
Pouca atividade
Clareza
Razoavelmente clara
Facilidade para iniciantes
48/100

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.