@@ -699,13 +699,50 @@ static void mi_detect_cpu_features(void) {
699699
700700// Initialize the process; called by thread_init or the process loader
701701void 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)
0 commit comments