Symptom
On current dev3 (f45596ab), a full-debug build hangs deterministically when one thread creates enough heaps to grow the dynamic thread-local slot array beyond its initial capacity.
This is reachable through public APIs only:
#include <mimalloc.h>
int main(void) {
mi_heap_t* heaps[40];
for (size_t i = 0; i < 40; i++) {
heaps[i] = mi_heap_new();
if (heaps[i] == NULL) return 1;
if (mi_heap_malloc(heaps[i], 32) == NULL) return 2;
}
for (size_t i = 0; i < 40; i++) mi_heap_destroy(heaps[i]);
return 0;
}
Configuration and result on stock upstream:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DMI_DEBUG_FULL=ON \
-DMI_OVERRIDE=OFF \
-DMI_BUILD_SHARED=OFF \
-DMI_BUILD_STATIC=ON
cmake --build build --target mimalloc-test-threadlocal-growth
timeout 10s build/mimalloc-test-threadlocal-growth
exit 124 (deterministic timeout)
Tested with GCC 14.2.1 on Linux. The same focused test also hangs with MinGW-w64 GCC 14.2.0.
Cause
The dynamic slot array was moved to meta allocation in ac61f0fb. Its growth path now forms a same-thread lock cycle:
mi_thread_locals_expand
-> _mi_meta_rezalloc
-> acquire subproc->theap_meta_lock
-> mi_theap_rezalloc
-> moving realloc calls mi_free(oldp)
-> mi_stat_free recognizes a meta page
-> acquire subproc->theap_meta_lock again
theap_meta_lock is not recursive, so the second acquisition never completes. An attached debugger stops in __lll_lock_wait.
The move is guaranteed once the slot array crosses a size class, so this is not timing-dependent. Applications that create enough independent heaps can make assertion/statistics-enabled allocator builds stop making progress.
Proposed fix
The slot array has a simpler lifetime rule than a general realloc needs:
- allocate a zeroed replacement from the meta allocator while it holds its lock;
- copy only the old logical header and slots;
- publish the replacement to thread-local storage;
- free the old meta block after the allocation lock has been released.
This removes the nested lock acquisition and explicitly guarantees that newly added slots are zero. _mi_meta_rezalloc has no other callers and can be removed.
I have a focused regression test that is RED by deterministic timeout before the change and exits successfully afterward. I will send the patch as a separate PR referencing this issue.
Symptom
On current
dev3(f45596ab), a full-debug build hangs deterministically when one thread creates enough heaps to grow the dynamic thread-local slot array beyond its initial capacity.This is reachable through public APIs only:
Configuration and result on stock upstream:
Tested with GCC 14.2.1 on Linux. The same focused test also hangs with MinGW-w64 GCC 14.2.0.
Cause
The dynamic slot array was moved to meta allocation in
ac61f0fb. Its growth path now forms a same-thread lock cycle:theap_meta_lockis not recursive, so the second acquisition never completes. An attached debugger stops in__lll_lock_wait.The move is guaranteed once the slot array crosses a size class, so this is not timing-dependent. Applications that create enough independent heaps can make assertion/statistics-enabled allocator builds stop making progress.
Proposed fix
The slot array has a simpler lifetime rule than a general realloc needs:
This removes the nested lock acquisition and explicitly guarantees that newly added slots are zero.
_mi_meta_rezallochas no other callers and can be removed.I have a focused regression test that is RED by deterministic timeout before the change and exits successfully afterward. I will send the patch as a separate PR referencing this issue.