LIST_APPEND and SET_ADD opcodes safety in Free Threading
Dieses Issue hat noch niemand übernommen.
- Vorherrschende Sprache
- Python
- Sterne
- 77.2k
- Forks
- 35.9k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
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.
Beitragsleitfaden
Erste Schritte
- Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
- Forke das Repository und arbeite in einem Branch.
- Öffne einen Pull Request, der die Issue-Nummer nennt.
Rechercherichtung
Beginne damit, SET_ADD und LIST_APPEND durch _PySet_AddTakeRef() und set_add_entry_takeref() zu verfolgen, wobei der bereitgestellte Thread-Reproducer als Verhaltensprüfung dient. Führe ihn auf einem free-threaded build aus, einschließlich des thread-sanitized build, und ermittle, ob das Weglassen des critical section sicher ist. Als abgeschlossen gilt die Aufgabe, wenn die Begründung für die Sicherheit geklärt ist und jede erforderliche Erklärung oder Korrektur abgedeckt ist.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- backend
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Ruhig
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 48/100