Open
Description
Crash report
What happened?
Repro:
import concurrent.futures
import functools
import threading
import typing
def closure(b, c):
b.wait()
for i in range(10):
getattr(c, str(i), None)
setattr(c, str(i), 99)
with concurrent.futures.ThreadPoolExecutor(max_workers=32) as executor:
for _ in range(100):
class MyClass:
pass
b = threading.Barrier(32)
o = MyClass()
for j in range(32):
executor.submit(functools.partial(closure, b, o))
with an interpreter build with free threading and --with-pydebug
this will eventually crash with:
python: Objects/dictobject.c:1139: int compare_unicode_unicode(PyDictObject *, PyDictKeysObject *, void *, Py_ssize_t, PyObject *, Py_hash_t): Assertion `ep_key != NULL' failed.
From a JAX CI job that exhibited the problem, I have this stack trace:
#5 __tsan::CallUserSignalHandler(__tsan::ThreadState*, bool, bool, int, __sanitizer::__sanitizer_siginfo*, void*) tsan_interceptors_posix.cpp.o (python3.13+0xe5db5) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#6 _PyDictKeys_StringLookup /usr/local/google/home/phawkins/p/cpython/Objects/dictobject.c:1209:12 (python3.13+0x2f4fc5) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#7 _PyObject_TryGetInstanceAttribute /usr/local/google/home/phawkins/p/cpython/Objects/dictobject.c:6975:21 (python3.13+0x2f4fc5)
#8 _PyObject_GenericGetAttrWithDict /usr/local/google/home/phawkins/p/cpython/Objects/object.c:1676:17 (python3.13+0x320157) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#9 PyObject_GenericGetAttr /usr/local/google/home/phawkins/p/cpython/Objects/object.c:1751:12 (python3.13+0x31ff14) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#10 PyObject_GetAttr /usr/local/google/home/phawkins/p/cpython/Objects/object.c:1261:18 (python3.13+0x31f44a) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#11 _PyEval_EvalFrameDefault /usr/local/google/home/phawkins/p/cpython/Python/generated_cases.c.h:3770:28 (python3.13+0x4c00a0) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#12 _PyEval_EvalFrame /usr/local/google/home/phawkins/p/cpython/./Include/internal/pycore_ceval.h:119:16 (python3.13+0x4a8283) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#13 _PyEval_Vector /usr/local/google/home/phawkins/p/cpython/Python/ceval.c:1816:12 (python3.13+0x4a8283)
#14 _PyFunction_Vectorcall /usr/local/google/home/phawkins/p/cpython/Objects/call.c (python3.13+0x2595dc) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#15 _PyObject_VectorcallDictTstate /usr/local/google/home/phawkins/p/cpython/Objects/call.c:146:15 (python3.13+0x257864) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
#16 _PyObject_Call_Prepend /usr/local/google/home/phawkins/p/cpython/Objects/call.c:504:24 (python3.13+0x259a42) (BuildId: 9b1024b81603c5cc913dc4b031a6f189f128dafb)
I think what is happening here is that do_lookup
does an unlocked read of the index:
Line 1064 in b220c1c
and then the key:
Line 1138 in b220c1c
but a writer may set the index of a fresh entry before populating the key. For example, this writer would do that:
Line 1697 in b220c1c
(I don't know which writer is involved here.)
This is from the 3.13 branch at commit 341b86e .
CPython versions tested on:
3.13
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.13.3+ experimental free-threading build (heads/3.13:341b86e095e, Apr 23 2025, 21:51:53) [Clang 18.1.8 (16+build1)]