Skip to content

Data race between gc_should_collect and gc_set_threshold_impl #148613

@Naserume

Description

@Naserume

Bug report

Bug description:

gc_set_threshold_impl writes gcstate->young.threshold

cpython/Modules/gcmodule.c

Lines 154 to 161 in 69e0a78

static PyObject *
gc_set_threshold_impl(PyObject *module, int threshold0, int group_right_1,
int threshold1, int group_right_2, int threshold2)
/*[clinic end generated code: output=2e3c7c7dd59060f3 input=0d9612db50984eec]*/
{
GCState *gcstate = get_gc_state();
gcstate->young.threshold = threshold0;

while gc_should_collect() reads gcstate->young.threshold non atomically

static bool
gc_should_collect(GCState *gcstate)
{
int count = _Py_atomic_load_int_relaxed(&gcstate->young.count);
int threshold = gcstate->young.threshold;
int gc_enabled = _Py_atomic_load_int_relaxed(&gcstate->enabled);

Reproducer:

import sys
import gc
from threading import Thread, Barrier

class C:
    pass

N_THREADS = 8
N_ITERS = 100_000
barrier = Barrier(N_THREADS)

def allocator():
    barrier.wait()
    keep = []
    for i in range(N_ITERS):
        a = C(); b = C()
        a.r = b; b.r = a
        if i % 100 == 0:
            keep.append(a)
            if len(keep) > 1000:
                keep.clear()

def setter():
    barrier.wait()
    for i in range(N_ITERS):
        gc.set_threshold(100 + (i & 0x3F), 10, 10)

threads = [Thread(target=allocator) for _ in range(N_THREADS - 1)]
threads.append(Thread(target=setter))
for t in threads: t.start()
for t in threads: t.join()

TSAN Report:

WARNING: ThreadSanitizer: data race (pid=482463)
  Read of size 4 at 0x555555e38f80 by thread T5:
    #0 gc_should_collect_mem_usage /cpython/Python/gc_free_threading.c:2168:36 (python3.15t+0x4a90c3) 
    #1 gc_should_collect /cpython/Python/gc_free_threading.c:2217:12 (python3.15t+0x4a90c3)
    #2 record_allocation /cpython/Python/gc_free_threading.c:2234:13 (python3.15t+0x4a8324) 
    #3 _PyObject_GC_Link /cpython/Python/gc_free_threading.c:2914:5 (python3.15t+0x4a8324)
    #4 _PyType_AllocNoTrack /cpython/Objects/typeobject.c:2537:9 (python3.15t+0x322b3d) 
    #5 PyType_GenericAlloc /cpython/Objects/typeobject.c:2558:21 (python3.15t+0x32291e) 
    #6 object_new /cpython/Objects/typeobject.c:7340:21 (python3.15t+0x32ed3b) 
    #7 type_call /cpython/Objects/typeobject.c:2471:11 (python3.15t+0x32a7e2) 
    #8 _PyObject_MakeTpCall /cpython/Objects/call.c:242:18 (python3.15t+0x20ef0f) 
    #9 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:142:16 (python3.15t+0x20fcf7) 
    #10 PyObject_Vectorcall /cpython/Objects/call.c:327:12 (python3.15t+0x20fcf7)
    #11 _Py_VectorCall_StackRefSteal /cpython/Python/ceval.c:733:11 (python3.15t+0x428ab9) 
    #12 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:4344:35 (python3.15t+0x435c37) 
    #13 _PyEval_EvalFrame /cpython/./Include/internal/pycore_ceval.h:118:16 (python3.15t+0x428660) 
    #14 _PyEval_Vector /cpython/Python/ceval.c:2124:12 (python3.15t+0x428660)
    #15 _PyFunction_Vectorcall /cpython/Objects/call.c (python3.15t+0x2102af) 
    #16 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x211de9) 
    #17 _PyObject_VectorcallPrepend /cpython/Objects/call.c:855:20 (python3.15t+0x211de9)
    #18 method_vectorcall /cpython/Objects/classobject.c:55:12 (python3.15t+0x21531f) 
    #19 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x480a91) 
    #20 context_run /cpython/Python/context.c:727:29 (python3.15t+0x480a91)
    #21 method_vectorcall_FASTCALL_KEYWORDS /cpython/Objects/descrobject.c:421:24 (python3.15t+0x229fe7) 
    #22 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x20fc33) 
    #23 PyObject_Vectorcall /cpython/Objects/call.c:327:12 (python3.15t+0x20fc33)
    #24 _Py_VectorCallInstrumentation_StackRefSteal /cpython/Python/ceval.c:775:11 (python3.15t+0x42928c) 
    #25 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:1841:35 (python3.15t+0x42f07c) 
    #26 _PyEval_EvalFrame /cpython/./Include/internal/pycore_ceval.h:118:16 (python3.15t+0x428660) 
    #27 _PyEval_Vector /cpython/Python/ceval.c:2124:12 (python3.15t+0x428660)
    #28 _PyFunction_Vectorcall /cpython/Objects/call.c (python3.15t+0x2102af) 
    #29 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x211de9) 
    #30 _PyObject_VectorcallPrepend /cpython/Objects/call.c:855:20 (python3.15t+0x211de9)
    #31 method_vectorcall /cpython/Objects/classobject.c:55:12 (python3.15t+0x21531f) 
    #32 _PyVectorcall_Call /cpython/Objects/call.c:273:16 (python3.15t+0x20ff3b) 
    #33 _PyObject_Call /cpython/Objects/call.c:348:16 (python3.15t+0x20ff3b)
    #34 PyObject_Call /cpython/Objects/call.c:373:12 (python3.15t+0x20ffa5) 
    #35 thread_run /cpython/./Modules/_threadmodule.c:388:21 (python3.15t+0x605bc2) 
    #36 pythread_wrapper /cpython/Python/thread_pthread.h:234:5 (python3.15t+0x535427) 
  Previous write of size 4 at 0x555555e38f80 by thread T8:
    #0 gc_set_threshold_impl /cpython/Modules/gcmodule.c:161:30 (python3.15t+0x5510f6) 
    #1 gc_set_threshold /cpython/Modules/clinic/gcmodule.c.h:266:20 (python3.15t+0x5510f6)
    #2 cfunction_call /cpython/Objects/methodobject.c:575:18 (python3.15t+0x2c10d1) 
    #3 _PyObject_MakeTpCall /cpython/Objects/call.c:242:18 (python3.15t+0x20ef0f) 
    #4 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:142:16 (python3.15t+0x20fcf7) 
    #5 PyObject_Vectorcall /cpython/Objects/call.c:327:12 (python3.15t+0x20fcf7)
    #6 _Py_VectorCall_StackRefSteal /cpython/Python/ceval.c:733:11 (python3.15t+0x428ab9) 
    #7 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:4344:35 (python3.15t+0x435c37) 
    #8 _PyEval_EvalFrame /cpython/./Include/internal/pycore_ceval.h:118:16 (python3.15t+0x428660) 
    #9 _PyEval_Vector /cpython/Python/ceval.c:2124:12 (python3.15t+0x428660)
    #10 _PyFunction_Vectorcall /cpython/Objects/call.c (python3.15t+0x2102af) 
    #11 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x211de9) 
    #12 _PyObject_VectorcallPrepend /cpython/Objects/call.c:855:20 (python3.15t+0x211de9)
    #13 method_vectorcall /cpython/Objects/classobject.c:55:12 (python3.15t+0x21531f) 
    #14 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x480a91) 
    #15 context_run /cpython/Python/context.c:727:29 (python3.15t+0x480a91)
    #16 method_vectorcall_FASTCALL_KEYWORDS /cpython/Objects/descrobject.c:421:24 (python3.15t+0x229fe7) 
    #17 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x20fc33) 
    #18 PyObject_Vectorcall /cpython/Objects/call.c:327:12 (python3.15t+0x20fc33)
    #19 _Py_VectorCallInstrumentation_StackRefSteal /cpython/Python/ceval.c:775:11 (python3.15t+0x42928c) 
    #20 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:1841:35 (python3.15t+0x42f07c) 
    #21 _PyEval_EvalFrame /cpython/./Include/internal/pycore_ceval.h:118:16 (python3.15t+0x428660) 
    #22 _PyEval_Vector /cpython/Python/ceval.c:2124:12 (python3.15t+0x428660)
    #23 _PyFunction_Vectorcall /cpython/Objects/call.c (python3.15t+0x2102af) 
    #24 _PyObject_VectorcallTstate /cpython/./Include/internal/pycore_call.h:144:11 (python3.15t+0x211de9) 
    #25 _PyObject_VectorcallPrepend /cpython/Objects/call.c:855:20 (python3.15t+0x211de9)
    #26 method_vectorcall /cpython/Objects/classobject.c:55:12 (python3.15t+0x21531f) 
    #27 _PyVectorcall_Call /cpython/Objects/call.c:273:16 (python3.15t+0x20ff3b) 
    #28 _PyObject_Call /cpython/Objects/call.c:348:16 (python3.15t+0x20ff3b)
    #29 PyObject_Call /cpython/Objects/call.c:373:12 (python3.15t+0x20ffa5) 
    #30 thread_run /cpython/./Modules/_threadmodule.c:388:21 (python3.15t+0x605bc2) 
    #31 pythread_wrapper /cpython/Python/thread_pthread.h:234:5 (python3.15t+0x535427) 

  Location is global '_PyRuntime' of size 405120 at 0x555555e108c0 (python3.15t+0x8e4f80)

CPython versions tested on:

Python 3.15.0a8+ free-threading build (heads/main:44f1b987ed1, Apr 14 2026, 07:56:49)

Operating systems tested on:

Ubuntu

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions