blob: 5dfccb66c0354a3c0ea29927a2025c8a3f26e2ea [file] [edit]
#include "../src/static.c"
#include "testhelper.h"
#include <string.h>
#define BLOCKS 2048
static void* blocks[BLOCKS];
static size_t sizes[BLOCKS];
static size_t block_count;
static mi_heap_t* disposable;
static int mode;
static _Atomic(size_t) owner_ready;
static _Atomic(size_t) owner_exit;
static bool find_live(const mi_heap_t* heap, const mi_heap_area_t* area,
void* block, size_t size, void* arg) {
MI_UNUSED(heap); MI_UNUSED(area); MI_UNUSED(size);
if (block != NULL) {
for (size_t i = 0; i < block_count; i++) {
if (block == blocks[i]) { *(bool*)arg = true; break; }
}
}
return true;
}
static bool all_freed(void) {
bool live = false;
mi_collect(true);
return (mi_heap_visit_blocks(mi_heap_main(), true, find_live, &live) &&
mi_heap_visit_abandoned_blocks(mi_heap_main(), true, find_live, &live) && !live);
}
static bool allocate_blocks(void) {
for (size_t i = 0; i < block_count; i++) {
sizes[i] = (mode == 3 ? 8 + (i * 197) % 16000 : 64);
blocks[i] = (disposable == NULL ? mi_malloc(sizes[i]) : mi_heap_malloc(disposable, sizes[i]));
if (blocks[i] == NULL) return false;
memset(blocks[i], (int)(i % 251), sizes[i]);
}
return true;
}
static bool free_blocks(void) {
mi_thread_init();
mi_collect(true);
mi_tld_t* const tld = _mi_theap_default()->tld;
bool pending = false;
for (size_t i = 0; i < block_count; i++) {
const unsigned char* p = (const unsigned char*)blocks[i];
if (p[0] != i % 251 || p[sizes[i]-1] != i % 251) return false;
mi_free(blocks[i]);
if (mi_atomic_load_relaxed(&tld->free_batch.guard) != 0) return false;
if (tld->free_batch.batched_total >= MI_FREE_BATCH_BYTES) return false;
if (tld->free_batch.batched_total != 0) pending = true;
}
if (disposable != NULL) {
if (tld->free_batch.batched_total != 0) return false;
mi_heap_destroy(disposable);
disposable = NULL;
mi_collect(true);
return true;
}
if (!pending) return false;
if (mode == 0) {
mi_collect(true);
return tld->free_batch.batched_total == 0;
}
if (mode == 1) {
mi_thread_done();
if (_mi_thread_is_initialized()) return false;
mi_thread_init();
return _mi_theap_default()->tld->free_batch.batched_total == 0;
}
return true; // automatic thread cleanup must publish the remaining blocks
}
static bool test_remote_free(int test_mode) {
mode = test_mode;
block_count = (mode == 3 ? BLOCKS : 128);
return allocate_blocks() && mi_run_on_thread(free_blocks) && all_freed();
}
static bool free_without_thread_state(void) {
mi_thread_done();
if (_mi_thread_is_initialized()) return false;
for (size_t i = 0; i < block_count; i++) mi_free(blocks[i]);
return !_mi_thread_is_initialized();
}
static bool test_uninitialized_free(void) {
mode = 0;
block_count = 128;
return allocate_blocks() && mi_run_on_thread(free_without_thread_state) && all_freed();
}
static bool test_abandoned_owner(void) {
mode = 0;
block_count = 128;
if (!mi_run_on_thread(allocate_blocks)) return false;
for (size_t i = 0; i < block_count; i++) mi_free(blocks[i]);
return all_freed();
}
static bool test_destroyed_heap(void) {
mode = 0;
block_count = 128;
disposable = mi_heap_new();
return disposable != NULL && allocate_blocks() && mi_run_on_thread(free_blocks);
}
static bool free_with_busy_guard(void) {
mi_thread_init();
mi_collect(true);
mi_tld_t* const tld = _mi_theap_default()->tld;
mi_free(blocks[0]);
const size_t pending = tld->free_batch.batched_total;
if (pending == 0) return false;
if (!mi_free_batch_try_acquire(tld)) return false;
mi_free(blocks[1]);
const bool bypassed = (tld->free_batch.batched_total == pending &&
mi_atomic_load_relaxed(&tld->free_batch.guard) == 1);
mi_free_batch_release(tld);
mi_heap_t* const heap = tld->free_batch.heap;
tld->free_batch.heap = NULL;
tld->free_batch.collecting = true;
if (!mi_free_batch_try_acquire(tld)) return false;
mi_free(blocks[2]);
_mi_free_collect(tld);
const bool reentered = (tld->free_batch.batched_total == pending &&
mi_atomic_load_relaxed(&tld->free_batch.guard) == 1);
tld->free_batch.collecting = false;
tld->free_batch.heap = heap;
mi_free_batch_release(tld);
mi_atomic_store_release(&tld->free_batch.guard, 2);
mi_free(blocks[3]);
const bool handed_back = (tld->free_batch.batched_total > pending &&
mi_atomic_load_relaxed(&tld->free_batch.guard) == 0);
for (size_t i = 4; i < block_count; i++) mi_free(blocks[i]);
mi_collect(true);
return bypassed && reentered && handed_back && tld->free_batch.batched_total == 0;
}
static bool test_busy_guard(void) {
mode = 0;
block_count = 128;
return allocate_blocks() && mi_run_on_thread(free_with_busy_guard) && all_freed();
}
static bool departing_owner(void) {
const bool allocated = allocate_blocks();
mi_atomic_store_release(&owner_ready, allocated ? 1 : 2);
while (allocated && mi_atomic_load_acquire(&owner_exit) == 0) _mi_prim_thread_yield();
return allocated;
}
static bool test_concurrent_owner_exit(void) {
mode = 3;
block_count = BLOCKS;
for (size_t round = 0; round < 8; round++) {
mi_atomic_store_relaxed(&owner_ready, 0);
mi_atomic_store_relaxed(&owner_exit, 0);
mi_thread_fun_args_t arg = { departing_owner, false };
#ifdef _WIN32
HANDLE thread = CreateThread(NULL, 0, mi_win_thread_entry, &arg, 0, NULL);
if (thread == NULL) return false;
#else
pthread_t thread;
if (pthread_create(&thread, NULL, mi_pthread_entry, &arg) != 0) return false;
#endif
size_t ready;
while ((ready = mi_atomic_load_acquire(&owner_ready)) == 0) _mi_prim_thread_yield();
mi_atomic_store_release(&owner_exit, 1);
if (ready == 1) {
for (size_t i = 0; i < block_count; i++) mi_free(blocks[i]);
}
#ifdef _WIN32
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
#else
if (pthread_join(thread, NULL) != 0) return false;
#endif
if (!arg.result || !all_freed()) return false;
}
return true;
}
int main(void) {
mi_option_set(mi_option_guarded_sample_rate, 0);
CHECK("remote free explicit collect", test_remote_free(0));
CHECK("remote free thread reinitialization", test_remote_free(1));
CHECK("remote free automatic thread exit", test_remote_free(2));
CHECK("remote free bounded cache", test_remote_free(3));
CHECK("free without thread initialization", test_uninitialized_free());
CHECK("remote free abandoned owner", test_abandoned_owner());
CHECK("destroy heap after remote frees", test_destroyed_heap());
CHECK("remote free busy guard and reentry", test_busy_guard());
CHECK("remote free concurrent owner exit", test_concurrent_owner_exit());
return print_test_summary();
}