Skip to content

Commit b27a808

Browse files
authored
[GC] Avoid OOM in large-allocation-only workloads (#105521)
A few problems can occur with workloads that only allocate huge objects: - Regions can be pushed to the global decommit list faster than we can process them. This can lead to OOM (more easily reproducible using a hard heap limit). - BGCs don't currently call `distribute_free_regions`, so if only those occur, then the freelist processing never occurs at all. This change addresses those by doing the following: - Call `distribute_free_regions` during `background_mark_phase` (while we initially have the VM suspended to avoid complications). - Currently, `distribute_free_regions` can't hit the `move_highest_free_regions` code path when `background_running_p()`. Change that check to also require `settings.condemned_generation != max_generation`. - Slow the movement of huge regions from the freelist to the global decommit list. - Add aging to regions in `global_free_huge_regions`. - Require an age of MIN_AGE_TO_DECOMMIT_HUGE (2) before including them in a surplus balance. - Factor aging code into `age_free_regions`. Fixes #94175
1 parent 8a46116 commit b27a808

File tree

2 files changed

+93
-31
lines changed

2 files changed

+93
-31
lines changed

src/coreclr/gc/gc.cpp

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9651,7 +9651,7 @@ int gc_heap::grow_brick_card_tables (uint8_t* start,
96519651
dprintf (GC_TABLE_LOG, ("card table: %zx(translated: %zx), seg map: %zx, mark array: %zx",
96529652
(size_t)ct, (size_t)translated_ct, (size_t)new_seg_mapping_table, (size_t)card_table_mark_array (ct)));
96539653

9654-
if (hp->is_bgc_in_progress())
9654+
if (is_bgc_in_progress())
96559655
{
96569656
dprintf (GC_TABLE_LOG, ("new low: %p, new high: %p, latest mark array is %p(translate: %p)",
96579657
saved_g_lowest_address, saved_g_highest_address,
@@ -9778,7 +9778,7 @@ int gc_heap::grow_brick_card_tables (uint8_t* start,
97789778
else
97799779
{
97809780
#ifdef BACKGROUND_GC
9781-
if (hp->is_bgc_in_progress())
9781+
if (is_bgc_in_progress())
97829782
{
97839783
dprintf (GC_TABLE_LOG, ("in range new seg %p, mark_array is %p", new_seg, hp->mark_array));
97849784
if (!commit_mark_array_new_seg (hp, new_seg))
@@ -13076,6 +13076,21 @@ void region_free_list::age_free_regions (region_free_list free_lists[count_free_
1307613076
}
1307713077
}
1307813078

13079+
size_t region_free_list::get_size_free_regions(int max_age)
13080+
{
13081+
size_t result = 0;
13082+
13083+
for (heap_segment* region = head_free_region; region != nullptr; region = heap_segment_next (region))
13084+
{
13085+
if (heap_segment_age_in_free (region) <= max_age)
13086+
{
13087+
result += get_region_size(region);
13088+
}
13089+
}
13090+
13091+
return result;
13092+
}
13093+
1307913094
void region_free_list::print (int hn, const char* msg, int* ages)
1308013095
{
1308113096
dprintf (3, ("h%2d PRINTING-------------------------------", hn));
@@ -13371,6 +13386,7 @@ void gc_heap::distribute_free_regions()
1337113386
global_free_huge_regions.transfer_regions (&global_regions_to_decommit[huge_free_region]);
1337213387

1337313388
size_t free_space_in_huge_regions = global_free_huge_regions.get_size_free_regions();
13389+
size_t free_space_in_young_huge_regions = global_free_huge_regions.get_size_free_regions(MIN_AGE_TO_DECOMMIT_HUGE - 1);
1337413390

1337513391
ptrdiff_t num_regions_to_decommit[kind_count];
1337613392
int region_factor[kind_count] = { 1, LARGE_REGION_FACTOR };
@@ -13384,6 +13400,7 @@ void gc_heap::distribute_free_regions()
1338413400
#endif //!MULTIPLE_HEAPS
1338513401

1338613402
size_t num_huge_region_units_to_consider[kind_count] = { 0, free_space_in_huge_regions / region_size[large_free_region] };
13403+
size_t num_young_huge_region_units_to_consider[kind_count] = { 0, free_space_in_young_huge_regions / region_size[large_free_region] };
1338713404

1338813405
for (int kind = basic_free_region; kind < kind_count; kind++)
1338913406
{
@@ -13402,9 +13419,22 @@ void gc_heap::distribute_free_regions()
1340213419

1340313420
ptrdiff_t balance = total_num_free_regions[kind] + num_huge_region_units_to_consider[kind] - total_budget_in_region_units[kind];
1340413421

13422+
// Ignore young huge regions if they are contributing to a surplus.
13423+
if (balance > 0)
13424+
{
13425+
if (balance > static_cast<ptrdiff_t>(num_young_huge_region_units_to_consider[kind]))
13426+
{
13427+
balance -= num_young_huge_region_units_to_consider[kind];
13428+
}
13429+
else
13430+
{
13431+
balance = 0;
13432+
}
13433+
}
13434+
1340513435
if (
1340613436
#ifdef BACKGROUND_GC
13407-
background_running_p() ||
13437+
(background_running_p() && (settings.condemned_generation != max_generation)) ||
1340813438
#endif
1340913439
(balance < 0))
1341013440
{
@@ -13588,6 +13618,43 @@ void gc_heap::distribute_free_regions()
1358813618
#endif //USE_REGIONS
1358913619
}
1359013620

13621+
void gc_heap::age_free_regions(const char* label)
13622+
{
13623+
#ifdef USE_REGIONS
13624+
// If we are doing an ephemeral GC as a precursor to a BGC, then we will age all of the region
13625+
// kinds during the ephemeral GC and skip the call to age_free_regions during the BGC itself.
13626+
bool age_all_region_kinds = (settings.condemned_generation == max_generation) || is_bgc_in_progress();
13627+
if (age_all_region_kinds)
13628+
{
13629+
global_free_huge_regions.age_free_regions();
13630+
}
13631+
13632+
#ifdef MULTIPLE_HEAPS
13633+
for (int i = 0; i < gc_heap::n_heaps; i++)
13634+
{
13635+
gc_heap* hp = gc_heap::g_heaps[i];
13636+
#else //MULTIPLE_HEAPS
13637+
{
13638+
gc_heap* hp = pGenGCHeap;
13639+
const int i = 0;
13640+
#endif //MULTIPLE_HEAPS
13641+
13642+
if (age_all_region_kinds)
13643+
{
13644+
// age and print all kinds of free regions
13645+
region_free_list::age_free_regions (hp->free_regions);
13646+
region_free_list::print (hp->free_regions, i, label);
13647+
}
13648+
else
13649+
{
13650+
// age and print only basic free regions
13651+
hp->free_regions[basic_free_region].age_free_regions();
13652+
hp->free_regions[basic_free_region].print (i, label);
13653+
}
13654+
}
13655+
#endif //USE_REGIONS
13656+
}
13657+
1359113658
#ifdef WRITE_WATCH
1359213659
uint8_t* g_addresses [array_size+2]; // to get around the bug in GetWriteWatch
1359313660

@@ -22765,21 +22832,8 @@ void gc_heap::gc1()
2276522832
for (int i = 0; i < gc_heap::n_heaps; i++)
2276622833
{
2276722834
g_heaps[i]->descr_generations ("END");
22768-
#ifdef USE_REGIONS
22769-
if (settings.condemned_generation == max_generation)
22770-
{
22771-
// age and print all kinds of free regions
22772-
region_free_list::age_free_regions (g_heaps[i]->free_regions);
22773-
region_free_list::print (g_heaps[i]->free_regions, i, "END");
22774-
}
22775-
else
22776-
{
22777-
// age and print only basic free regions
22778-
g_heaps[i]->free_regions[basic_free_region].age_free_regions();
22779-
g_heaps[i]->free_regions[basic_free_region].print (i, "END");
22780-
}
22781-
#endif //USE_REGIONS
2278222835
}
22836+
age_free_regions ("END");
2278322837

2278422838
#ifdef DYNAMIC_HEAP_COUNT
2278522839
update_total_soh_stable_size();
@@ -22819,18 +22873,7 @@ void gc_heap::gc1()
2281922873
compute_gc_and_ephemeral_range (settings.condemned_generation, true);
2282022874
stomp_write_barrier_ephemeral (ephemeral_low, ephemeral_high,
2282122875
map_region_to_generation_skewed, (uint8_t)min_segment_size_shr);
22822-
if (settings.condemned_generation == max_generation)
22823-
{
22824-
// age and print all kinds of free regions
22825-
region_free_list::age_free_regions(free_regions);
22826-
region_free_list::print(free_regions, 0, "END");
22827-
}
22828-
else
22829-
{
22830-
// age and print only basic free regions
22831-
free_regions[basic_free_region].age_free_regions();
22832-
free_regions[basic_free_region].print (0, "END");
22833-
}
22876+
age_free_regions ("END");
2283422877
#endif //USE_REGIONS
2283522878

2283622879
update_end_ngc_time();
@@ -37501,7 +37544,15 @@ void gc_heap::allow_fgc()
3750137544

3750237545
BOOL gc_heap::is_bgc_in_progress()
3750337546
{
37504-
return (background_running_p() || (current_bgc_state == bgc_initialized));
37547+
#ifdef MULTIPLE_HEAPS
37548+
// All heaps are changed to/from the bgc_initialized state during the VM suspension at the start of BGC,
37549+
// so checking any heap will work.
37550+
gc_heap* hp = gc_heap::g_heaps[0];
37551+
#else
37552+
gc_heap* hp = pGenGCHeap;
37553+
#endif //MULTIPLE_HEAPS
37554+
37555+
return (background_running_p() || (hp->current_bgc_state == bgc_initialized));
3750537556
}
3750637557

3750737558
void gc_heap::clear_commit_flag()
@@ -38016,6 +38067,14 @@ void gc_heap::background_mark_phase ()
3801638067
if (bgc_t_join.joined())
3801738068
#endif //MULTIPLE_HEAPS
3801838069
{
38070+
// There's no need to distribute a second time if we just did an ephemeral GC, and we don't want to
38071+
// age the free regions twice.
38072+
if (!do_ephemeral_gc_p)
38073+
{
38074+
distribute_free_regions ();
38075+
age_free_regions ("BGC");
38076+
}
38077+
3801938078
#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
3802038079
// Resetting write watch for software write watch is pretty fast, much faster than for hardware write watch. Reset
3802138080
// can be done while the runtime is suspended or after the runtime is restarted, the preference was to reset while

src/coreclr/gc/gcpriv.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,7 @@ class region_free_list
14021402
size_t get_num_free_regions();
14031403
size_t get_size_committed_in_free() { return size_committed_in_free_regions; }
14041404
size_t get_size_free_regions() { return size_free_regions; }
1405+
size_t get_size_free_regions(int max_age);
14051406
heap_segment* get_first_free_region() { return head_free_region; }
14061407
static void unlink_region (heap_segment* region);
14071408
static void add_region (heap_segment* region, region_free_list to_free_list[count_free_region_kinds]);
@@ -2410,6 +2411,7 @@ class gc_heap
24102411
#endif //!USE_REGIONS
24112412
PER_HEAP_METHOD void delay_free_segments();
24122413
PER_HEAP_ISOLATED_METHOD void distribute_free_regions();
2414+
PER_HEAP_ISOLATED_METHOD void age_free_regions(const char* label);
24132415
#ifdef BACKGROUND_GC
24142416
PER_HEAP_ISOLATED_METHOD void reset_write_watch_for_gc_heap(void* base_address, size_t region_size);
24152417
PER_HEAP_ISOLATED_METHOD void get_write_watch_for_gc_heap(bool reset, void *base_address, size_t region_size, void** dirty_pages, uintptr_t* dirty_page_count_ref, bool is_runtime_suspended);
@@ -3186,7 +3188,7 @@ class gc_heap
31863188
// Restores BGC settings if necessary.
31873189
PER_HEAP_ISOLATED_METHOD void recover_bgc_settings();
31883190

3189-
PER_HEAP_METHOD BOOL is_bgc_in_progress();
3191+
PER_HEAP_ISOLATED_METHOD BOOL is_bgc_in_progress();
31903192

31913193
PER_HEAP_METHOD void clear_commit_flag();
31923194

@@ -6045,6 +6047,7 @@ class heap_segment
60456047
// the region's free list.
60466048
#define MAX_AGE_IN_FREE 99
60476049
#define AGE_IN_FREE_TO_DECOMMIT 20
6050+
#define MIN_AGE_TO_DECOMMIT_HUGE 2
60486051
int age_in_free;
60496052
// This is currently only used by regions that are swept in plan -
60506053
// we then thread this list onto the generation's free list.

0 commit comments

Comments
 (0)