Skip to content

[sanitizer] Stress workloads / fq http_api ThreadSanitizer / data race in free at Python interpreter shutdown (release-tsan) #50414

Closed as duplicate

Description

@robot-vibe-db

Summary

ThreadSanitizer data race in free during Python interpreter finalization (_Py_Finalize / free_threadstate) racing with drop_gil in a worker thread, in the py3-packaged test/stress binaries (release-tsan).

Triage

Environment

  • Branch: 26.2.1.ent.2
  • Commit: 616359c
  • Build: release-tsan

Reports

Affected tests

  • ydb/tests/stress/kafka/tests/test_kafka_streams.py.TestYdbTopicWorkload.test (try_1, try_2, try_3)
  • ydb/tests/stress/streaming/tests/test_workload.py.TestYdbWorkload.test (try_1, try_2, try_3)
  • ydb/tests/stress/transfer/tests/test_workload.py.TestYdbWorkload.test[column-local] (try_1, try_2, try_3)
  • ydb/tests/stress/transfer/tests/test_workload.py.TestYdbWorkload.test[column-remote] (try_1, try_2, try_3)
  • ydb/tests/stress/transfer/tests/test_workload.py.TestYdbWorkload.test[row-local] (try_1, try_2, try_3)
  • ydb/tests/fq/http_api/py3test (try_1, try_2, try_3; reported 2 warnings)

Error

Type: ThreadSanitizer data race in free during CPython interpreter finalization (drop_gil vs free_threadstate). The signature is identical across all affected py3 binaries (kafka_streams_test / streaming / transfer / ydb-tests-fq-http_api); the kafka_streams_test occurrence is shown verbatim below.

WARNING: ThreadSanitizer: data race (pid=1247856)
  Write of size 8 at 0x724800028818 by main thread:
    #0 free /-S/contrib/libs/clang20-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:741:3 (kafka_streams_test+0x51cf4aa) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #1 _PyMem_RawFree /-S/contrib/tools/python3/Objects/obmalloc.c:90:5 (kafka_streams_test+0x5679b79) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #2 PyMem_RawFree /-S/contrib/tools/python3/Objects/obmalloc.c:1005:5 (kafka_streams_test+0x567b748) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #3 free_threadstate /-S/contrib/tools/python3/Python/pystate.c:1476:9 (kafka_streams_test+0x5ce78b2) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #4 _PyThreadState_DeleteList /-S/contrib/tools/python3/Python/pystate.c:1940:9 (kafka_streams_test+0x5ce78b2)
    #5 _Py_Finalize /-S/contrib/tools/python3/Python/pylifecycle.c:2092:5 (kafka_streams_test+0x5cde713) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #6 Py_FinalizeEx /-S/contrib/tools/python3/Python/pylifecycle.c:2261:12 (kafka_streams_test+0x5cde583) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #7 pymain /-S/library/python/runtime_py3/main/main.c:232:9 (kafka_streams_test+0x77c738e) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #8 main /-S/library/python/runtime_py3/main/main.c:251:12 (kafka_streams_test+0x77c743d) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)

  Previous atomic read of size 8 at 0x724800028818 by thread T59:
    #0 _Py_atomic_load_uintptr_relaxed /-S/contrib/tools/python3/Include/cpython/pyatomic_gcc.h:347:10 (kafka_streams_test+0x5758d5c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #1 _Py_eval_breaker_bit_is_set /-S/contrib/tools/python3/Include/internal/pycore_ceval.h:292:19 (kafka_streams_test+0x5758d5c)
    #2 drop_gil /-S/contrib/tools/python3/Python/ceval_gil.c:266:9 (kafka_streams_test+0x5758d5c)
    #3 _PyEval_ReleaseLock /-S/contrib/tools/python3/Python/ceval_gil.c:603:5 (kafka_streams_test+0x5758f17) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #4 detach_thread /-S/contrib/tools/python3/Python/pystate.c:2151:5 (kafka_streams_test+0x5ce5cd9) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #5 _PyThreadState_Detach /-S/contrib/tools/python3/Python/pystate.c:2157:5 (kafka_streams_test+0x5ce5cd9)
    #6 PyEval_SaveThread /-S/contrib/tools/python3/Python/ceval_gil.c:647:5 (kafka_streams_test+0x5759081) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #7 select_epoll_poll_impl /-S/contrib/tools/python3/Modules/selectmodule.c:1628:9 (kafka_streams_test+0x5cb14e0) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #8 select_epoll_poll /-S/contrib/tools/python3/Modules/clinic/selectmodule.c.h:1007:20 (kafka_streams_test+0x5cb14e0)
    #9 _PyEval_EvalFrameDefault /-S/contrib/tools/python3/Python/generated_cases.c.h:1701:23 (kafka_streams_test+0x5749a2c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #10 _PyEval_EvalFrame /-S/contrib/tools/python3/Include/internal/pycore_ceval.h:120:16 (kafka_streams_test+0x574490a) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #11 _PyEval_Vector /-S/contrib/tools/python3/Python/ceval.c:1820:12 (kafka_streams_test+0x574490a)
    #12 _PyFunction_Vectorcall /-S/contrib/tools/python3/Objects/call.c (kafka_streams_test+0x55e590c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #13 _PyObject_VectorcallTstate /-S/contrib/tools/python3/Include/internal/pycore_call.h:168:11 (kafka_streams_test+0x565d897) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #14 method_vectorcall /-S/contrib/tools/python3/Objects/classobject.c:71:20 (kafka_streams_test+0x565d897)
    #15 _PyVectorcall_Call /-S/contrib/tools/python3/Objects/call.c:273:16 (kafka_streams_test+0x55e5443) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #16 _PyObject_Call /-S/contrib/tools/python3/Objects/call.c:348:16 (kafka_streams_test+0x55e563d) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #17 PyObject_Call /-S/contrib/tools/python3/Objects/call.c:373:12 (kafka_streams_test+0x55e5704) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #18 thread_run /-S/contrib/tools/python3/Modules/_threadmodule.c:343:21 (kafka_streams_test+0x5c0f443) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #19 pythread_wrapper /-S/contrib/tools/python3/Python/thread_pthread.h:242:5 (kafka_streams_test+0x5d01afa) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)

  Thread T59 (tid=1323193, running) created by main thread at:
    #0 pthread_create /-S/contrib/libs/clang20-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1041:3 (kafka_streams_test+0x51d08aa) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #1 do_start_joinable_thread /-S/contrib/tools/python3/Python/thread_pthread.h:289:14 (kafka_streams_test+0x5d00ba3) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #2 PyThread_start_joinable_thread /-S/contrib/tools/python3/Python/thread_pthread.h:331:9 (kafka_streams_test+0x5d00a3a) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #3 ThreadHandle_start /-S/contrib/tools/python3/Modules/_threadmodule.c:428:9 (kafka_streams_test+0x5c0f07a) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #4 do_start_new_thread /-S/contrib/tools/python3/Modules/_threadmodule.c:1857:9 (kafka_streams_test+0x5c0f07a)
    #5 thread_PyThread_start_joinable_thread /-S/contrib/tools/python3/Modules/_threadmodule.c:1980:14 (kafka_streams_test+0x5c0e5de) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #6 cfunction_call /-S/contrib/tools/python3/Objects/methodobject.c:539:18 (kafka_streams_test+0x564980d) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #7 _PyObject_MakeTpCall /-S/contrib/tools/python3/Objects/call.c:242:18 (kafka_streams_test+0x55e4cb6) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #8 _PyObject_VectorcallTstate /-S/contrib/tools/python3/Include/internal/pycore_call.h:166:16 (kafka_streams_test+0x55e558c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #9 PyObject_Vectorcall /-S/contrib/tools/python3/Objects/call.c:327:12 (kafka_streams_test+0x55e558c)
    #10 _PyEval_EvalFrameDefault /-S/contrib/tools/python3/Python/generated_cases.c.h:1509:19 (kafka_streams_test+0x5749241) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #11 _PyEval_EvalFrame /-S/contrib/tools/python3/Include/internal/pycore_ceval.h:120:16 (kafka_streams_test+0x574490a) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #12 _PyEval_Vector /-S/contrib/tools/python3/Python/ceval.c:1820:12 (kafka_streams_test+0x574490a)
    #13 _PyFunction_Vectorcall /-S/contrib/tools/python3/Objects/call.c (kafka_streams_test+0x55e590c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #14 _PyObject_VectorcallDictTstate /-S/contrib/tools/python3/Objects/call.c:146:15 (kafka_streams_test+0x55e4ac6) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #15 _PyObject_Call_Prepend /-S/contrib/tools/python3/Objects/call.c:504:24 (kafka_streams_test+0x55e5c27) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #16 slot_tp_init /-S/contrib/tools/python3/Objects/typeobject.c:9838:15 (kafka_streams_test+0x56a2f7c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #17 type_call /-S/contrib/tools/python3/Objects/typeobject.c:2008:19 (kafka_streams_test+0x569a5b3) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #18 _PyObject_MakeTpCall /-S/contrib/tools/python3/Objects/call.c:242:18 (kafka_streams_test+0x55e4cb6) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #19 _PyObject_VectorcallTstate /-S/contrib/tools/python3/Include/internal/pycore_call.h:166:16 (kafka_streams_test+0x55e558c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #20 PyObject_Vectorcall /-S/contrib/tools/python3/Objects/call.c:327:12 (kafka_streams_test+0x55e558c)
    #21 _PyEval_EvalFrameDefault /-S/contrib/tools/python3/Python/generated_cases.c.h:1509:19 (kafka_streams_test+0x5749241) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #22 _PyEval_EvalFrame /-S/contrib/tools/python3/Include/internal/pycore_ceval.h:120:16 (kafka_streams_test+0x574477d) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #23 _PyEval_Vector /-S/contrib/tools/python3/Python/ceval.c:1820:12 (kafka_streams_test+0x574477d)
    #24 PyEval_EvalCode /-S/contrib/tools/python3/Python/ceval.c:604:21 (kafka_streams_test+0x574477d)
    #25 builtin_exec_impl /-S/contrib/tools/python3/Python/bltinmodule.c:1142:17 (kafka_streams_test+0x570fc3a) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #26 builtin_exec /-S/contrib/tools/python3/Python/clinic/bltinmodule.c.h:559:20 (kafka_streams_test+0x570fc3a)
    #27 cfunction_vectorcall_FASTCALL_KEYWORDS /-S/contrib/tools/python3/Objects/methodobject.c:440:24 (kafka_streams_test+0x5648ff6) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #28 _PyObject_VectorcallTstate /-S/contrib/tools/python3/Include/internal/pycore_call.h:168:11 (kafka_streams_test+0x55e5515) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #29 PyObject_Vectorcall /-S/contrib/tools/python3/Objects/call.c:327:12 (kafka_streams_test+0x55e5515)
    #30 _PyEval_EvalFrameDefault /-S/contrib/tools/python3/Python/generated_cases.c.h:813:23 (kafka_streams_test+0x57474c8) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #31 _PyEval_EvalFrame /-S/contrib/tools/python3/Include/internal/pycore_ceval.h:120:16 (kafka_streams_test+0x574490a) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #32 _PyEval_Vector /-S/contrib/tools/python3/Python/ceval.c:1820:12 (kafka_streams_test+0x574490a)
    #33 _PyFunction_Vectorcall /-S/contrib/tools/python3/Objects/call.c (kafka_streams_test+0x55e590c) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #34 _PyVectorcall_Call /-S/contrib/tools/python3/Objects/call.c:273:16 (kafka_streams_test+0x55e5443) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #35 _PyObject_Call /-S/contrib/tools/python3/Objects/call.c:348:16 (kafka_streams_test+0x55e563d) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #36 PyObject_Call /-S/contrib/tools/python3/Objects/call.c:373:12 (kafka_streams_test+0x55e5704) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #37 RunModule /-S/library/python/runtime_py3/main/main.c:62:14 (kafka_streams_test+0x77c6f28) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)
    #38 pymain /-S/library/python/runtime_py3/main/main.c:212:15 (kafka_streams_test+0x77c6f28)
    #39 main /-S/library/python/runtime_py3/main/main.c:251:12 (kafka_streams_test+0x77c743d) (BuildId: d7406fbde644780426ec024e63a5a8c36ba84722)

SUMMARY: ThreadSanitizer: data race /-S/contrib/libs/clang20-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:741:3 in free

Activity

  1. ydbot commented on Aug 18, 2026

    @ydbot
    Collaborator

    Further work and discussion for this issue continue in: YDBBUGS-712

  2. robot-vibe-db commented on Aug 18, 2026

    @robot-vibe-db
    ContributorAuthor

    Analysis

    Root Cause

    This is a known upstream CPython bug (python/cpython#124878). During interpreter finalization (_Py_Finalize), the main thread tears down thread states via _PyThreadState_DeleteList → free_threadstate → PyMem_RawFree → free, while daemon/worker threads (here: epoll-polling threads in select_epoll_poll_impl) are still running and accessing their PyThreadState through drop_gil → _Py_eval_breaker_bit_is_set. The result is a use-after-free race on the PyThreadState memory at address 0x724800028818.

    The race signature is identical across all affected binaries (kafka_streams_test, streaming, transfer, ydb-tests-fq-http_api) — all are py3-packaged test binaries using the embedded CPython 3.13.15 runtime (contrib/tools/python3). No YDB product frames (ydb/core, ydb/library) appear in any stack.

    Relationship to Prior Issues

    This is a recurrence of the exact same race tracked in:

    Existing Suppression

    The bundled CPython at contrib/tools/python3/tsan.supp already contains:

    race:_PyThreadState_DeleteList
    

    and the ya.make declares SUPPRESSIONS(lsan.supp tsan.supp). This suppression should match the main-thread stack (frame #4 is _PyThreadState_DeleteList). The fact that TSan still reports the race on 26.2.1.ent.2 suggests the suppression file may not be propagating to the test binaries on this branch, or TSAN_OPTIONS is being overridden.

    Upstream Fix Status

    The definitive fix was merged upstream in python/cpython#130649 (March 2025). It introduces a reference count on PyThreadState (initialized to 2: one for the interpreter list, one for the OS thread) so that free_threadstate only frees memory when both the interpreter and the OS thread are done with it. However, this fix targets CPython 3.14+ and was not backported to the 3.13 branch. For 3.13, only a TSan suppression backport was applied upstream (python/cpython#130687).

    The bundled CPython 3.13.15 in this repo does NOT contain the refcount-based fix (no _refcount field or _Py_THREAD_SHUTTING_DOWN state in pystate.c).

    Criticality: LOW

    • No production impact: The race is entirely within the CPython interpreter during test/stress binary shutdown. No ydbd or product library code is involved.
    • No data corruption risk: The race occurs at process exit; the process is about to terminate. While technically a use-after-free, it cannot affect production data or cluster state.
    • Test-only noise: The only consequence is that TSan marks the test as failed (exit code 100), causing spurious test failures in the release-tsan regression CI.
    • Recurring known issue: Same race has been reported and closed multiple times across several stable branches without any production incident.

    Possible Solutions

    1. Verify/fix suppression propagation (recommended, lowest effort): Investigate why the existing race:_PyThreadState_DeleteList suppression in contrib/tools/python3/tsan.supp is not taking effect on the 26.2.1.ent.2 branch. Possible causes: the test binaries' dependency chain doesn't inherit the SUPPRESSIONS from the Python library's ya.make, or TSAN_OPTIONS is being overridden by the test runner. Adding explicit SUPPRESSIONS directives to the affected test ya.make files or to library/python/runtime_py3 would be a quick fix.

    2. Broaden the suppression pattern: Add race:free_threadstate to contrib/tools/python3/tsan.supp. This targets the exact function performing the racy write and would catch the race even if the inlined call chain prevents _PyThreadState_DeleteList from appearing in TSan's frame matching.

    3. Cherry-pick the upstream CPython fix: Port python/cpython#130649 into the bundled contrib/tools/python3. This would fully eliminate the race by adding reference counting to PyThreadState. This is the most correct solution but carries risk since it modifies the embedded CPython runtime.

    4. Ensure graceful thread shutdown in the test harness: Modify library/python/runtime_py3/main/main.c to explicitly request all non-daemon Python threads to stop (e.g., via threading._shutdown() or setting a shutdown flag) and join them before calling Py_FinalizeEx(). This would prevent the race from occurring in the first place, but may require changes to the test workloads themselves if they spawn threads that don't check for shutdown.

    Recommendation

    Given the low criticality and recurring nature, the most pragmatic path is option 1 or 2: ensure the existing TSan suppression reaches the test binaries (or add race:free_threadstate as an additional suppression). This eliminates the CI noise without touching the CPython runtime. For a long-term fix, option 3 (cherry-picking the upstream refcount fix) would be ideal when the bundled CPython is next updated.

  3. robot-vibe-db commented on Aug 18, 2026

    @robot-vibe-db
    ContributorAuthor

    Full analysis log

    Analysis performed by claude, claude-opus-4-6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions