diff --git a/src/gc.c b/src/gc.c index 7fac0f130d4f4..508ab0062f187 100644 --- a/src/gc.c +++ b/src/gc.c @@ -1788,13 +1788,17 @@ JL_DLLEXPORT void jl_gc_queue_root(const jl_value_t *ptr) { jl_ptls_t ptls = jl_current_task->ptls; jl_taggedvalue_t *o = jl_astaggedvalue(ptr); - // The modification of the `gc_bits` is not atomic but it - // should be safe here since GC is not allowed to run here and we only - // write GC_OLD to the GC bits outside GC. This could cause - // duplicated objects in the remset but that shouldn't be a problem. - o->bits.gc = GC_MARKED; - arraylist_push(ptls->heap.remset, (jl_value_t*)ptr); - ptls->heap.remset_nptr++; // conservative + // The modification of the `gc_bits` needs to be atomic. + // We need to ensure that objects are in the remset at + // most once, since the mark phase may update page metadata, + // which is not idempotent. See comments in https://github.com/JuliaLang/julia/issues/50419 + uintptr_t header = jl_atomic_load_relaxed((_Atomic(uintptr_t) *)&o->header); + header &= ~GC_OLD; // clear the age bit + header = jl_atomic_exchange_relaxed((_Atomic(uintptr_t) *)&o->header, header); + if (header & GC_OLD) { // write barrier has not been triggered in this object yet + arraylist_push(ptls->heap.remset, (jl_value_t*)ptr); + ptls->heap.remset_nptr++; // conservative + } } void jl_gc_queue_multiroot(const jl_value_t *parent, const void *ptr, jl_datatype_t *dt) JL_NOTSAFEPOINT