Skip to content

Arena allocator pre-maturely returns NOMEM on parent arena while space is still available in child arena #1403

Description

@OscarTHZhang

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;
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions