improve mempool destructor support (3/3) default tip

Thu, 22 May 2025 22:22:14 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 22 May 2025 22:22:14 +0200
changeset 1325
20caf6efaf07
parent 1324
399f7bb81d11

improve mempool destructor support (3/3)

documentation and final fixes

resolves #655

CHANGELOG file | annotate | diff | comparison | revisions
docs/Writerside/topics/about.md file | annotate | diff | comparison | revisions
docs/Writerside/topics/mempool.h.md file | annotate | diff | comparison | revisions
src/cx/mempool.h file | annotate | diff | comparison | revisions
src/mempool.c file | annotate | diff | comparison | revisions
tests/test_mempool.c file | annotate | diff | comparison | revisions
--- 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);

mercurial