LIST_APPEND and SET_ADD opcodes safety in Free Threading
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Python
- Estrellas
- 77.2k
- Forks
- 35.9k
- Métricas de merge de PR
- Métricas de PR pendientes
Descripción
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.
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Comienza rastreando SET_ADD y LIST_APPEND a través de _PySet_AddTakeRef() y set_add_entry_takeref(), usando el reproducer con hilos proporcionado como comprobación del comportamiento. Ejecútalo en un free-threaded build, incluido el thread-sanitized build, y determina si omitir el critical section es seguro. Se considera terminado cuando se haya establecido la justificación de seguridad y se haya cubierto cualquier explicación o corrección necesaria.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- python
- Área
- backend
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Tranquilo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 48/100