Skip to content

Free-threading: data race on static cache in _datetime.normalize_century() #159458

Open

Description

@AnnaAr321

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

Activity

  1. AnnaAr321 commented on Oct 15, 2026

    @AnnaAr321
    ContributorAuthor

    I have prepared a fix for it and will mail it promptly

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 15, 2026
  3. encukou commented on Oct 15, 2026

    @encukou
    Member

    I have prepared a fix for it and will mail it promptly

    Thank you; please do!

  4. added and removed
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 15, 2026
  5. added a commit that references this issue on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions