Repository navigation
Some operations on managed dict are not safe from memory_order POV #133980
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on May 13, 2025 - addedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
on May 13, 2025 I think these are fine:
1: GC traverse is called during stop the world (no other threads are running) so it doesn't need atomics
2: GC clear is called when the object is not accessible so it doesn't need atomics (same logic applies totp_dealloc)
3: This constructs a pointer to the dictionary; it doesn't load the dictionary_PyObject_InitInlineValues: object initialization doesn't need atomics (no other threads have access to those fields)
The
memory_order_seq_cststores can bememory_order_release, but it doesn't matter (except possibly for perf).memory_order_seq_cstis strictly stronger thanmemory_order_seq_cst. We don't care about a total order formemory_order_seq_cstacross stores to other memory locations here._PyObject_SetManagedDict: this is code in an
#ifdefthat only runs in the default GIL-enabled build (not the free threaded build)Reacted by Sergey Miryanovsergey-miryanov commented
on May 14, 2025 ContributorAuthorMore actionsThanks for explanation!
About
_PyObject_GetDictPtrit is called fromPyObject_GenericSetDictwhere store to ptr is made. It is guarded by CS, but I believe it should use atomic store too. WDYT?Lines 1933 to 1935 in 9ad0c7b
Py_BEGIN_CRITICAL_SECTION(obj); Py_XSETREF(*dictptr, Py_NewRef(value)); Py_END_CRITICAL_SECTION(); About _PyObject_GetDictPtr it is called from PyObject_GenericSetDict where store to ptr is made. It is guarded by CS, but I believe it should use atomic store too. WDYT?
Yes, it should use atomic store with release memory order, created #133988 to fix it.
Reproducer:
from threading import Thread e = Exception() def writer(): for i in range(10000): e.__dict__ = {1:2} def reader(): for i in range(10000): e.__dict__ t1 = Thread(target=writer) t2 = Thread(target=reader) t1.start() t2.start() t1.join() t2.join()
Reacted by Sergey Miryanov and Mikhail Efimov- added3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes
on May 14, 2025 sergey-miryanov commented
on May 14, 2025 ContributorAuthorMore actions@kumaraditya303 Thanks!
Bug report
Bug description:
First of all, I'm not a threading expert and my understanding of the memory-ordering model may be wrong. So, if I'm wrong I will be happy to fix my knowledge lacoons.
I saw some inconsistency (from my sight) of loading and writing of managed dict pointer.
Non-atomic loads:
PyObject_VisitManagedDictcpython/Objects/dictobject.c
Line 7202 in 9ad0c7b
PyObject_ClearManagedDictcpython/Objects/dictobject.c
Line 7462 in 9ad0c7b
_PyObject_GetDictPtrcpython/Objects/object.c
Line 1541 in 9ad0c7b
Non-atomic stores:
_PyObject_InitInlineValuescpython/Objects/dictobject.c
Lines 6787 to 6791 in 9ad0c7b
IIUC mixing of non-atomic loads/stores with atomic ones may lead to data races.
memory_order_acquireloads:_PyObject_GetManagedDictcpython/Include/internal/pycore_object.h
Lines 932 to 936 in 9ad0c7b
memory_order_releasestores:_PyObject_MaterializeManagedDict_LockHeldcpython/Objects/dictobject.c
Lines 6827 to 6829 in 9ad0c7b
store_instance_attr_lock_heldcpython/Objects/dictobject.c
Lines 6925 to 6927 in 9ad0c7b
ensure_managed_dictcpython/Objects/dictobject.c
Lines 7494 to 7495 in 9ad0c7b
memory_order_seq_cststores:set_dict_inline_valuescpython/Objects/dictobject.c
Line 7225 in 9ad0c7b
try_set_dict_inline_only_or_other_dictcpython/Objects/dictobject.c
Lines 7252 to 7253 in 9ad0c7b
replace_dict_probably_inline_materializedcpython/Objects/dictobject.c
Lines 7287 to 7289 in 9ad0c7b
IIUC mixing acquire/release with seq_cst may break total order of seq_cst operations.
Mixing with
memory_order_seq_cststores_PyObject_SetManagedDictcpython/Objects/dictobject.c
Lines 7356 to 7365 in 9ad0c7b
_PyObject_SetManagedDictuses non-atomic load but stores withseq_cstmode so it is OK (IIUC), but following store without fence may lead to data race.Are my findings valid or am I completely wrong?
cc @colesbury @kumaraditya303
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs
PyObject_GenericSetDict#133988PyObject_GenericSetDict(GH-133988) #134354