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:
importdisimportgcimportthreadingGLOBAL_SET=Nonedefworker():
EVENT.wait()
obj=GLOBAL_SETforiinrange(100):
# Call set.add() which uses the critical sectionobj.add(i)
MARKER=b"MARKER".decode()
EVENT=threading.Event()
classEvilHash:
def__init__(self, hash_value):
self.hash_value=hash_valuedef__hash__(self):
globalGLOBAL_SET, EVENTifGLOBAL_SETisNone:
# Invoke the evil gc.get_objects()!forobjingc.get_objects():
ifisinstance(obj, set) andMARKERinobj:
GLOBAL_SET=objEVENT.set()
returnself.hash_valuedef__repr__(self):
returnf"EvilHash({self.hash_value})"deffunc():
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 sectionEvilHash(0), EvilHash(1), EvilHash(2), EvilHash(3), EvilHash(4),
EvilHash(5)}
print(build_set)
print("Length:", len(build_set))
iflen(build_set) !=107:
raiseException("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.
Hi,
While reviewing PR gh-152273, I moved
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(so);check toset_add_entry_takeref()to check for bugs. I discovered thatSET_ADDopcode 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_APPENDhas a similar design (also omit locking).The following code uses the evil
gc.get_objects()function to callset.add()in a different thread while the main thread is building a set: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-sanitizerdoesn't show any warning.