Do not return superpages that had remote frees to the OS

This commit is contained in:
Feoramund
2025-04-12 22:55:23 -04:00
parent 5e773aaf56
commit a0425ca220

View File

@@ -289,6 +289,8 @@ Heap_Superpage :: struct {
prev: ^Heap_Superpage,
next: ^Heap_Superpage, // atomic in orphanage, otherwise non-atomic
has_ever_been_remotely_freed: bool,
remote_free_set: bool, // atomic
owner: int, // atomic
master_cache_block: ^Heap_Cache_Block, // atomic
@@ -436,7 +438,21 @@ heap_free_superpage :: proc "contextless" (superpage: ^Heap_Superpage) {
//
// The rest are put to a global orphanage so that the memory is ready to be
// used as soon as needed.
if intrinsics.atomic_add_explicit(&heap_orphanage_count, 1, .Acq_Rel) >= HEAP_MAX_EMPTY_ORPHANED_SUPERPAGES {
//
// The one exception is Superpages that have had a free operation done on
// them by another thread: Because each heap has a cache for those
// superpages and it is possible for the remote thread to be pre-empted
// after it has freed the final bin but before it has added it to the
// owning thread's cache, we cannot ever return that memory to the
// operating system, as it would eventually lead to an access violation.
//
// It is impossible to safely prove that no other threads are busy with the
// superpage. Even if we were to use a counter on the superpage, the
// superpage could end up being freed before the remote thread has a chance
// to decrement it.
if superpage.has_ever_been_remotely_freed {
heap_push_orphan(superpage)
} else if intrinsics.atomic_add_explicit(&heap_orphanage_count, 1, .Acq_Rel) >= HEAP_MAX_EMPTY_ORPHANED_SUPERPAGES {
intrinsics.atomic_sub_explicit(&heap_orphanage_count, 1, .Relaxed)
free_virtual_memory(superpage, SUPERPAGE_SIZE)
heap_debug_cover(.Superpage_Freed_On_Full_Orphanage)
@@ -504,6 +520,8 @@ heap_cache_register_superpage :: proc "contextless" (superpage: ^Heap_Superpage)
// possible for another thread to remotely free memory while
// the thread which owned it is in limbo.
if intrinsics.atomic_load_explicit(&slab.remote_free_bins_scheduled, .Acquire) > 0 {
superpage.has_ever_been_remotely_freed = true
heap_merge_remote_frees(slab)
if slab.free_bins > 0 {
@@ -1386,6 +1404,8 @@ heap_alloc :: proc "contextless" (size: int, zero_memory: bool = true) -> (ptr:
continue consume_loop
}
superpage.has_ever_been_remotely_freed = true
// First, we will remove the cache entry, to avoid issues with
// freeing operations that happen simultaneously while we're
// merging.