Fix malloc counters during GC

https://github.com/ruby/ruby/pull/16919 changed malloc counters to be
reset in `gc_sweep_finish`. However, this introduced two problems:

1. If a GC is triggered due to the `malloc_limit` being hit, then it would
   run a GC with lazy sweeping, during lazy sweeping Ruby code could call
   `xmalloc`, which would immediately finish sweeping by calling `gc_rest`
   because the `malloc_increase` is not reset.
2. When `gc_rest` is called, it immediately completes sweeping. However,
   all of the counters for the freed memory is lost because `malloc_increase`
   is reset at the end. Previously, lazy sweeping would be interleaved
   with Ruby code execution, meaning `xfree` was interleaved with `xmalloc`,
   allowing for more memory to be allocated before `malloc_increase` hits
   `malloc_limit`.

The issue can be reproduced by this script:

   live = []
   500.times do
   live << Array.new(200) { String.new(capacity: 64 * 1024) }
   end
   puts GC.stat

Before:

   {count: 235, ..., minor_gc_count: 196, major_gc_count: 39}

After:

   {count: 196, ..., minor_gc_count: 153, major_gc_count: 43}
This commit is contained in:
Peter Zhu authored and eightbitraptor committed 2026-06-17 11:00:21 +01:00
1 parent e16d406db2
commit 45fd5d58f1
1 file changed
+3 -7
Notes: git 2026-09-14 01:47:40 +00:00
+3 -7
View File
@@ -4212,13 +4212,6 @@ gc_sweep_finish(rb_objspace_t *objspace)
}
}
gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters);
#if RGENGC_ESTIMATE_OLDMALLOC
if (objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_MASK) {
gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters);
}
#endif
rb_gc_event_hook(0, RUBY_INTERNAL_EVENT_GC_END_SWEEP);
gc_mode_transition(objspace, gc_mode_none);
@@ -6669,6 +6662,7 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
gc_prof_set_malloc_info(objspace);
{
int64_t inc = gc_malloc_counters_increase(objspace, &objspace->malloc_counters.counters);
gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.counters);
size_t old_limit = malloc_limit;
/* A net-negative `inc` (more freed than malloc'd since last GC) is
@@ -6724,6 +6718,8 @@ gc_reset_malloc_info(rb_objspace_t *objspace, bool full_mark)
gc_params.oldmalloc_limit_max);
}
else {
gc_malloc_counters_snapshot(objspace, &objspace->malloc_counters.oldcounters);
if ((objspace->profile.latest_gc_info & GPR_FLAG_MAJOR_BY_OLDMALLOC) == 0) {
objspace->rgengc.oldmalloc_increase_limit =
(size_t)(objspace->rgengc.oldmalloc_increase_limit / ((gc_params.oldmalloc_limit_growth_factor - 1)/10 + 1));