Skip to content

Commit b822796

Browse files
author
jinpzhan
committed
Fix mimalloc init re-entrancy on Windows (TLS recursion + page-map)
1 parent dfa50c3 commit b822796

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

include/mimalloc/prim.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,15 @@ We try to circumvent this in an efficient way:
353353

354354
static inline mi_heap_t* mi_prim_get_default_heap(void);
355355

356+
// On Windows, accessing `__declspec(thread)` storage can trigger on-demand TLS
357+
// initialization (`__dyn_tls_init`) which may run user TLS constructors that
358+
// allocate memory. During mimalloc process initialization this can cause
359+
// recursive allocations before global state (like the page map) is ready.
360+
// Guard against this by avoiding TLS access before `mi_process_init` completes.
361+
#if defined(_WIN32) && !defined(MI_TLS_RECURSE_GUARD)
362+
#define MI_TLS_RECURSE_GUARD 1
363+
#endif
364+
356365
#if defined(MI_MALLOC_OVERRIDE)
357366
#if defined(__APPLE__) // macOS
358367
#define MI_TLS_SLOT 89 // seems unused?

src/init.c

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -699,13 +699,50 @@ static void mi_detect_cpu_features(void) {
699699

700700
// Initialize the process; called by thread_init or the process loader
701701
void mi_process_init(void) mi_attr_noexcept {
702-
// ensure we are called once
703-
static mi_atomic_once_t process_init;
702+
// Ensure initialization runs exactly once.
703+
//
704+
// Note: we cannot use `mi_atomic_once` directly because it is non-blocking:
705+
// other threads may observe initialization as "done" while it is still in
706+
// progress, which can lead to asserts like `_mi_page_map != NULL`.
707+
//
708+
// At the same time, `mi_process_init()` is re-entrant on the initializing
709+
// thread (via `mi_thread_init()` calling back into `mi_process_init()`), so
710+
// we must not block the owner thread while it is initializing.
711+
//
712+
// State: 0 = not started, 1 = in progress, 2 = done.
713+
static mi_atomic_once_t process_init_state;
714+
static _Atomic(uintptr_t) process_init_owner_tid;
715+
716+
const uintptr_t tid = (uintptr_t)_mi_thread_id();
717+
uintptr_t state = mi_atomic_load_acquire(&process_init_state);
718+
if (state == 2) return;
719+
720+
if (state == 1) {
721+
// If we re-enter on the initializing thread, return immediately.
722+
if (mi_atomic_load_relaxed(&process_init_owner_tid) == tid) return;
723+
// Otherwise wait until the initialization completes.
724+
do {
725+
mi_atomic_yield();
726+
state = mi_atomic_load_acquire(&process_init_state);
727+
} while (state != 2);
728+
return;
729+
}
730+
731+
uintptr_t expected = 0;
732+
if (!mi_atomic_cas_strong_acq_rel(&process_init_state, &expected, (uintptr_t)1)) {
733+
// Someone else raced us; follow the waiting path.
734+
do {
735+
mi_atomic_yield();
736+
state = mi_atomic_load_acquire(&process_init_state);
737+
} while (state != 2);
738+
return;
739+
}
740+
741+
mi_atomic_store_release(&process_init_owner_tid, tid);
742+
704743
#if _MSC_VER < 1920
705744
mi_heap_main_init(); // vs2017 can dynamically re-initialize heap_main
706745
#endif
707-
if (!mi_atomic_once(&process_init)) return;
708-
_mi_process_is_initialized = true;
709746
_mi_verbose_message("process init: 0x%zx\n", _mi_thread_id());
710747

711748
mi_detect_cpu_features();
@@ -744,6 +781,9 @@ void mi_process_init(void) mi_attr_noexcept {
744781
mi_reserve_os_memory((size_t)ksize*MI_KiB, true, true);
745782
}
746783
}
784+
785+
_mi_process_is_initialized = true;
786+
mi_atomic_store_release(&process_init_state, (uintptr_t)2);
747787
}
748788

749789
// Called when the process is done (cdecl as it is used with `at_exit` on some platforms)

src/page-map.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,10 @@ static size_t mi_page_map_get_idx(mi_page_t* page, size_t* sub_idx, size_t* slic
338338
void _mi_page_map_register(mi_page_t* page) {
339339
mi_assert_internal(page != NULL);
340340
mi_assert_internal(_mi_is_aligned(page, MI_PAGE_ALIGN));
341-
mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!
342341
if mi_unlikely(_mi_page_map == NULL) {
343342
if (!_mi_page_map_init()) return;
344343
}
344+
mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!
345345
mi_assert(_mi_page_map!=NULL);
346346
size_t slice_count;
347347
size_t sub_idx;

0 commit comments

Comments
 (0)