Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a data race on the garbage collector debug flag in free-threading builds:
:func:`gc.set_debug` and :func:`gc.get_debug` now access it atomically.
5 changes: 3 additions & 2 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "Python.h"
#include "pycore_gc.h"
#include "pycore_object.h" // _PyObject_IS_GC()
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_INT_RELAXED()
#include "pycore_pystate.h" // _PyInterpreterState_GET()

typedef struct _gc_runtime_state GCState;
Expand Down Expand Up @@ -116,7 +117,7 @@ gc_set_debug_impl(PyObject *module, int flags)
/*[clinic end generated code: output=7c8366575486b228 input=5e5ce15e84fbed15]*/
{
GCState *gcstate = get_gc_state();
gcstate->debug = flags;
FT_ATOMIC_STORE_INT_RELAXED(gcstate->debug, flags);
Py_RETURN_NONE;
}

Expand All @@ -131,7 +132,7 @@ gc_get_debug_impl(PyObject *module)
/*[clinic end generated code: output=91242f3506cd1e50 input=91a101e1c3b98366]*/
{
GCState *gcstate = get_gc_state();
return gcstate->debug;
return FT_ATOMIC_LOAD_INT_RELAXED(gcstate->debug);
}

/*[clinic input]
Expand Down
23 changes: 14 additions & 9 deletions Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "pycore_interpframe.h" // _PyFrame_GetLocalsArray()
#include "pycore_list.h" // _PyList_GetItemRef()
#include "pycore_object_alloc.h" // _PyObject_MallocWithType()
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_INT_RELAXED()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_tstate.h" // _PyThreadStateImpl
#include "pycore_tuple.h" // _PyTuple_MaybeUntrack()
Expand Down Expand Up @@ -1738,6 +1739,7 @@ delete_garbage(struct collection_state *state)
{
PyThreadState *tstate = _PyThreadState_GET();
GCState *gcstate = state->gcstate;
int debug = FT_ATOMIC_LOAD_INT_RELAXED(gcstate->debug);

assert(!_PyErr_Occurred(tstate));

Expand All @@ -1760,7 +1762,7 @@ delete_garbage(struct collection_state *state)

state->collected++;

if (gcstate->debug & _PyGC_DEBUG_SAVEALL) {
if (debug & _PyGC_DEBUG_SAVEALL) {
assert(gcstate->garbage != NULL);
if (PyList_Append(gcstate->garbage, op) < 0) {
_PyErr_Clear(tstate);
Expand All @@ -1786,16 +1788,17 @@ handle_legacy_finalizers(struct collection_state *state)
{
GCState *gcstate = state->gcstate;
assert(gcstate->garbage != NULL);
int debug = FT_ATOMIC_LOAD_INT_RELAXED(gcstate->debug);

PyObject *op;
while ((op = worklist_pop(&state->legacy_finalizers)) != NULL) {
state->uncollectable++;

if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) {
if (debug & _PyGC_DEBUG_UNCOLLECTABLE) {
debug_cycle("uncollectable", op);
}

if ((gcstate->debug & _PyGC_DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
if ((debug & _PyGC_DEBUG_SAVEALL) || has_legacy_finalizer(op)) {
if (PyList_Append(gcstate->garbage, op) < 0) {
PyErr_Clear();
}
Expand Down Expand Up @@ -2146,7 +2149,7 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
bool needs_resurrection_check = state->objs_to_decref.head != 0;

// Print debugging information.
if (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) {
if (FT_ATOMIC_LOAD_INT_RELAXED(interp->gc.debug) & _PyGC_DEBUG_COLLECTABLE) {
needs_resurrection_check = true;
PyObject *op;
WORKSTACK_FOR_EACH(&state->unreachable, op) {
Expand Down Expand Up @@ -2260,7 +2263,8 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
invoke_gc_callback(tstate, "start", generation, 0, 0, 0, 0.0);
}

if (gcstate->debug & _PyGC_DEBUG_STATS) {
int debug = FT_ATOMIC_LOAD_INT_RELAXED(gcstate->debug);
if (debug & _PyGC_DEBUG_STATS) {
PySys_WriteStderr("gc: collecting generation %d...\n", generation);
show_stats_each_generations(gcstate);
}
Expand All @@ -2287,7 +2291,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason)
(void)PyTime_PerfCounterRaw(&stop);
double duration = PyTime_AsSecondsDouble(stop - start);

if (gcstate->debug & _PyGC_DEBUG_STATS) {
if (debug & _PyGC_DEBUG_STATS) {
PySys_WriteStderr(
"gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n",
n+m, n, duration);
Expand Down Expand Up @@ -2618,10 +2622,11 @@ void
_PyGC_DumpShutdownStats(PyInterpreterState *interp)
{
GCState *gcstate = &interp->gc;
if (!(gcstate->debug & _PyGC_DEBUG_SAVEALL)
int debug = FT_ATOMIC_LOAD_INT_RELAXED(gcstate->debug);
if (!(debug & _PyGC_DEBUG_SAVEALL)
&& gcstate->garbage != NULL && PyList_GET_SIZE(gcstate->garbage) > 0) {
const char *message;
if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) {
if (debug & _PyGC_DEBUG_UNCOLLECTABLE) {
message = "gc: %zd uncollectable objects at shutdown";
}
else {
Expand All @@ -2637,7 +2642,7 @@ _PyGC_DumpShutdownStats(PyInterpreterState *interp)
{
PyErr_FormatUnraisable("Exception ignored in GC shutdown");
}
if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) {
if (debug & _PyGC_DEBUG_UNCOLLECTABLE) {
PyObject *repr = NULL, *bytes = NULL;
repr = PyObject_Repr(gcstate->garbage);
if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr))) {
Expand Down
Loading