Thu, 22 May 2025 22:22:14 +0200
improve mempool destructor support (3/3)
documentation and final fixes
resolves #655
--- a/CHANGELOG Thu May 22 21:00:33 2025 +0200 +++ b/CHANGELOG Thu May 22 22:22:14 2025 +0200 @@ -2,6 +2,7 @@ ------------------------ * adds cxMempoolTransfer() and cxMempoolTransferObject() + * adds support for different destruction strategies in CxMempool * adds cxListSet() * adds cxListContains() * adds cxListFirst() and cxListLast()
--- a/docs/Writerside/topics/about.md Thu May 22 21:00:33 2025 +0200 +++ b/docs/Writerside/topics/about.md Thu May 22 22:22:14 2025 +0200 @@ -29,6 +29,7 @@ ### Version 3.2 - preview {collapsible="true"} * adds cxMempoolTransfer() and cxMempoolTransferObject() +* adds support for different destruction strategies in CxMempool * adds cxListSet() * adds cxListContains() * adds cxListFirst() and cxListLast()
--- a/docs/Writerside/topics/mempool.h.md Thu May 22 21:00:33 2025 +0200 +++ b/docs/Writerside/topics/mempool.h.md Thu May 22 22:22:14 2025 +0200 @@ -15,46 +15,93 @@ ```C #include <cx/mempool.h> -CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func fnc); +enum cx_mempool_type { + CX_MEMPOOL_TYPE_SIMPLE, + CX_MEMPOOL_TYPE_ADVANCED, + CX_MEMPOOL_TYPE_PURE, +}; + +CxMempool *cxMempoolCreate(size_t capacity, + enum cx_mempool_type type); CxMempool *cxMempoolCreateSimple(size_t capacity); +CxMempool *cxMempoolCreateAdvanced(size_t capacity); +CxMempool *cxMempoolCreatePure(size_t capacity); void cxMempoolFree(CxMempool *pool); -void cxMempoolSetDestructor(void *memory, cx_destructor_func fnc); +void cxMempoolGlobalDestructor(CxMempool *pool, + cx_destructor_func fnc); + +void cxMempoolGlobalDestructor2(CxMempool *pool, + cx_destructor_func2 fnc, void *data); + +void cxMempoolSetDestructor(void *memory, + cx_destructor_func fnc); + +void cxMempoolSetDestructor2(void *memory, + cx_destructor_func fnc, void *data); void cxMempoolRemoveDestructor(void *memory); +void cxMempoolRemoveDestructor2(void *memory); + int cxMempoolRegister(CxMempool *pool, void *memory, - cx_destructor_func fnc); + cx_destructor_func fnc); + +int cxMempoolRegister2(CxMempool *pool, void *memory, + cx_destructor_func fnc, void *data); ``` -A memory pool is created with the `cxMempoolCreate()` function with a default `capacity` -and an optional default destructor function `fnc`. -If specified, the default destructor function is registered for all freshly allocated memory within the pool, -as if `cxMempoolSetDestructor()` was called immediately after allocation. -When you set `fnc` is to `NULL` during pool creation, or use `cxMempoolCreateSimple`, no default destructor is registered. +A memory pool is created with the `cxMempoolCreate()` family of functions with a default `capacity`. +If `capacity` is set to zero, an implementation default is used. -After creating a memory pool `CxMempool *mpool`, you can access the provided allocator via `mpool->allocator`. +The `type` specifies how much additional data is allocated for each pooled memory block. +A simple pool reserves memory for an optional `cx_destructor_func`. +An advanced pool reserves memory for an optional `cx_destructor_func2` +and an additional `data` pointer that will be passed to that destructor. +A pure pool does not reserve any additional data and therefore does not support registering +custom destructors with the allocated memory. + +> After creating a memory pool `CxMempool *mpool`, you can access the provided allocator via `mpool->allocator`. +>{style="note"} + +The functions `cxMempoolGlobalDestructor()` and `cxMempoolGlobalDestructor2()` can be used to specify destructor functions +that shall be invoked for _all_ objects allocated by the pool when they are freed (see [](#order-of-destruction)). +This is usually only useful for pools that will only contain objects of the same type. -The functions `cxMempoolSetDestructor()` and `cxMempoolRemoveDestructor()` can be used to assign a specific destructor -function to an allocated object or remove any assigned destructor function, respectively. +In _simple_ memory pools, the two functions `cxMempoolSetDestructor()` and `cxMempoolRemoveDestructor()` can be used to assign a specific destructor +function to an allocated object or remove an assigned destructor function, respectively. The `memory` pointer points to the allocated object, which must have been allocated by any `CxMempool`'s provided allocator. +For _advanced_ pools, the functions `cxMempoolSetDestructor2()` and `cxMempoolRemoveDestructor2()` do the same. +It is disallowed to use the functions with a pool of the wrong type and will most likely cause undefined behavior. +Pure pools do not allow setting destructors for individual memory blocks at all. -The `cxMempoolRegister()` function allocates a new wrapper object for `memory` with `pool`'s allocator that -will call the specified destructor function when destroyed. -Usually this function returns zero except for platforms where memory allocations are likely to fail, +The `cxMempoolRegister()` function allocates a new wrapper object for `memory` +and makes the specified destructor function being called when the pool gets destroyed. +Alternatively, the `cxMempoolRegister2()` function can be used to register an advanced destructor and a pointer to custom data. +Be aware that the memory pointed to by the additional data pointer must remain valid until the pool gets destroyed! +Usually these functions return zero except for platforms where memory allocations are likely to fail, in which case a non-zero value is returned. +> When you register foreign memory with a pool, you can decide which destructor type you want to use, +> regardless of the pool's type. +> That means, for example, you can use `cxMempoolReigster2()` for simple pools, `cxMempoolRegister()` for pure pools, etc. +> +> When you use `cxMempoolReigster2()` the `data` pointer must not be `NULL` or the behavior will be undefined when the pool gets destroyed. + ### Order of Destruction When you call `cxMempoolFree()` the following actions are performed: -1. In any order, for each object in the pool +1. In any order, for each object allocated by the pool 1. the destructor function assigned to that object is called - 2. the object's memory is deallocated -2. The pool memory is deallocated -3. The pool structure is deallocated + 2. the pool's global simple destructor is called + 3. the pool's global advanced destructor is called + 4. the object's memory is deallocated +2. In any order, for each registered foreign object the destructor is called +3. The pool memory is deallocated +4. The pool structure is deallocated ## Transfer Memory @@ -75,11 +122,11 @@ The transferred allocator will be destroyed when the `dest` pool gets destroyed. The function `cxMempoolTransferObject()` transfers a _single_ object managed by the `source` pool to the `dest` pool. -Memory that was registered with `cxMempoolRegister()` cannot be transferred this way. -Also, if `obj` has a reference to `source->allocator`, it must be updated to `dest->allocator` manually. +In contrast to transferring an entire pool, if `obj` has a reference to `source->allocator`, it must be updated to `dest->allocator` manually. +It is also possible to transfer registered memory from one pool to another, this way. The functions returns zero when the transfer was successful and non-zero if a necessary memory allocation was not possible, -or the `source` and `dest` pointers point to the same pool. +the `source` and `dest` pointers point to the same pool, or the pools have different type (simple, advanced, pure). In case of an error, no memory is transferred and both pools are in a valid state.
--- a/src/cx/mempool.h Thu May 22 21:00:33 2025 +0200 +++ b/src/cx/mempool.h Thu May 22 22:22:14 2025 +0200 @@ -172,21 +172,23 @@ /** * Sets the global destructor for all memory blocks within the specified pool. * - * @param pool (@c CxMempool*) the memory pool - * @param fnc (@c cx_destructor_func) the destructor that shall be applied to all memory blocks + * @param pool the memory pool + * @param fnc the destructor that shall be applied to all memory blocks */ -#define cxMempoolGlobalDestructor(pool, fnc) \ - (pool)->destr = (cx_destructor_func)(fnc) +cx_attr_nonnull_arg(1) +cx_attr_export +void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); /** * Sets the global destructor for all memory blocks within the specified pool. * - * @param pool (@c CxMempool*) the memory pool - * @param fnc (@c cx_destructor_func2) the destructor that shall be applied to all memory blocks - * @param data (@c void*) additional data for the destructor function + * @param pool the memory pool + * @param fnc the destructor that shall be applied to all memory blocks + * @param data additional data for the destructor function */ -#define cxMempoolGlobalDestructor2(pool, fnc, data) \ - (pool)->destr2 = (cx_destructor_func2)(fnc); (pool)->destr2_data = (data) +cx_attr_nonnull_arg(1) +cx_attr_export +void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); /** * Creates a basic array-based memory pool.
--- a/src/mempool.c Thu May 22 21:00:33 2025 +0200 +++ b/src/mempool.c Thu May 22 22:22:14 2025 +0200 @@ -564,6 +564,15 @@ return pool; } +void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc) { + pool->destr = fnc; +} + +void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data) { + pool->destr2 = fnc; + pool->destr2_data = data; +} + static void cx_mempool_free_transferred_allocator(void *al) { cxFreeDefault(al); } @@ -628,14 +637,14 @@ // 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) { + // 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 + } // remove from the source pool size_t last_index = source->size - 1; if (i != last_index) { @@ -648,6 +657,27 @@ return 0; } } + // search in the registered objects + for (size_t i = 0; i < source->registered_size; i++) { + struct cx_mempool_foreign_memory_s *mem = &source->registered[i]; + if (mem->mem == obj) { + // first, make sure that the dest pool can take the object + if (cx_mempool_ensure_registered_capacity(dest, + dest->registered_size + 1)) { + return 1; // LCOV_EXCL_LINE + } + dest->registered[dest->registered_size++] = *mem; + // remove from the source pool + size_t last_index = source->registered_size - 1; + if (i != last_index) { + source->registered[i] = source->registered[last_index]; + memset(&source->registered[last_index], 0, + sizeof(struct cx_mempool_foreign_memory_s)); + } + source->registered_size--; + return 0; + } + } // not found return 1; }
--- a/tests/test_mempool.c Thu May 22 21:00:33 2025 +0200 +++ b/tests/test_mempool.c Thu May 22 22:22:14 2025 +0200 @@ -238,11 +238,13 @@ CX_TEST_ASSERT(src->size == 1); CX_TEST_ASSERT(dest->size == 1); - // cannot transfer foreign memory this way + // can also transfer foreign memory this way + CX_TEST_ASSERT(src->registered_size == 1); + CX_TEST_ASSERT(dest->registered_size == 0); result = cxMempoolTransferObject(src, dest, c); - CX_TEST_ASSERT(result != 0); - CX_TEST_ASSERT(src->size == 1); - CX_TEST_ASSERT(dest->size == 1); + CX_TEST_ASSERT(result == 0); + CX_TEST_ASSERT(src->registered_size == 0); + CX_TEST_ASSERT(dest->registered_size == 1); result = cxMempoolTransferObject(dest, dest, b); CX_TEST_ASSERT(result != 0);