Add custom assert callback (#236)

diff --git a/CHANGELOG b/CHANGELOG
index b7681db..cfc9e73 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -14,6 +14,8 @@
 API breaking change - added flag to rpmalloc_thread_finalize to avoid releasing thread caches.
 Pass nonzero value to retain old behaviour of releasing thread caches to global cache.
 
+Add option to config to set a custom error callback for assert failures (if ENABLE_ASSERT)
+
 
 1.4.1
 
diff --git a/rpmalloc/rpmalloc.c b/rpmalloc/rpmalloc.c
index d8329ec..597e7e9 100644
--- a/rpmalloc/rpmalloc.c
+++ b/rpmalloc/rpmalloc.c
@@ -178,9 +178,21 @@
 #    define _DEBUG
 #  endif
 #  include <assert.h>
+#define RPMALLOC_TOSTRING_M(x) #x
+#define RPMALLOC_TOSTRING(x) RPMALLOC_TOSTRING_M(x)
+#define rpmalloc_assert(truth, message)                                                                                 \
+	do {                                                                                                                \
+		if (!(truth)) {                                                                                                 \
+			if (_memory_config.error_callback) {                                                                        \
+				_memory_config.error_callback(                                                                          \
+				    message##" ("##RPMALLOC_TOSTRING(truth)##") at "##__FILE__##":"##RPMALLOC_TOSTRING(__LINE__));      \
+			} else {                                                                                                    \
+				assert((truth) && message);                                                                             \
+			}                                                                                                           \
+		}                                                                                                               \
+	} while (0)
 #else
-#  undef  assert
-#  define assert(x) do {} while(0)
+#  define rpmalloc_assert(truth, message) do {} while(0)
 #endif
 #if ENABLE_STATISTICS
 #  include <stdio.h>
@@ -812,8 +824,8 @@
 //  returns address to start of mapped region to use
 static void*
 _rpmalloc_mmap(size_t size, size_t* offset) {
-	assert(!(size % _memory_page_size));
-	assert(size >= _memory_page_size);
+	rpmalloc_assert(!(size % _memory_page_size), "Invalid mmap size");
+	rpmalloc_assert(size >= _memory_page_size, "Invalid mmap size");
 	_rpmalloc_stat_add_peak(&_mapped_pages, (size >> _memory_page_size_shift), _mapped_pages_peak);
 	_rpmalloc_stat_add(&_mapped_total, (size >> _memory_page_size_shift));
 	return _memory_config.memory_map(size, offset);
@@ -826,10 +838,10 @@
 //  release is set to 0 for partial unmap, or size of entire range for a full unmap
 static void
 _rpmalloc_unmap(void* address, size_t size, size_t offset, size_t release) {
-	assert(!release || (release >= size));
-	assert(!release || (release >= _memory_page_size));
+	rpmalloc_assert(!release || (release >= size), "Invalid unmap size");
+	rpmalloc_assert(!release || (release >= _memory_page_size), "Invalid unmap size");
 	if (release) {
-		assert(!(release % _memory_page_size));
+		rpmalloc_assert(!(release % _memory_page_size), "Invalid unmap size");
 		_rpmalloc_stat_sub(&_mapped_pages, (release >> _memory_page_size_shift));
 		_rpmalloc_stat_add(&_unmapped_total, (release >> _memory_page_size_shift));
 	}
@@ -841,12 +853,12 @@
 _rpmalloc_mmap_os(size_t size, size_t* offset) {
 	//Either size is a heap (a single page) or a (multiple) span - we only need to align spans, and only if larger than map granularity
 	size_t padding = ((size >= _memory_span_size) && (_memory_span_size > _memory_map_granularity)) ? _memory_span_size : 0;
-	assert(size >= _memory_page_size);
+	rpmalloc_assert(size >= _memory_page_size, "Invalid mmap size");
 #if PLATFORM_WINDOWS
 	//Ok to MEM_COMMIT - according to MSDN, "actual physical pages are not allocated unless/until the virtual addresses are actually accessed"
 	void* ptr = VirtualAlloc(0, size + padding, (_memory_huge_pages ? MEM_LARGE_PAGES : 0) | MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
 	if (!ptr) {
-		assert(ptr && "Failed to map virtual memory block");
+		rpmalloc_assert(ptr, "Failed to map virtual memory block");
 		return 0;
 	}
 #else
@@ -869,29 +881,29 @@
 #  endif
 	if ((ptr == MAP_FAILED) || !ptr) {
 		if (errno != ENOMEM)
-			assert("Failed to map virtual memory block" == 0);
+			rpmalloc_assert((ptr != MAP_FAILED) && ptr, "Failed to map virtual memory block");
 		return 0;
 	}
 #endif
 	_rpmalloc_stat_add(&_mapped_pages_os, (int32_t)((size + padding) >> _memory_page_size_shift));
 	if (padding) {
 		size_t final_padding = padding - ((uintptr_t)ptr & ~_memory_span_mask);
-		assert(final_padding <= _memory_span_size);
-		assert(final_padding <= padding);
-		assert(!(final_padding % 8));
+		rpmalloc_assert(final_padding <= _memory_span_size, "Internal failure in padding");
+		rpmalloc_assert(final_padding <= padding, "Internal failure in padding");
+		rpmalloc_assert(!(final_padding % 8), "Internal failure in padding");
 		ptr = pointer_offset(ptr, final_padding);
 		*offset = final_padding >> 3;
 	}
-	assert((size < _memory_span_size) || !((uintptr_t)ptr & ~_memory_span_mask));
+	rpmalloc_assert((size < _memory_span_size) || !((uintptr_t)ptr & ~_memory_span_mask), "Internal failure in padding");
 	return ptr;
 }
 
 //! Default implementation to unmap pages from virtual memory
 static void
 _rpmalloc_unmap_os(void* address, size_t size, size_t offset, size_t release) {
-	assert(release || (offset == 0));
-	assert(!release || (release >= _memory_page_size));
-	assert(size >= _memory_page_size);
+	rpmalloc_assert(release || (offset == 0), "Invalid unmap size");
+	rpmalloc_assert(!release || (release >= _memory_page_size), "Invalid unmap size");
+	rpmalloc_assert(size >= _memory_page_size, "Invalid unmap size");
 	if (release && offset) {
 		offset <<= 3;
 		address = pointer_offset(address, -(int32_t)offset);
@@ -903,12 +915,12 @@
 #if !DISABLE_UNMAP
 #if PLATFORM_WINDOWS
 	if (!VirtualFree(address, release ? 0 : size, release ? MEM_RELEASE : MEM_DECOMMIT)) {
-		assert(address && "Failed to unmap virtual memory block");
+		rpmalloc_assert(0, "Failed to unmap virtual memory block");
 	}
 #else
 	if (release) {
 		if (munmap(address, release)) {
-			assert("Failed to unmap virtual memory block" == 0);
+			rpmalloc_assert(0, "Failed to unmap virtual memory block");
 		}
 	} else {
 #if defined(MADV_FREE_REUSABLE)
@@ -923,7 +935,7 @@
 #else
 		if (posix_madvise(address, size, POSIX_MADV_DONTNEED)) {
 #endif
-			assert("Failed to madvise virtual memory block as free" == 0);
+			rpmalloc_assert(0, "Failed to madvise virtual memory block as free");
 		}
 	}
 #endif
@@ -978,7 +990,7 @@
 //! Pop head span from double linked list
 static void
 _rpmalloc_span_double_link_list_pop_head(span_t** head, span_t* span) {
-	assert(*head == span);
+	rpmalloc_assert(*head == span, "Linked list corrupted");
 	span = *head;
 	*head = span->next;
 }
@@ -986,7 +998,7 @@
 //! Remove a span from double linked list
 static void
 _rpmalloc_span_double_link_list_remove(span_t** head, span_t* span) {
-	assert(*head);
+	rpmalloc_assert(*head, "Linked list corrupted");
 	if (*head == span) {
 		*head = span->next;
 	} else {
@@ -1018,7 +1030,7 @@
 //! Declare the span to be a subspan and store distance from master span and span count
 static void
 _rpmalloc_span_mark_as_subspan_unless_master(span_t* master, span_t* subspan, size_t span_count) {
-	assert((subspan != master) || (subspan->flags & SPAN_FLAG_MASTER));
+	rpmalloc_assert((subspan != master) || (subspan->flags & SPAN_FLAG_MASTER), "Span master pointer and/or flag mismatch");
 	if (subspan != master) {
 		subspan->flags = SPAN_FLAG_SUBSPAN;
 		subspan->offset_from_master = (uint32_t)((uintptr_t)pointer_diff(subspan, master) >> _memory_span_size_shift);
@@ -1140,18 +1152,18 @@
 //! Unmap memory pages for the given number of spans (or mark as unused if no partial unmappings)
 static void
 _rpmalloc_span_unmap(span_t* span) {
-	assert((span->flags & SPAN_FLAG_MASTER) || (span->flags & SPAN_FLAG_SUBSPAN));
-	assert(!(span->flags & SPAN_FLAG_MASTER) || !(span->flags & SPAN_FLAG_SUBSPAN));
+	rpmalloc_assert((span->flags & SPAN_FLAG_MASTER) || (span->flags & SPAN_FLAG_SUBSPAN), "Span flag corrupted");
+	rpmalloc_assert(!(span->flags & SPAN_FLAG_MASTER) || !(span->flags & SPAN_FLAG_SUBSPAN), "Span flag corrupted");
 
 	int is_master = !!(span->flags & SPAN_FLAG_MASTER);
 	span_t* master = is_master ? span : ((span_t*)pointer_offset(span, -(intptr_t)((uintptr_t)span->offset_from_master * _memory_span_size)));
-	assert(is_master || (span->flags & SPAN_FLAG_SUBSPAN));
-	assert(master->flags & SPAN_FLAG_MASTER);
+	rpmalloc_assert(is_master || (span->flags & SPAN_FLAG_SUBSPAN), "Span flag corrupted");
+	rpmalloc_assert(master->flags & SPAN_FLAG_MASTER, "Span flag corrupted");
 
 	size_t span_count = span->span_count;
 	if (!is_master) {
 		//Directly unmap subspans (unless huge pages, in which case we defer and unmap entire page range with master)
-		assert(span->align_offset == 0);
+		rpmalloc_assert(span->align_offset == 0, "Span align offset corrupted");
 		if (_memory_span_size >= _memory_page_size) {
 			_rpmalloc_unmap(span, span_count * _memory_span_size, 0, 0);
 			_rpmalloc_stat_sub(&_reserved_spans, span_count);
@@ -1165,7 +1177,7 @@
 
 	if (atomic_add32(&master->remaining_spans, -(int32_t)span_count) <= 0) {
 		//Everything unmapped, unmap the master span with release flag to unmap the entire range of the super span
-		assert(!!(master->flags & SPAN_FLAG_MASTER) && !!(master->flags & SPAN_FLAG_SUBSPAN));
+		rpmalloc_assert(!!(master->flags & SPAN_FLAG_MASTER) && !!(master->flags & SPAN_FLAG_SUBSPAN), "Span flag corrupted");
 		size_t unmap_count = master->span_count;
 		if (_memory_span_size < _memory_page_size)
 			unmap_count = master->total_spans;
@@ -1179,8 +1191,8 @@
 //! Move the span (used for small or medium allocations) to the heap thread cache
 static void
 _rpmalloc_span_release_to_cache(heap_t* heap, span_t* span) {
-	assert(heap == span->heap);
-	assert(span->size_class < SIZE_CLASS_COUNT);
+	rpmalloc_assert(heap == span->heap, "Span heap pointer corrupted");
+	rpmalloc_assert(span->size_class < SIZE_CLASS_COUNT, "Invalid span size class");
 #if ENABLE_ADAPTIVE_THREAD_CACHE || ENABLE_STATISTICS
 	atomic_decr32(&heap->span_use[0].current);
 #endif
@@ -1200,7 +1212,7 @@
 //! as allocated, returning number of blocks in list
 static uint32_t
 free_list_partial_init(void** list, void** first_block, void* page_start, void* block_start, uint32_t block_count, uint32_t block_size) {
-	assert(block_count);
+	rpmalloc_assert(block_count, "Internal failure");
 	*first_block = block_start;
 	if (block_count > 1) {
 		void* free_block = pointer_offset(block_start, block_size);
@@ -1230,7 +1242,7 @@
 //! Initialize an unused span (from cache or mapped) to be new active span, putting the initial free list in heap class free list
 static void*
 _rpmalloc_span_initialize_new(heap_t* heap, span_t* span, uint32_t class_idx) {
-	assert(span->span_count == 1);
+	rpmalloc_assert(span->span_count == 1, "Internal failure");
 	size_class_t* size_class = _memory_size_class + class_idx;
 	span->size_class = class_idx;
 	span->heap = heap;
@@ -1273,7 +1285,7 @@
 
 static int
 _rpmalloc_span_is_fully_utilized(span_t* span) {
-	assert(span->free_list_limit <= span->block_count);
+	rpmalloc_assert(span->free_list_limit <= span->block_count, "Span free list corrupted");
 	return !span->free_list && (span->free_list_limit >= span->block_count);
 }
 
@@ -1304,7 +1316,7 @@
 		span->used_count -= free_count;
 	}
 	//If this assert triggers you have memory leaks
-	assert(span->list_size == span->used_count);
+	rpmalloc_assert(span->list_size == span->used_count, "Memory leak detected");
 	if (span->list_size == span->used_count) {
 		_rpmalloc_stat_dec(&heap->span_use[0].current);
 		_rpmalloc_stat_dec(&heap->size_class_use[iclass].spans_current);
@@ -1472,9 +1484,9 @@
 	span_t* span = (span_t*)((void*)atomic_exchange_ptr_acquire(&heap->span_free_deferred, 0));
 	while (span) {
 		span_t* next_span = (span_t*)span->free_list;
-		assert(span->heap == heap);
+		rpmalloc_assert(span->heap == heap, "Span heap pointer corrupted");
 		if (EXPECTED(span->size_class < SIZE_CLASS_COUNT)) {
-			assert(heap->full_span_count);
+			rpmalloc_assert(heap->full_span_count, "Heap span counter corrupted");
 			--heap->full_span_count;
 			_rpmalloc_stat_dec(&heap->span_use[0].spans_deferred);
 #if RPMALLOC_FIRST_CLASS_HEAPS
@@ -1490,8 +1502,8 @@
 			if (span->size_class == SIZE_CLASS_HUGE) {
 				_rpmalloc_deallocate_huge(span);
 			} else {
-				assert(span->size_class == SIZE_CLASS_LARGE);
-				assert(heap->full_span_count);
+				rpmalloc_assert(span->size_class == SIZE_CLASS_LARGE, "Span size class invalid");
+				rpmalloc_assert(heap->full_span_count, "Heap span counter corrupted");
 				--heap->full_span_count;
 #if RPMALLOC_FIRST_CLASS_HEAPS
 				_rpmalloc_span_double_link_list_remove(&heap->large_huge_span, span);
@@ -1934,7 +1946,7 @@
 
 #if ENABLE_STATISTICS
 	atomic_decr32(&_memory_active_heaps);
-	assert(atomic_load32(&_memory_active_heaps) >= 0);
+	rpmalloc_assert(atomic_load32(&_memory_active_heaps) >= 0, "Still active heaps during finalization");
 #endif
 
 	// If we are forcibly terminating with _exit the state of the
@@ -2005,7 +2017,7 @@
 		span_cache->count = 0;
 	}
 #endif
-	assert(!atomic_load_ptr(&heap->span_free_deferred));
+	rpmalloc_assert(!atomic_load_ptr(&heap->span_free_deferred), "Heaps still active during finalization");
 }
 
 
@@ -2028,8 +2040,8 @@
 _rpmalloc_allocate_from_heap_fallback(heap_t* heap, uint32_t class_idx) {
 	span_t* span = heap->size_class[class_idx].partial_span;
 	if (EXPECTED(span != 0)) {
-		assert(span->block_count == _memory_size_class[span->size_class].block_count);
-		assert(!_rpmalloc_span_is_fully_utilized(span));
+		rpmalloc_assert(span->block_count == _memory_size_class[span->size_class].block_count, "Span block count corrupted");
+		rpmalloc_assert(!_rpmalloc_span_is_fully_utilized(span), "Internal failure");
 		void* block;
 		if (span->free_list) {
 			//Swap in free list if not empty
@@ -2043,7 +2055,7 @@
 				(void*)((uintptr_t)block_start & ~(_memory_page_size - 1)), block_start,
 				span->block_count - span->free_list_limit, span->block_size);
 		}
-		assert(span->free_list_limit <= span->block_count);
+		rpmalloc_assert(span->free_list_limit <= span->block_count, "Span block count corrupted");
 		span->used_count = span->free_list_limit;
 
 		//Swap in deferred free list if present
@@ -2076,7 +2088,7 @@
 //! Allocate a small sized memory block from the given heap
 static void*
 _rpmalloc_allocate_small(heap_t* heap, size_t size) {
-	assert(heap);
+	rpmalloc_assert(heap, "No thread heap");
 	//Small sizes have unique size classes
 	const uint32_t class_idx = (uint32_t)((size + (SMALL_GRANULARITY - 1)) >> SMALL_GRANULARITY_SHIFT);
 	_rpmalloc_stat_inc_alloc(heap, class_idx);
@@ -2088,7 +2100,7 @@
 //! Allocate a medium sized memory block from the given heap
 static void*
 _rpmalloc_allocate_medium(heap_t* heap, size_t size) {
-	assert(heap);
+	rpmalloc_assert(heap, "No thread heap");
 	//Calculate the size class index and do a dependent lookup of the final class index (in case of merged classes)
 	const uint32_t base_idx = (uint32_t)(SMALL_CLASS_COUNT + ((size - (SMALL_SIZE_LIMIT + 1)) >> MEDIUM_GRANULARITY_SHIFT));
 	const uint32_t class_idx = _memory_size_class[base_idx].class_idx;
@@ -2101,7 +2113,7 @@
 //! Allocate a large sized memory block from the given heap
 static void*
 _rpmalloc_allocate_large(heap_t* heap, size_t size) {
-	assert(heap);
+	rpmalloc_assert(heap, "No thread heap");
 	//Calculate number of needed max sized spans (including header)
 	//Since this function is never called if size > LARGE_SIZE_LIMIT
 	//the span_count is guaranteed to be <= LARGE_CLASS_COUNT
@@ -2116,7 +2128,7 @@
 		return span;
 
 	//Mark span as owned by this heap and set base data
-	assert(span->span_count >= span_count);
+	rpmalloc_assert(span->span_count >= span_count, "Internal failure");
 	span->size_class = SIZE_CLASS_LARGE;
 	span->heap = heap;
 
@@ -2131,7 +2143,7 @@
 //! Allocate a huge block by mapping memory pages directly
 static void*
 _rpmalloc_allocate_huge(heap_t* heap, size_t size) {
-	assert(heap);
+	rpmalloc_assert(heap, "No thread heap");
 	_rpmalloc_heap_cache_adopt_deferred(heap, 0);
 	size += SPAN_HEADER_SIZE;
 	size_t num_pages = size >> _memory_page_size_shift;
@@ -2191,7 +2203,7 @@
 		// and size aligned to span header size multiples is less than size + alignment,
 		// then use natural alignment of blocks to provide alignment
 		size_t multiple_size = size ? (size + (SPAN_HEADER_SIZE - 1)) & ~(uintptr_t)(SPAN_HEADER_SIZE - 1) : SPAN_HEADER_SIZE;
-		assert(!(multiple_size % SPAN_HEADER_SIZE));
+		rpmalloc_assert(!(multiple_size % SPAN_HEADER_SIZE), "Failed alignment calculation");
 		if (multiple_size <= (size + alignment))
 			return _rpmalloc_allocate(heap, multiple_size);
 	}
@@ -2299,7 +2311,7 @@
 static void
 _rpmalloc_deallocate_direct_small_or_medium(span_t* span, void* block) {
 	heap_t* heap = span->heap;
-	assert(heap->owner_thread == get_thread_id() || !heap->owner_thread || heap->finalize);
+	rpmalloc_assert(heap->owner_thread == get_thread_id() || !heap->owner_thread || heap->finalize, "Internal failure");
 	//Add block to free list
 	if (UNEXPECTED(_rpmalloc_span_is_fully_utilized(span))) {
 		span->used_count = span->block_count;
@@ -2374,9 +2386,9 @@
 //! Deallocate the given large memory block to the current heap
 static void
 _rpmalloc_deallocate_large(span_t* span) {
-	assert(span->size_class == SIZE_CLASS_LARGE);
-	assert(!(span->flags & SPAN_FLAG_MASTER) || !(span->flags & SPAN_FLAG_SUBSPAN));
-	assert((span->flags & SPAN_FLAG_MASTER) || (span->flags & SPAN_FLAG_SUBSPAN));
+	rpmalloc_assert(span->size_class == SIZE_CLASS_LARGE, "Bad span size class");
+	rpmalloc_assert(!(span->flags & SPAN_FLAG_MASTER) || !(span->flags & SPAN_FLAG_SUBSPAN), "Span flag corrupted");
+	rpmalloc_assert((span->flags & SPAN_FLAG_MASTER) || (span->flags & SPAN_FLAG_SUBSPAN), "Span flag corrupted");
 	//We must always defer (unless finalizing) if from another heap since we cannot touch the list or counters of another heap
 #if RPMALLOC_FIRST_CLASS_HEAPS
 	int defer = (span->heap->owner_thread && (span->heap->owner_thread != get_thread_id()) && !span->heap->finalize);
@@ -2387,7 +2399,7 @@
 		_rpmalloc_deallocate_defer_free_span(span->heap, span);
 		return;
 	}
-	assert(span->heap->full_span_count);
+	rpmalloc_assert(span->heap->full_span_count, "Heap spanc counter corrupted");
 	--span->heap->full_span_count;
 #if RPMALLOC_FIRST_CLASS_HEAPS
 	_rpmalloc_span_double_link_list_remove(&span->heap->large_huge_span, span);
@@ -2398,7 +2410,7 @@
 	atomic_decr32(&span->heap->span_use[idx].current);
 #endif
 	heap_t* heap = span->heap;
-	assert(heap);
+	rpmalloc_assert(heap, "No thread heap");
 	if ((span->span_count > 1) && !heap->finalize && !heap->spans_reserved) {
 		heap->span_reserve = span;
 		heap->spans_reserved = span->span_count;
@@ -2407,8 +2419,8 @@
 		} else { //SPAN_FLAG_SUBSPAN
 			span_t* master = (span_t*)pointer_offset(span, -(intptr_t)((size_t)span->offset_from_master * _memory_span_size));
 			heap->span_reserve_master = master;
-			assert(master->flags & SPAN_FLAG_MASTER);
-			assert(atomic_load32(&master->remaining_spans) >= (int32_t)span->span_count);
+			rpmalloc_assert(master->flags & SPAN_FLAG_MASTER, "Span flag corrupted");
+			rpmalloc_assert(atomic_load32(&master->remaining_spans) >= (int32_t)span->span_count, "Master span count corrupted");
 		}
 		_rpmalloc_stat_inc(&heap->span_use[idx].spans_to_reserved);
 	} else {
@@ -2420,7 +2432,7 @@
 //! Deallocate the given huge span
 static void
 _rpmalloc_deallocate_huge(span_t* span) {
-	assert(span->heap);
+	rpmalloc_assert(span->heap, "No span heap");
 #if RPMALLOC_FIRST_CLASS_HEAPS
 	int defer = (span->heap->owner_thread && (span->heap->owner_thread != get_thread_id()) && !span->heap->finalize);
 #else
@@ -2430,7 +2442,7 @@
 		_rpmalloc_deallocate_defer_free_span(span->heap, span);
 		return;
 	}
-	assert(span->heap->full_span_count);
+	rpmalloc_assert(span->heap->full_span_count, "Heap span counter corrupted");
 	--span->heap->full_span_count;
 #if RPMALLOC_FIRST_CLASS_HEAPS
 	_rpmalloc_span_double_link_list_remove(&span->heap->large_huge_span, span);
@@ -2475,7 +2487,7 @@
 		span_t* span = (span_t*)((uintptr_t)p & _memory_span_mask);
 		if (EXPECTED(span->size_class < SIZE_CLASS_COUNT)) {
 			//Small/medium sized block
-			assert(span->span_count == 1);
+			rpmalloc_assert(span->span_count == 1, "Span counter corrupted");
 			void* blocks_start = pointer_offset(span, SPAN_HEADER_SIZE);
 			uint32_t block_offset = (uint32_t)pointer_diff(p, blocks_start);
 			uint32_t block_idx = block_offset / span->block_size;
@@ -2817,6 +2829,18 @@
 #if RPMALLOC_FIRST_CLASS_HEAPS
 	_memory_first_class_orphan_heaps = 0;
 #endif
+#if ENABLE_STATISTICS
+	_memory_active_heaps = 0;
+	_mapped_pages = 0;
+	_mapped_pages_peak = 0;
+	_master_spans = 0;
+	_reserved_spans = 0;
+	_mapped_total = 0;
+	_unmapped_total = 0;
+	_mapped_pages_os = 0;
+	_huge_pages_current = 0;
+	_huge_pages_peak = 0;
+#endif
 	memset(_memory_heaps, 0, sizeof(_memory_heaps));
 	atomic_store32_release(&_memory_global_lock, 0);
 
@@ -2865,9 +2889,9 @@
 #endif
 #if ENABLE_STATISTICS
 	//If you hit these asserts you probably have memory leaks (perhaps global scope data doing dynamic allocations) or double frees in your code
-	assert(atomic_load32(&_mapped_pages) == 0);
-	assert(atomic_load32(&_reserved_spans) == 0);
-	assert(atomic_load32(&_mapped_pages_os) == 0);
+	rpmalloc_assert(atomic_load32(&_mapped_pages) == 0, "Memory leak detected");
+	rpmalloc_assert(atomic_load32(&_reserved_spans) == 0, "Memory leak detected");
+	rpmalloc_assert(atomic_load32(&_mapped_pages_os) == 0, "Memory leak detected");
 #endif
 
 	_rpmalloc_initialized = 0;
@@ -3176,16 +3200,14 @@
 void
 rpmalloc_dump_statistics(void* file) {
 #if ENABLE_STATISTICS
-	//If you hit this assert, you still have active threads or forgot to finalize some thread(s)
-	//assert(atomic_load32(&_memory_active_heaps) == 0);
 	for (size_t list_idx = 0; list_idx < HEAP_ARRAY_SIZE; ++list_idx) {
 		heap_t* heap = _memory_heaps[list_idx];
 		while (heap) {
 			int need_dump = 0;
 			for (size_t iclass = 0; !need_dump && (iclass < SIZE_CLASS_COUNT); ++iclass) {
 				if (!atomic_load32(&heap->size_class_use[iclass].alloc_total)) {
-					assert(!atomic_load32(&heap->size_class_use[iclass].free_total));
-					assert(!atomic_load32(&heap->size_class_use[iclass].spans_map_calls));
+					rpmalloc_assert(!atomic_load32(&heap->size_class_use[iclass].free_total), "Heap statistics counter mismatch");
+					rpmalloc_assert(!atomic_load32(&heap->size_class_use[iclass].spans_map_calls), "Heap statistics counter mismatch");
 					continue;
 				}
 				need_dump = 1;
diff --git a/rpmalloc/rpmalloc.h b/rpmalloc/rpmalloc.h
index c74bc90..b1fa757 100644
--- a/rpmalloc/rpmalloc.h
+++ b/rpmalloc/rpmalloc.h
@@ -153,6 +153,9 @@
 	//  If you set a memory_unmap function, you must also set a memory_map function or
 	//  else the default implementation will be used for both.
 	void (*memory_unmap)(void* address, size_t size, size_t offset, size_t release);
+	//! Called when an assert fails, if asserts are enabled. Will use the standard assert()
+	//  if this is not set.
+	void (*error_callback)(const char* message);
 	//! Size of memory pages. The page size MUST be a power of two. All memory mapping
 	//  requests to memory_map will be made with size set to a multiple of the page size.
 	//  Used if RPMALLOC_CONFIGURABLE is defined to 1, otherwise system page size is used.
diff --git a/test/main.c b/test/main.c
index 012e80b..0a92ac7 100644
--- a/test/main.c
+++ b/test/main.c
@@ -1061,6 +1061,36 @@
 	return 0;
 }
 
+static int got_error;
+
+static void
+test_error_callback(const char* message) {
+	//printf("%s\n", message);
+	(void)sizeof(message);
+	got_error = 1;
+}
+
+static int
+test_error(void) {
+	//printf("Detecting memory leak\n");
+
+	rpmalloc_config_t config = {0};
+	config.error_callback = test_error_callback;
+	rpmalloc_initialize_config(&config);
+
+	rpmalloc(10);
+
+	rpmalloc_finalize();
+
+	if (!got_error) {
+		printf("Leak not detected and reported as expected\n");
+		return -1;
+	}
+
+	printf("Error detection test passed\n");
+	return 0;
+}
+
 int
 test_run(int argc, char** argv) {
 	(void)sizeof(argc);
@@ -1080,6 +1110,8 @@
 		return -1;
 	if (test_first_class_heaps())
 		return -1;
+	if (test_error())
+		return -1;
 	printf("All tests passed\n");
 	return 0;
 }