Skip to content

Commit 98da7ef

Browse files
committed
Fix garbage_object_p() to load and use background_sweep_thread atomically
The use here is not protected by the sweep lock, so we should. Also, use atomic load for checking if object is marked so that it's not re-ordered past the next atomic load of page->before_sweep.
1 parent bb00189 commit 98da7ef

1 file changed

Lines changed: 37 additions & 24 deletions

File tree

gc/default/default.c

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -541,13 +541,15 @@ typedef struct rb_objspace {
541541
unsigned int during_reference_updating : 1;
542542
unsigned int gc_stressful: 1;
543543
unsigned int has_newobj_hook: 1;
544-
unsigned int during_minor_gc : 1;
545544
unsigned int during_incremental_marking : 1;
546-
unsigned int during_lazy_sweeping : 1;
547-
548545

549546
unsigned int measure_gc : 1;
550547
} flags;
548+
// This can't be a bitfield because it's accessed in garbage_object_p() from the sweep thread
549+
// while the ruby GC thread could be running and changing other bitfields.
550+
bool during_lazy_sweeping;
551+
// This one too, it's accessed in debug_free_check
552+
bool during_minor_gc;
551553

552554
rb_event_flag_t hook_events;
553555

@@ -563,10 +565,10 @@ typedef struct rb_objspace {
563565
bool sweep_thread_sweep_exited;
564566
bool sweep_thread_waiting_request;
565567
bool sweep_thread_sweeping;
568+
rb_atomic_t use_background_sweep_thread;
566569
bool background_sweep_mode;
567570
bool background_sweep_abort;
568571
bool background_sweep_restart_heaps;
569-
bool use_background_sweep_thread;
570572
bool sweep_rest;
571573
unsigned int heaps_done_background_sweep;
572574

@@ -1349,12 +1351,12 @@ total_final_slots_count(rb_objspace_t *objspace)
13491351

13501352
#define is_marking(objspace) (gc_mode(objspace) == gc_mode_marking)
13511353
#define is_sweeping(objspace) (gc_mode(objspace) == gc_mode_sweeping)
1352-
#define is_full_marking(objspace) ((objspace)->flags.during_minor_gc == FALSE)
1354+
#define is_full_marking(objspace) ((objspace)->during_minor_gc == FALSE)
13531355
#define is_incremental_marking(objspace) ((objspace)->flags.during_incremental_marking != FALSE)
13541356
#define will_be_incremental_marking(objspace) ((objspace)->rgengc.need_major_gc != GPR_FLAG_NONE)
13551357
#define GC_INCREMENTAL_SWEEP_SLOT_COUNT 2048
13561358
#define GC_INCREMENTAL_SWEEP_POOL_SLOT_COUNT 1024
1357-
#define is_lazy_sweeping(objspace) ((objspace)->flags.during_lazy_sweeping != FALSE)
1359+
#define is_lazy_sweeping(objspace) ((objspace)->during_lazy_sweeping != FALSE)
13581360
/* In lazy sweeping or the previous incremental marking finished and did not yield a free page. */
13591361
#define needs_continue_sweeping(objspace, heap) \
13601362
((heap)->free_pages == NULL && is_lazy_sweeping(objspace))
@@ -1557,6 +1559,14 @@ RVALUE_MARKED(rb_objspace_t *objspace, VALUE obj)
15571559
return RVALUE_MARKED_BITMAP(obj) != 0;
15581560
}
15591561

1562+
static inline int
1563+
RVALUE_MARKED_ATOMIC(rb_objspace_t *objspace, VALUE obj)
1564+
{
1565+
bits_t *bits = GET_HEAP_MARK_BITS(obj);
1566+
bits_t word = __atomic_load_n(&bits[BITMAP_INDEX(obj)], __ATOMIC_SEQ_CST);
1567+
return (word & BITMAP_BIT(obj)) != 0;
1568+
}
1569+
15601570
static inline int
15611571
RVALUE_PINNED(rb_objspace_t *objspace, VALUE obj)
15621572
{
@@ -1912,8 +1922,11 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
19121922

19131923
bool dead = false;
19141924

1915-
if (!objspace->background_sweep_mode) { // set to false/true by ruby GC thread when entering/exiting GC
1916-
// psweep: not safe to read flags on object if during background sweeping
1925+
// Set to false/true by the ruby GC thread when entering/exiting GC, so shouldn't change throughout this call.
1926+
rb_atomic_t use_sweep_thread = RUBY_ATOMIC_LOAD(objspace->use_background_sweep_thread);
1927+
1928+
if (!use_sweep_thread) {
1929+
// It's not safe to read flags on an object if the sweep thread is running
19171930
asan_unpoisoning_object(ptr) {
19181931
switch (BUILTIN_TYPE(ptr)) {
19191932
case T_NONE:
@@ -1932,17 +1945,19 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr)
19321945
struct heap_page *page = GET_HEAP_PAGE(ptr);
19331946
bool during_lazy_sweep = is_lazy_sweeping(objspace);
19341947

1935-
if (!objspace->background_sweep_mode) {
1936-
return during_lazy_sweep && !RVALUE_MARKED(objspace, ptr) && RUBY_ATOMIC_LOAD(page->before_sweep);
1948+
if (!use_sweep_thread) {
1949+
// The ruby GC thread or a user thread called us
1950+
bool marked = RVALUE_MARKED(objspace, ptr);
1951+
GC_ASSERT(marked == RVALUE_MARKED_ATOMIC(objspace, ptr));
1952+
return during_lazy_sweep && !marked && RUBY_ATOMIC_LOAD(page->before_sweep);
19371953
}
19381954
// we're currently lazy sweeping with the sweep thread in background mode
19391955
else if (during_lazy_sweep) {
1940-
bool is_before1, is_before2;
1941-
// This is technically UB because reading of mark bits is not synchronized, but I think it's fine.
1942-
bool is_garbage = ((is_before1 = RUBY_ATOMIC_LOAD(page->before_sweep)) &&
1943-
!RVALUE_MARKED(objspace, ptr) && (is_before2 = RUBY_ATOMIC_LOAD(page->before_sweep)));
1956+
bool marked = RVALUE_MARKED_ATOMIC(objspace, ptr); // load it atomically so it can't be re-ordered past the next atomic load
1957+
bool before_sweep = RUBY_ATOMIC_LOAD(page->before_sweep);
1958+
bool is_garbage = !marked && before_sweep;
19441959
if (is_garbage) return true;
1945-
if (is_before1 && is_before2) return false; // must be marked (before_sweep and marked)
1960+
if (marked && before_sweep) return false;
19461961
// already swept page, just check flags
19471962
return BUILTIN_TYPE(ptr) == T_NONE || BUILTIN_TYPE(ptr) == T_MOVED || BUILTIN_TYPE(ptr) == T_ZOMBIE;
19481963
}
@@ -4402,9 +4417,7 @@ debug_free_check(rb_objspace_t *objspace, VALUE vp)
44024417
if (RVALUE_OLD_P(objspace, vp)) rb_bug("page_sweep: %p - old while minor GC.", (void *)p);
44034418
if (RVALUE_REMEMBERED(objspace, vp)) rb_bug("page_sweep: %p - remembered.", (void *)p);
44044419
}
4405-
if (RVALUE_WB_UNPROTECTED(objspace, vp)) CLEAR_IN_BITMAP(GET_HEAP_WB_UNPROTECTED_BITS(vp), vp);
44064420
#define CHECK(x) if (x(objspace, vp) != FALSE) rb_bug("obj_free: " #x "(%s) != FALSE", rb_obj_info(vp))
4407-
CHECK(RVALUE_WB_UNPROTECTED);
44084421
CHECK(RVALUE_MARKED);
44094422
CHECK(RVALUE_MARKING);
44104423
CHECK(RVALUE_UNCOLLECTIBLE);
@@ -4944,7 +4957,7 @@ static void
49444957
gc_sweep_start(rb_objspace_t *objspace)
49454958
{
49464959
gc_mode_transition(objspace, gc_mode_sweeping);
4947-
objspace->flags.during_lazy_sweeping = TRUE;
4960+
objspace->during_lazy_sweeping = TRUE;
49484961
objspace->rincgc.pooled_slots = 0;
49494962

49504963
// Background sweeping cannot be happening
@@ -4984,7 +4997,7 @@ gc_sweep_start(rb_objspace_t *objspace)
49844997
(objspace->profile.latest_gc_info & GPR_FLAG_METHOD) == 0 &&
49854998
!(objspace->hook_events & RUBY_INTERNAL_EVENT_FREEOBJ)) {
49864999

4987-
objspace->use_background_sweep_thread = true;
5000+
RUBY_ATOMIC_SET(objspace->use_background_sweep_thread, true);
49885001
psweep_debug(-1, "[gc] gc_sweep_start: requesting sweep thread\n");
49895002
sweep_lock_lock(&objspace->sweep_lock);
49905003
{
@@ -4994,7 +5007,7 @@ gc_sweep_start(rb_objspace_t *objspace)
49945007
sweep_lock_unlock(&objspace->sweep_lock);
49955008
}
49965009
else {
4997-
objspace->use_background_sweep_thread = false;
5010+
RUBY_ATOMIC_SET(objspace->use_background_sweep_thread, false);
49985011
psweep_debug(-1, "[gc] gc_sweep_start: not using background sweep thread\n");
49995012
}
50005013
}
@@ -5053,7 +5066,7 @@ gc_sweep_finish(rb_objspace_t *objspace)
50535066
gc_report(1, objspace, "gc_sweep_finish\n");
50545067
psweep_debug(-1, "[gc] gc_sweep_finish\n");
50555068

5056-
objspace->use_background_sweep_thread = false;
5069+
RUBY_ATOMIC_SET(objspace->use_background_sweep_thread, false);
50575070

50585071
gc_prof_set_heap_info(objspace);
50595072
heap_pages_free_unused_pages(objspace);
@@ -5095,7 +5108,7 @@ gc_sweep_finish(rb_objspace_t *objspace)
50955108

50965109
rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP);
50975110
gc_mode_transition(objspace, gc_mode_none);
5098-
objspace->flags.during_lazy_sweeping = FALSE;
5111+
objspace->during_lazy_sweeping = FALSE;
50995112

51005113
#if RGENGC_CHECK_MODE >= 2
51015114
gc_verify_internal_consistency(objspace);
@@ -7348,7 +7361,7 @@ gc_marks_start(rb_objspace_t *objspace, int full_mark)
73487361
"objspace->rincgc.pooled_page_num: %"PRIdSIZE", "
73497362
"objspace->rincgc.step_slots: %"PRIdSIZE", \n",
73507363
objspace->marked_slots, objspace->rincgc.pooled_slots, objspace->rincgc.step_slots);
7351-
objspace->flags.during_minor_gc = FALSE;
7364+
objspace->during_minor_gc = FALSE;
73527365
if (ruby_enable_autocompact) {
73537366
objspace->flags.during_compacting |= TRUE;
73547367
}
@@ -7373,7 +7386,7 @@ gc_marks_start(rb_objspace_t *objspace, int full_mark)
73737386
}
73747387
}
73757388
else {
7376-
objspace->flags.during_minor_gc = TRUE;
7389+
objspace->during_minor_gc = TRUE;
73777390
objspace->marked_slots =
73787391
objspace->rgengc.old_objects + objspace->rgengc.uncollectible_wb_unprotected_objects; /* uncollectible objects are marked already */
73797392
objspace->profile.minor_gc_count++;

0 commit comments

Comments
 (0)