Skip to content

gh-153014: Fix data race on the GC debug flag in free-threading builds - #153015

Open
tonghuaroot wants to merge 6 commits into
python:mainfrom
tonghuaroot:gc-debug-ft-race
Open

tonghuaroot wants to merge 6 commits into
python:mainfrom
tonghuaroot:gc-debug-ft-race

Conversation

@tonghuaroot

@tonghuaroot tonghuaroot commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

In a free-threading build, gc.set_debug() stored gcstate->debug with a
plain write while gc.get_debug() and the collector read it without
synchronisation. gc.set_debug() takes no lock and, unlike
gc_set_threshold_impl(), does not run under _PyEval_StopTheWorld(), so
ThreadSanitizer reports a write/write race on gcstate->debug.

This accesses the flag with FT_ATOMIC_STORE_INT_RELAXED /
FT_ATOMIC_LOAD_INT_RELAXED in gc_set_debug_impl() / gc_get_debug_impl()
and in the collector reads in Python/gc_free_threading.c. Four of those
collector reads run outside stop-the-world (in delete_garbage(),
handle_legacy_finalizers(), and two stats reads in gc_collect_main()) and
race the write directly; the read in gc_collect_internal() runs under
stop-the-world and is made atomic only for consistency. Relaxed ordering is
correct for an independent int flag, matching how gcstate->enabled and
sys dlopenflags (gh-151644) are already handled.

Python/gc.c (the default GIL build) is left unchanged: it has no concurrent
access, and FT_ATOMIC_* compiles to a plain load/store there anyway.

Verified with a --with-thread-sanitizer --disable-gil build stressing
concurrent gc.set_debug() / gc.get_debug() plus a thread churning cyclic
garbage:

Before: WARNING: ThreadSanitizer: data race ... Write of size 4 ... gc_set_debug (2 reports)
After:  0 data races

A regression test is added under Lib/test/test_free_threading/.

… builds

In free-threading builds gc.set_debug() stored gcstate->debug with a plain
write while gc.get_debug() and the collector read it without synchronisation,
which ThreadSanitizer reports as a data race.  Access the flag with
FT_ATOMIC_STORE/LOAD_INT_RELAXED, consistent with how gc.enable()/disable()
already handle gcstate->enabled.
Comment thread Python/gc_free_threading.c Outdated
@sergey-miryanov

Copy link
Copy Markdown
Contributor

cc @nascheme as a GC-FT expert

@nascheme

nascheme commented Jul 8, 2026

Copy link
Copy Markdown
Member

This looks worth fixing. Some suggested improvements:

  • Use threading_helper.run_concurrently()
  • Remove the thread that runs GC collections. I don't think that's useful since it stops-the-world.
  • I'd use one writer and one reader. It would be enough to trigger the TSAN warnings.
  • If gc->debug is read more than once inside a function, read it into a local variable.

@github-actions

Copy link
Copy Markdown

This PR is stale because it has been open for 90 days with no activity.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Oct 11, 2026
- Read gcstate->debug once into a local in delete_garbage,
  handle_legacy_finalizers, gc_collect_main and _PyGC_DumpShutdownStats.
- Simplify the regression test to one gc.set_debug() writer racing one
  gc.get_debug() reader via run_concurrently(), dropping the collector
  thread.
@tonghuaroot

Copy link
Copy Markdown
Contributor Author

Done: gcstate->debug is now read once into a local in the four collector functions, and the test is one gc.set_debug() writer racing one gc.get_debug() reader via run_concurrently() (no collector thread). Verified under --disable-gil --with-thread-sanitizer: TSAN flags the race without the fix and is clean with it. Also merged main to clear the conflict.

I have made the requested changes; please review again

@bedevere-app

bedevere-app Bot commented Oct 12, 2026

Copy link
Copy Markdown

Thanks for making the requested changes!

: please review the changes made to this pull request.

@nascheme

Copy link
Copy Markdown
Member

Hi tonghuaroot, thanks for making the changes requested. This looks good to merge now. After reconsidering the unit test, I think it's better we just remove it (I pushed a change). In terms of CI cost vs what this test buys us, I think it's not worth it. This race is not very serious. Fixing the code so we don't get a TSAN warning is good. Sorry for asking you to fix the test on the first review, normally we do want unit tests when bugs are fixed.

@nascheme
nascheme enabled auto-merge (squash) October 12, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting change review stale Stale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants