Repository navigation
Free-threading: data race on static cache in _datetime.normalize_century() #159458
Copy link
Copy link
Open
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Under free-threaded builds with ThreadSanitizer, concurrent calls to datetime.date.strftime(), datetime.datetime.strftime(), or time.strftime() with %Y, %G, %F, or %C format specifiers trigger a data race on the function-local static int cache = -1; variable in normalize_century() in Modules/_datetimemodule.c.
Reproducer
Note: Because cache is only written on the first call when cache < 0, the race only triggers if strftime() has not already been called on the main thread earlier in the process.
import datetime
import threading
d = datetime.date(2026, 4, 12)
num_threads = 8
barrier = threading.Barrier(num_threads)
def worker():
barrier.wait()
d.strftime("%Y-%m-%d")
threads = [threading.Thread(target=worker) for _ in range(num_threads)]
for t in threads:
t.start()
for t in threads:
t.join()ThreadSanitizer output
WARNING: ThreadSanitizer: data race (pid=9244)
Read of size 4 at 0x7fdd2ad2ee60 by thread T40:
#0 normalize_century Modules/_datetimemodule.c:1762:9
#1 wrap_strftime Modules/_datetimemodule.c:1946:18
#2 date_strftime Modules/_datetimemodule.c:3592:14
#3 method_vectorcall_VARARGS_KEYWORDS Objects/descrobject.c:359:14
#4 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#5 PyObject_Vectorcall Objects/call.c:327:12
#6 _PyEval_EvalFrameDefault Python/generated_cases.c.h:1621:35
#7 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#8 _PyEval_Vector Python/ceval.c:2121:12
#9 _PyFunction_Vectorcall Objects/call.c
#10 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#11 _PyObject_VectorcallPrepend Objects/call.c:852:20
#12 method_vectorcall Objects/classobject.c:54:12
#13 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#14 context_run Python/context.c:722:29
#15 _PyEval_EvalFrameDefault Python/generated_cases.c.h:3762:35
#16 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#17 _PyEval_Vector Python/ceval.c:2121:12
#18 _PyFunction_Vectorcall Objects/call.c
#19 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#20 _PyObject_VectorcallPrepend Objects/call.c:852:20
#21 method_vectorcall Objects/classobject.c:54:12
#22 _PyVectorcall_Call Objects/call.c:273:16
#23 _PyObject_Call Objects/call.c:348:16
#24 PyObject_Call Objects/call.c:373:12
#25 thread_run Modules/_threadmodule.c:359:21
#26 pythread_wrapper Python/thread_pthread.h:242:5
Previous write of size 4 at 0x7fdd2ad2ee60 by thread T39:
#0 normalize_century Modules/_datetimemodule.c:1769:15
#1 wrap_strftime Modules/_datetimemodule.c:1946:18
#2 date_strftime Modules/_datetimemodule.c:3592:14
#3 method_vectorcall_VARARGS_KEYWORDS Objects/descrobject.c:359:14
#4 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#5 PyObject_Vectorcall Objects/call.c:327:12
#6 _PyEval_EvalFrameDefault Python/generated_cases.c.h:1621:35
#7 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#8 _PyEval_Vector Python/ceval.c:2121:12
#9 _PyFunction_Vectorcall Objects/call.c
#10 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#11 _PyObject_VectorcallPrepend Objects/call.c:852:20
#12 method_vectorcall Objects/classobject.c:54:12
#13 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#14 context_run Python/context.c:722:29
#15 _PyEval_EvalFrameDefault Python/generated_cases.c.h:3762:35
#16 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#17 _PyEval_Vector Python/ceval.c:2121:12
#18 _PyFunction_Vectorcall Objects/call.c
#19 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#20 _PyObject_VectorcallPrepend Objects/call.c:852:20
#21 method_vectorcall Objects/classobject.c:54:12
#22 _PyVectorcall_Call Objects/call.c:273:16
#23 _PyObject_Call Objects/call.c:348:16
#24 PyObject_Call Objects/call.c:373:12
#25 thread_run Modules/_threadmodule.c:359:21
#26 pythread_wrapper Python/thread_pthread.h:242:5
Location is global 'normalize_century.cache' of size 4 at 0x7fdd2ad2ee60 (libpython3.14.so+0x41fe60)
Thread T40 'Thread-40 (work' (tid=9289, running) created by main thread at:
#0 pthread_create tsan_interceptors_posix.cpp:1083:3
#1 do_start_joinable_thread Python/thread_pthread.h:289:14
#2 PyThread_start_joinable_thread Python/thread_pthread.h:331:9
#3 ThreadHandle_start Modules/_threadmodule.c:445:9
#4 do_start_new_thread Modules/_threadmodule.c:1868:9
#5 thread_PyThread_start_joinable_thread Modules/_threadmodule.c:1991:14
#6 cfunction_call Objects/methodobject.c:564:18
#7 _PyObject_MakeTpCall Objects/call.c:242:18
#8 _PyObject_VectorcallTstate Include/internal/pycore_call.h:175:16
#9 PyObject_Vectorcall Objects/call.c:327:12
#10 _PyEval_EvalFrameDefault Python/generated_cases.c.h:3241:35
#11 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#12 _PyEval_Vector Python/ceval.c:2121:12
#13 _PyFunction_Vectorcall Objects/call.c
#14 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#15 _PyObject_VectorcallPrepend Objects/call.c:874:18
#16 method_vectorcall Objects/classobject.c:54:12
#17 _PyVectorcall_Call Objects/call.c:273:16
#18 _PyObject_Call Objects/call.c:348:16
#19 PyObject_Call Objects/call.c:373:12
#20 _PyEval_EvalFrameDefault Python/generated_cases.c.h:2666:32
#21 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#22 _PyEval_Vector Python/ceval.c:2121:12
#23 _PyFunction_Vectorcall Objects/call.c
#24 _PyObject_VectorcallDictTstate Objects/call.c:135:15
#25 _PyObject_Call_Prepend Objects/call.c:504:24
#26 call_method Objects/typeobject.c:3080:19
#27 slot_tp_call Objects/typeobject.c:10590:12
#28 _PyObject_MakeTpCall Objects/call.c:242:18
#29 _PyObject_VectorcallTstate Include/internal/pycore_call.h:175:16
#30 PyObject_Vectorcall Objects/call.c:327:12
#31 _PyEval_EvalFrameDefault Python/generated_cases.c.h:4103:35
#32 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#33 _PyEval_Vector Python/ceval.c:2121:12
#34 _PyFunction_Vectorcall Objects/call.c
#35 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#36 _PyObject_VectorcallPrepend Objects/call.c:874:18
#37 method_vectorcall Objects/classobject.c:54:12
#38 _PyVectorcall_Call Objects/call.c:273:16
#39 _PyObject_Call Objects/call.c:348:16
#40 PyObject_Call Objects/call.c:373:12
#41 _PyEval_EvalFrameDefault Python/generated_cases.c.h:2666:32
#42 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#43 _PyEval_Vector Python/ceval.c:2121:12
#44 _PyFunction_Vectorcall Objects/call.c
#45 _PyObject_VectorcallDictTstate Objects/call.c:135:15
#46 _PyObject_Call_Prepend Objects/call.c:504:24
#47 call_method Objects/typeobject.c:3080:19
#48 slot_tp_call Objects/typeobject.c:10590:12
#49 _PyObject_MakeTpCall Objects/call.c:242:18
#50 _PyObject_VectorcallTstate Include/internal/pycore_call.h:175:16
#51 PyObject_Vectorcall Objects/call.c:327:12
#52 _PyEval_EvalFrameDefault Python/generated_cases.c.h:1621:35
#53 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#54 _PyEval_Vector Python/ceval.c:2121:12
#55 PyEval_EvalCode Python/ceval.c:992:21
#56 builtin_exec_impl Python/bltinmodule.c:1180:17
#57 builtin_exec Python/clinic/bltinmodule.c.h:571:20
#58 cfunction_vectorcall_FASTCALL_KEYWORDS Objects/methodobject.c:465:24
#59 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#60 PyObject_Vectorcall Objects/call.c:327:12
#61 _PyEval_EvalFrameDefault Python/generated_cases.c.h:1621:35
#62 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#63 _PyEval_Vector Python/ceval.c:2121:12
#64 _PyFunction_Vectorcall Objects/call.c
#65 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#66 _PyObject_CallFunctionVa Objects/call.c
#67 callmethod Objects/call.c:626:12
#68 PyObject_CallMethod Objects/call.c:645:24
#69 RunFileAsMain launcher_common.cc:147:12
#70 RunMainFromImporter launcher_common.cc:523:11
#71 Launcher_Main launcher_common.cc:790:14
#72 HermeticLauncherMain hermetic_launcher_lib.cc:113:10
#73 main hermetic_launcher.cc:4:10
Thread T39 'Thread-39 (work' (tid=9288, running) created by main thread at:
#0 pthread_create tsan_interceptors_posix.cpp:1083:3
#1 do_start_joinable_thread Python/thread_pthread.h:289:14
#2 PyThread_start_joinable_thread Python/thread_pthread.h:331:9
#3 ThreadHandle_start Modules/_threadmodule.c:445:9
#4 do_start_new_thread Modules/_threadmodule.c:1868:9
#5 thread_PyThread_start_joinable_thread Modules/_threadmodule.c:1991:14
#6 cfunction_call Objects/methodobject.c:564:18
#7 _PyObject_MakeTpCall Objects/call.c:242:18
#8 _PyObject_VectorcallTstate Include/internal/pycore_call.h:175:16
#9 PyObject_Vectorcall Objects/call.c:327:12
#10 _PyEval_EvalFrameDefault Python/generated_cases.c.h:3241:35
#11 _PyEval_EvalFrame Include/internal/pycore_ceval.h:120:16
#12 _PyEval_Vector Python/ceval.c:2121:12
#13 _PyFunction_Vectorcall Objects/call.c
#14 _PyObject_VectorcallTstate Include/internal/pycore_call.h:177:11
#15 _PyObject_VectorcallPrepend Objects/call.c:874:18
#16 method_vectorcall Objects/classobject.c:54:12
#17 _PyVectorcall_Call Objects/call.c:273:16
SUMMARY: ThreadSanitizer: data race Modules/_datetimemodule.c in normalize_century
CPython versions tested on:
3.14, free-threaded build
CPython versions tested on:
3.14
Operating systems tested on:
Linux
Linked PRs
- gh-159458: Fix free-threading data race in datetime strftime century normalization #159520
- [3.15] gh-159458: Fix free-threading data race in datetime strftime century normalization (GH-159520) #159536
- [3.14] gh-159458: Fix free-threading data race in datetime strftime century normalization (GH-159520) #159537
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Projects
- StatusShow more project fieldsNo status
Activity
AnnaAr321 commentedon Oct 15, 2026
I have prepared a fix for it and will mail it promptly
encukou commentedon Oct 15, 2026
Thank you; please do!
gh-159458: Fix free-threading data race in datetime strftime century …