Thu, 22 May 2025 21:00:33 +0200
improve mempool destructor support (2/3)
implement new behavior
issue #655
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "cx/mempool.h" #include <string.h> #include <errno.h> static int cx_mempool_ensure_capacity( struct cx_mempool_s *pool, size_t needed_capacity ) { if (needed_capacity <= pool->capacity) return 0; size_t newcap = pool->capacity >= 1000 ? pool->capacity + 1000 : pool->capacity * 2; size_t newmsize; if (pool->capacity > newcap || cx_szmul(newcap, sizeof(void*), &newmsize)) { errno = EOVERFLOW; return 1; } void **newdata = cxReallocDefault(pool->data, newmsize); if (newdata == NULL) return 1; pool->data = newdata; pool->capacity = newcap; return 0; } static int cx_mempool_ensure_registered_capacity( struct cx_mempool_s *pool, size_t needed_capacity ) { if (needed_capacity <= pool->registered_capacity) return 0; // we do not expect so many registrations size_t newcap = pool->registered_capacity + 8; size_t newmsize; if (pool->registered_capacity > newcap || cx_szmul(newcap, sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) { errno = EOVERFLOW; return 1; } void *newdata = cxReallocDefault(pool->registered, newmsize); if (newdata == NULL) return 1; pool->registered = newdata; pool->registered_capacity = newcap; return 0; } static void *cx_mempool_malloc_simple( void *p, size_t n ) { struct cx_mempool_s *pool = p; if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { return NULL; } struct cx_mempool_memory_s *mem = cxMallocDefault(sizeof(struct cx_mempool_memory_s) + n); if (mem == NULL) return NULL; mem->destructor = NULL; pool->data[pool->size] = mem; pool->size++; return mem->c; } static void *cx_mempool_calloc_simple( void *p, size_t nelem, size_t elsize ) { size_t msz; if (cx_szmul(nelem, elsize, &msz)) { errno = EOVERFLOW; return NULL; } void *ptr = cx_mempool_malloc_simple(p, msz); if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } static void *cx_mempool_realloc_simple( void *p, void *ptr, size_t n ) { struct cx_mempool_s *pool = p; const unsigned overhead = sizeof(struct cx_mempool_memory_s); struct cx_mempool_memory_s *mem = (void *) (((char *) ptr) - overhead); struct cx_mempool_memory_s *newm = cxReallocDefault(mem, n + overhead); if (newm == NULL) return NULL; if (mem != newm) { for (size_t i = 0; i < pool->size; i++) { if (pool->data[i] == mem) { pool->data[i] = newm; return ((char*)newm) + overhead; } } abort(); // LCOV_EXCL_LINE } else { return ptr; } } static void cx_mempool_free_simple( void *p, void *ptr ) { if (!ptr) return; struct cx_mempool_s *pool = p; struct cx_mempool_memory_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); for (size_t i = 0; i < pool->size; i++) { if (mem == pool->data[i]) { if (mem->destructor) { mem->destructor(mem->c); } if (pool->destr) { pool->destr(mem->c); } if (pool->destr2) { pool->destr2(pool->destr2_data, mem->c); } cxFreeDefault(mem); size_t last_index = pool->size - 1; if (i != last_index) { pool->data[i] = pool->data[last_index]; pool->data[last_index] = NULL; } pool->size--; return; } } abort(); // LCOV_EXCL_LINE } static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { const bool has_destr = pool->destr; const bool has_destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); } if (has_destr) { pool->destr(mem->c); } if (has_destr2) { pool->destr2(pool->destr2_data, mem->c); } cxFreeDefault(mem); } } static cx_allocator_class cx_mempool_simple_allocator_class = { cx_mempool_malloc_simple, cx_mempool_realloc_simple, cx_mempool_calloc_simple, cx_mempool_free_simple }; static void *cx_mempool_malloc_advanced( void *p, size_t n ) { struct cx_mempool_s *pool = p; if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { return NULL; } struct cx_mempool_memory2_s *mem = cxMallocDefault(sizeof(struct cx_mempool_memory_s) + n); if (mem == NULL) return NULL; mem->destructor = NULL; mem->data = NULL; pool->data[pool->size] = mem; pool->size++; return mem->c; } static void *cx_mempool_calloc_advanced( void *p, size_t nelem, size_t elsize ) { size_t msz; if (cx_szmul(nelem, elsize, &msz)) { errno = EOVERFLOW; return NULL; } void *ptr = cx_mempool_malloc_advanced(p, msz); if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } static void *cx_mempool_realloc_advanced( void *p, void *ptr, size_t n ) { struct cx_mempool_s *pool = p; const unsigned overhead = sizeof(struct cx_mempool_memory2_s); struct cx_mempool_memory2_s *mem = (void *) (((char *) ptr) - overhead); struct cx_mempool_memory2_s *newm = cxReallocDefault(mem, n + overhead); if (newm == NULL) return NULL; if (mem != newm) { for (size_t i = 0; i < pool->size; i++) { if (pool->data[i] == mem) { pool->data[i] = newm; return ((char*)newm) + overhead; } } abort(); // LCOV_EXCL_LINE } else { return ptr; } } static void cx_mempool_free_advanced( void *p, void *ptr ) { if (!ptr) return; struct cx_mempool_s *pool = p; struct cx_mempool_memory2_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s)); for (size_t i = 0; i < pool->size; i++) { if (mem == pool->data[i]) { if (mem->destructor) { mem->destructor(mem->data, mem->c); } if (pool->destr) { pool->destr(mem->c); } if (pool->destr2) { pool->destr2(pool->destr2_data, mem->c); } cxFreeDefault(mem); size_t last_index = pool->size - 1; if (i != last_index) { pool->data[i] = pool->data[last_index]; pool->data[last_index] = NULL; } pool->size--; return; } } abort(); // LCOV_EXCL_LINE } static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) { const bool has_destr = pool->destr; const bool has_destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory2_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->data, mem->c); } if (has_destr) { pool->destr(mem->c); } if (has_destr2) { pool->destr2(pool->destr2_data, mem->c); } cxFreeDefault(mem); } } static cx_allocator_class cx_mempool_advanced_allocator_class = { cx_mempool_malloc_advanced, cx_mempool_realloc_advanced, cx_mempool_calloc_advanced, cx_mempool_free_advanced }; static void *cx_mempool_malloc_pure( void *p, size_t n ) { struct cx_mempool_s *pool = p; if (cx_mempool_ensure_capacity(pool, pool->size + 1)) { return NULL; } void *mem = cxMallocDefault(n); if (mem == NULL) return NULL; pool->data[pool->size] = mem; pool->size++; return mem; } static void *cx_mempool_calloc_pure( void *p, size_t nelem, size_t elsize ) { size_t msz; if (cx_szmul(nelem, elsize, &msz)) { errno = EOVERFLOW; return NULL; } void *ptr = cx_mempool_malloc_pure(p, msz); if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } static void *cx_mempool_realloc_pure( void *p, void *ptr, size_t n ) { struct cx_mempool_s *pool = p; void *newm = cxReallocDefault(ptr, n); if (newm == NULL) return NULL; if (ptr != newm) { for (size_t i = 0; i < pool->size; i++) { if (pool->data[i] == ptr) { pool->data[i] = newm; return newm; } } abort(); // LCOV_EXCL_LINE } else { return ptr; } } static void cx_mempool_free_pure( void *p, void *ptr ) { if (!ptr) return; struct cx_mempool_s *pool = p; for (size_t i = 0; i < pool->size; i++) { if (ptr == pool->data[i]) { if (pool->destr) { pool->destr(ptr); } if (pool->destr2) { pool->destr2(pool->destr2_data, ptr); } cxFreeDefault(ptr); size_t last_index = pool->size - 1; if (i != last_index) { pool->data[i] = pool->data[last_index]; pool->data[last_index] = NULL; } pool->size--; return; } } abort(); // LCOV_EXCL_LINE } static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) { const bool has_destr = pool->destr; const bool has_destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { void *mem = pool->data[i]; if (has_destr) { pool->destr(mem); } if (has_destr2) { pool->destr2(pool->destr2_data, mem); } cxFreeDefault(mem); } } static cx_allocator_class cx_mempool_pure_allocator_class = { cx_mempool_malloc_pure, cx_mempool_realloc_pure, cx_mempool_calloc_pure, cx_mempool_free_pure }; static void cx_mempool_free_foreign(const struct cx_mempool_s *pool) { for (size_t i = 0; i < pool->registered_size; i++) { struct cx_mempool_foreign_memory_s info = pool->registered[i]; if (info.destr2_data == NULL) { if (info.destr) { info.destr(info.mem); } } else { info.destr2(info.destr2_data, info.mem); } } } void cxMempoolFree(CxMempool *pool) { if (pool == NULL) return; if (pool->allocator->cl == &cx_mempool_simple_allocator_class) { cx_mempool_free_all_simple(pool); } else if (pool->allocator->cl == &cx_mempool_advanced_allocator_class) { cx_mempool_free_all_advanced(pool); } else { cx_mempool_free_all_pure(pool); } cx_mempool_free_foreign(pool); cxFreeDefault(pool->data); cxFreeDefault(pool->registered); cxFreeDefault((void*) pool->allocator); cxFreeDefault(pool); } void cxMempoolSetDestructor( void *ptr, cx_destructor_func func ) { *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; } void cxMempoolSetDestructor2( void *ptr, cx_destructor_func2 func, void *data ) { struct cx_mempool_memory2_s *info = (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s)); info->destructor = func; info->data = data; } void cxMempoolRemoveDestructor(void *ptr) { *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; } void cxMempoolRemoveDestructor2(void *ptr) { struct cx_mempool_memory2_s *info = (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s)); info->destructor = NULL; info->data = NULL; } int cxMempoolRegister( CxMempool *pool, void *memory, cx_destructor_func destr ) { if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) { return 1; // LCOV_EXCL_LINE } pool->registered[pool->registered_size++] = (struct cx_mempool_foreign_memory_s) { .mem = memory, .destr = destr, .destr2_data = NULL }; return 0; } int cxMempoolRegister2( CxMempool *pool, void *memory, cx_destructor_func2 destr, void *data ) { if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) { return 1; // LCOV_EXCL_LINE } pool->registered[pool->registered_size++] = (struct cx_mempool_foreign_memory_s) { .mem = memory, .destr2 = destr, .destr2_data = data }; return 0; } CxMempool *cxMempoolCreate( size_t capacity, enum cx_mempool_type type ) { if (capacity == 0) capacity = 16; size_t poolsize; if (cx_szmul(capacity, sizeof(void*), &poolsize)) { errno = EOVERFLOW; return NULL; } CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator)); if (provided_allocator == NULL) { // LCOV_EXCL_START return NULL; } // LCOV_EXCL_STOP CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool)); if (pool == NULL) { cxFreeDefault(provided_allocator); return NULL; } provided_allocator->data = pool; pool->allocator = provided_allocator; if (type == CX_MEMPOOL_TYPE_SIMPLE) { provided_allocator->cl = &cx_mempool_simple_allocator_class; } else if (type == CX_MEMPOOL_TYPE_ADVANCED) { provided_allocator->cl = &cx_mempool_advanced_allocator_class; } else { provided_allocator->cl = &cx_mempool_pure_allocator_class; } pool->data = cxMallocDefault(poolsize); if (pool->data == NULL) { // LCOV_EXCL_START cxFreeDefault(provided_allocator); cxFreeDefault(pool); return NULL; } // LCOV_EXCL_STOP pool->size = 0; pool->capacity = capacity; return pool; } static void cx_mempool_free_transferred_allocator(void *al) { cxFreeDefault(al); } int cxMempoolTransfer( CxMempool *source, CxMempool *dest ) { // safety checks if (source == dest) return 1; if (source->allocator->cl != dest->allocator->cl) return 1; // ensure enough capacity in the destination pool if (cx_mempool_ensure_capacity(dest, dest->size + source->size)) { return 1; // LCOV_EXCL_LINE } if (cx_mempool_ensure_registered_capacity(dest, dest->registered_size + source->registered_size)) { return 1; // LCOV_EXCL_LINE } // allocate a replacement allocator for the source pool CxAllocator *new_source_allocator = cxMallocDefault(sizeof(CxAllocator)); if (new_source_allocator == NULL) { // LCOV_EXCL_START return 1; } // LCOV_EXCL_STOP new_source_allocator->cl = source->allocator->cl; new_source_allocator->data = source; // transfer all the data memcpy(&dest->data[dest->size], source->data, sizeof(void*)*source->size); dest->size += source->size; // transfer all registered memory memcpy(&dest->registered[dest->registered_size], source->registered, sizeof(struct cx_mempool_foreign_memory_s) * source->size); dest->registered_size += source->registered_size; // register the old allocator with the new pool // we have to remove const-ness for this, but that's okay here CxAllocator *transferred_allocator = (CxAllocator*) source->allocator; transferred_allocator->data = dest; cxMempoolRegister(dest, transferred_allocator, cx_mempool_free_transferred_allocator); // prepare the source pool for re-use source->allocator = new_source_allocator; memset(source->data, 0, source->size * sizeof(void*)); memset(source->registered, 0, source->registered_size * sizeof(struct cx_mempool_foreign_memory_s)); source->size = 0; source->registered_size = 0; return 0; } int cxMempoolTransferObject( CxMempool *source, CxMempool *dest, const void *obj ) { // safety check if (source == dest) return 1; // first, make sure that the dest pool can take the object if (cx_mempool_ensure_capacity(dest, dest->size + 1)) { return 1; // LCOV_EXCL_LINE } // search for the object for (size_t i = 0; i < source->size; i++) { struct cx_mempool_memory_s *mem = source->data[i]; if (mem->c == obj) { // remove from the source pool size_t last_index = source->size - 1; if (i != last_index) { source->data[i] = source->data[last_index]; source->data[last_index] = NULL; } source->size--; // add to the target pool dest->data[dest->size++] = mem; return 0; } } // not found return 1; }