gh-151646: Fix data race between gc.get_stats() and GC in free-threading builds#151766
Conversation
| sees a consistent snapshot rather than racing on these fields | ||
| (gh-151646). get_stats() reads buffer->index inside the lock so that | ||
| both writer and reader resolve the same slot under the same mutex. */ | ||
| PyMutex_Lock(&gcstate->stats_mutex); |
There was a problem hiding this comment.
It seems the guard macro Py_GIL_DISABLED is missing here and below the unlock.
There was a problem hiding this comment.
The whole gc_free_threading.c file is wrapped in #ifdef Py_GIL_DISABLED so I don't think that's needed.
|
This looks okay to me. Some comments:
Suggested unit test change: |
|
I will take a look after work and address comments, thanks for reviewing :) |
|
I revised the unit test to use |
Address review feedback (nascheme) on pythonGH-151766: - The stress test looped 8 reader threads x 50,000 iterations each. Run one writer for a fixed number of gc.collect() calls and have the readers loop until the writer signals done, so the duration is driven by the collection count. Use threading_helper.run_concurrently() for start synchronisation. - Trim the stats_mutex comments: keep one comment on the struct member saying what it protects, and drop the now-redundant comments at the lock sites. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks, applied both suggestions. Switched to one writer looping over a fixed number of |
In free-threading builds,
gc_get_stats_impl()reads per-generation statswith plain struct copies while
gc_collect_main()writes the same fieldsafter
_Py_StartTheWorld()— no synchronisation on either side.Fix: add
PyMutex stats_mutexto_gc_runtime_state(underPy_GIL_DISABLED) and hold it around both the seven field writes in thewriter and the three struct copies in the reader. The lock is acquired after
_Py_StartTheWorld(), so no deadlock with stop-the-world. GIL build isunchanged.
Also adds
Lib/test/test_free_threading/test_gc_get_stats_race.py.Verified with TSAN on Linux: race confirmed on
main, clean on the patch.🤖 Generated with Claude Code
gc.get_stats()and a concurrent collection under free-threading #151646