Looks like when registering a very large arena, mimalloc internally split it into smaller sub-arenas (16 GiB each) with parent-children relationship. Then if a series of allocation exhausted the parent arena, the mimalloc API would pre-maturely return NOMEM even though there are still space in the child arenas. This looks like a bug in the macro to iterate over the sub-arenas in the arena.c code.
Repro (version: v3)
#include <mimalloc.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#define GIB ((size_t)1024 * 1024 * 1024)
#define MIB ((size_t)1024 * 1024)
int main(void) {
const size_t arena_size = 24 * GIB;
const size_t allocation_size = 64 * MIB;
const size_t allocation_count = 272; // 17 GiB: requires a child arena.
const size_t alignment = mi_arena_min_alignment();
const size_t mapping_size = arena_size + alignment;
void* mapping = mmap(NULL, mapping_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
if (mapping == MAP_FAILED) {
perror("mmap");
return 1;
}
const uintptr_t aligned =
((uintptr_t)mapping + alignment - 1) & ~((uintptr_t)alignment - 1);
mi_arena_id_t arena_id = NULL;
if (!mi_manage_os_memory_ex((void*)aligned, arena_size,
true, /* committed */
false, /* pinned */
true, /* initially zero */
-1, /* no NUMA preference */
true, /* exclusive */
&arena_id)) {
fprintf(stderr, "failed to register the 24 GiB arena\n");
return 1;
}
mi_heap_t* heap = mi_heap_new_in_arena(arena_id);
if (heap == NULL) {
fprintf(stderr, "failed to create the arena heap\n");
return 1;
}
for (size_t i = 0; i < allocation_count; i++) {
void* p = mi_heap_malloc(heap, allocation_size);
if (p == NULL) {
fprintf(stderr,
"allocation %zu failed after %zu MiB; child arena was not used\n",
i, i * allocation_size / MIB);
return 1;
}
if (!mi_arena_contains(arena_id, p)) {
fprintf(stderr, "allocation %zu came from outside the requested arena\n", i);
return 1;
}
}
printf("allocated %zu MiB from the parent arena and its children\n",
allocation_count * allocation_size / MIB);
return 0;
}
Looks like when registering a very large arena, mimalloc internally split it into smaller sub-arenas (16 GiB each) with parent-children relationship. Then if a series of allocation exhausted the parent arena, the mimalloc API would pre-maturely return NOMEM even though there are still space in the child arenas. This looks like a bug in the macro to iterate over the sub-arenas in the arena.c code.
Repro (version: v3)