improve mempool destructor support (1/3)

Thu, 22 May 2025 16:25:32 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 22 May 2025 16:25:32 +0200
changeset 1323
deccdb82f24e
parent 1322
7be10b57f658
child 1324
399f7bb81d11

improve mempool destructor support (1/3)

refactoring while keeping old behavior

issue #655

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/src/cx/mempool.h	Thu May 22 16:23:55 2025 +0200
+++ b/src/cx/mempool.h	Thu May 22 16:25:32 2025 +0200
@@ -43,8 +43,52 @@
 extern "C" {
 #endif
 
-/** Internal structure for pooled memory. */
-struct cx_mempool_memory_s;
+struct cx_mempool_memory_s {
+    /** The destructor. */
+    cx_destructor_func destructor;
+    /** The actual memory. */
+    char c[];
+};
+
+struct cx_mempool_memory2_s {
+    /** The destructor. */
+    cx_destructor_func2 destructor;
+    /** Data for the destructor. */
+    void *data;
+    /** The actual memory. */
+    char c[];
+};
+
+struct cx_mempool_foreign_memory_s {
+    /** The foreign memory. */
+    void* mem;
+    union {
+        /** Simple destructor. */
+        cx_destructor_func destr;
+        /** Advanced destructor. */
+        cx_destructor_func2 destr2;
+    };
+    /** Data for the advanced destructor. */
+    void *destr2_data;
+};
+
+/** Specifies how individual blocks are allocated. */
+enum cx_mempool_type {
+    /**
+     * Allows registration of cx_destructor_func for each memory block.
+     */
+    CX_MEMPOOL_TYPE_SIMPLE,
+    /**
+     * Allows registration of cx_destructor_func2 for each memory block.
+     */
+    CX_MEMPOOL_TYPE_ADVANCED,
+    /**
+     * No individual destructor registration allowed.
+     *
+     * In this mode, no additional memory per block is allocated.
+     */
+    CX_MEMPOOL_TYPE_PURE,
+};
 
 /**
  * The basic structure of a memory pool.
@@ -54,20 +98,46 @@
     /** The provided allocator. */
     const CxAllocator *allocator;
 
-    /**
-     * A destructor that shall be automatically registered for newly allocated memory.
-     * This destructor MUST NOT free the memory.
-     */
-    cx_destructor_func auto_destr;
-
     /** Array of pooled memory. */
-    struct cx_mempool_memory_s **data;
+    void **data;
 
     /** Number of pooled memory items. */
     size_t size;
 
     /** Memory pool capacity. */
     size_t capacity;
+
+    /** Array of registered memory. */
+    struct cx_mempool_foreign_memory_s *registered;
+
+    /** Number of registered memory items. */
+    size_t registered_size;
+
+    /** Capacity for registered memory. */
+    size_t registered_capacity;
+
+    /**
+     * A destructor that shall be called before deallocating a memory block.
+     * This destructor MUST NOT free the memory itself.
+     *
+     * It is guaranteed that this destructor is called after the individual
+     * destructor of the memory block and before @c destr2.
+     */
+    cx_destructor_func destr;
+
+    /**
+     * A destructor that shall be called before deallocating a memory block.
+     * This destructor MUST NOT free the memory itself.
+     *
+    * It is guaranteed that this destructor is called after the individual
+     * destructor of the memory block and @c destr.
+     */
+    cx_destructor_func2 destr2;
+
+    /**
+     * Additional data for the @c destr2.
+     */
+    void *destr2_data;
 };
 
 /**
@@ -84,19 +154,39 @@
 void cxMempoolFree(CxMempool *pool);
 
 /**
- * Creates an array-based memory pool with a shared destructor function.
+ * Creates an array-based memory pool.
  *
- * This destructor MUST NOT free the memory.
+ * The type determines how much additional memory is allocated per block
+ * to register a destructor function.
  *
- * @param capacity the initial capacity of the pool
- * @param destr optional destructor function to use for allocated memory
+ * @param capacity the initial capacity of the pool (an implementation default if zero)
+ * @param type the type of memory pool
  * @return the created memory pool or @c NULL if allocation failed
  */
 cx_attr_nodiscard
 cx_attr_malloc
 cx_attr_dealloc(cxMempoolFree, 1)
 cx_attr_export
-CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr);
+CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type);
+
+/**
+ * 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
+ */
+#define cxMempoolGlobalDestructor(pool, fnc) \
+    (pool)->destr = (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
+ */
+#define cxMempoolGlobalDestructor2(pool, fnc, data) \
+    (pool)->destr2 = (cx_destructor_func2)(fnc); (pool)->destr2_data = (data)
 
 /**
  * Creates a basic array-based memory pool.
@@ -104,11 +194,12 @@
  * @param capacity (@c size_t) the initial capacity of the pool
  * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed
  */
-#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, NULL)
+#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_SIMPLE)
 
 /**
  * Sets the destructor function for a specific allocated memory object.
  *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined.
  * If the memory is not managed by a UCX memory pool, the behavior is undefined.
  * The destructor MUST NOT free the memory.
  *
@@ -123,10 +214,29 @@
 );
 
 /**
+ * Sets the destructor function for a specific allocated memory object.
+ *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined.
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ * The destructor MUST NOT free the memory.
+ *
+ * @param memory the object allocated in the pool
+ * @param fnc the destructor function
+ * @param data additional data for the destructor function
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxMempoolSetDestructor2(
+        void *memory,
+        cx_destructor_func2 fnc,
+        void *data
+);
+
+/**
  * Removes the destructor function for a specific allocated memory object.
  *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined.
  * If the memory is not managed by a UCX memory pool, the behavior is undefined.
- * The destructor MUST NOT free the memory.
  *
  * @param memory the object allocated in the pool
  */
@@ -135,12 +245,25 @@
 void cxMempoolRemoveDestructor(void *memory);
 
 /**
+ * Removes the destructor function for a specific allocated memory object.
+ *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined.
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ *
+ * @param memory the object allocated in the pool
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxMempoolRemoveDestructor2(void *memory);
+
+/**
  * Registers foreign memory with this pool.
  *
  * The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ * This function can be used with any pool of any type, since destructors for registered memory
+ * are entirely independent of the pool's memory management.
  *
- * A small portion of memory will be allocated to register the information in the pool.
- * If that allocation fails, this function will return non-zero.
+ * The destructor for the registered memory will be called after all pooled items have been freed.
  *
  * @param pool the pool
  * @param memory the object to register (MUST NOT be already allocated in the pool)
@@ -156,6 +279,35 @@
         cx_destructor_func destr
 );
 
+
+/**
+ * Registers foreign memory with this pool.
+ *
+ * The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ * This function can be used with any pool of any type, since destructors for registered memory
+ * are entirely independent of the pool's memory management.
+ *
+ * The destructor for the registered memory will be called after all pooled items have been freed.
+ *
+ * @attention The data pointer MUST NOT be @c NULL.
+ * If you wish to register a destructor without additional data, use cxMempoolRegister().
+ *
+ * @param pool the pool
+ * @param memory the object to register (MUST NOT be already allocated in the pool)
+ * @param destr the destructor function
+ * @param data additional data for the destructor function
+ * @retval zero success
+ * @retval non-zero failure
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxMempoolRegister2(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func2 destr,
+        void *data
+);
+
 /**
  * Transfers all the memory managed by one pool to another.
  *
@@ -164,10 +316,12 @@
  *
  * The source pool will get a completely new allocator and can be reused or destroyed afterward.
  *
+ * This function fails when the destination pool has a different type than the source pool.
+ *
  * @param source the pool to move the memory from
  * @param dest the pool where to transfer the memory to
  * @retval zero success
- * @retval non-zero failure
+ * @retval non-zero allocation failure or incompatible pools
  */
 cx_attr_nonnull
 cx_attr_export
@@ -179,8 +333,7 @@
 /**
  * Transfers an object from one pool to another.
  *
- * You can only transfer objects that have been allocated by this pool.
- * Objects that have been registered with cxMempoolRegister() cannot be transferred this way.
+ * This function fails when the destination pool has a different type than the source pool.
  *
  * @attention If the object maintains a reference to the pool's allocator,
  * you must make sure to update that reference to the allocator of the destination pool.
@@ -189,7 +342,7 @@
  * @param dest the pool where to transfer the memory to
  * @param obj pointer to the object that shall be transferred
  * @retval zero success
- * @retval non-zero failure or object was not found in the source pool
+ * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible
  */
 cx_attr_nonnull
 cx_attr_export
--- a/src/mempool.c	Thu May 22 16:23:55 2025 +0200
+++ b/src/mempool.c	Thu May 22 16:25:32 2025 +0200
@@ -31,13 +31,6 @@
 #include <string.h>
 #include <errno.h>
 
-struct cx_mempool_memory_s {
-    /** The destructor. */
-    cx_destructor_func destructor;
-    /** The actual memory. */
-    char c[];
-};
-
 static int cx_mempool_ensure_capacity(
         struct cx_mempool_s *pool,
         size_t needed_capacity
@@ -46,20 +39,39 @@
     size_t newcap = pool->capacity >= 1000 ?
         pool->capacity + 1000 : pool->capacity * 2;
     size_t newmsize;
-    if (pool->capacity > newcap || cx_szmul(newcap,
-            sizeof(struct cx_mempool_memory_s*), &newmsize)) {
+    if (pool->capacity > newcap
+            || cx_szmul(newcap, sizeof(void*), &newmsize)) {
         errno = EOVERFLOW;
         return 1;
     }
-    struct cx_mempool_memory_s **newdata = cxRealloc(
-        cxDefaultAllocator, pool->data, newmsize);
+    void **newdata = cxReallocDefault(pool->data, newmsize);
     if (newdata == NULL) return 1;
     pool->data = newdata;
     pool->capacity = newcap;
     return 0;
 }
 
-static void *cx_mempool_malloc(
+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
 ) {
@@ -69,18 +81,17 @@
         return NULL;
     }
 
-    struct cx_mempool_memory_s *mem = cxMalloc(
-        cxDefaultAllocator, sizeof(cx_destructor_func) + n);
+    struct cx_mempool_memory_s *mem =
+        cxMallocDefault(sizeof(struct cx_mempool_memory_s) + n);
     if (mem == NULL) return NULL;
-
-    mem->destructor = pool->auto_destr;
+    mem->destructor = NULL;
     pool->data[pool->size] = mem;
     pool->size++;
 
     return mem->c;
 }
 
-static void *cx_mempool_calloc(
+static void *cx_mempool_calloc_simple(
         void *p,
         size_t nelem,
         size_t elsize
@@ -90,29 +101,31 @@
         errno = EOVERFLOW;
         return NULL;
     }
-    void *ptr = cx_mempool_malloc(p, msz);
+    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(
+static void *cx_mempool_realloc_simple(
         void *p,
         void *ptr,
         size_t n
 ) {
     struct cx_mempool_s *pool = p;
 
-    struct cx_mempool_memory_s *mem, *newm;
-    mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
-    newm = cxReallocDefault(mem, n + sizeof(cx_destructor_func));
+    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) + sizeof(cx_destructor_func);
+                return ((char*)newm) + overhead;
             }
         }
         abort(); // LCOV_EXCL_LINE
@@ -121,21 +134,27 @@
     }
 }
 
-static void cx_mempool_free(
+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 = (struct cx_mempool_memory_s *)
-            ((char *) ptr - sizeof(cx_destructor_func));
+    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) {
@@ -149,17 +168,54 @@
     abort(); // LCOV_EXCL_LINE
 }
 
-void cxMempoolFree(CxMempool *pool) {
-    if (pool == NULL) return;
-    struct cx_mempool_memory_s *mem;
+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++) {
-        mem = pool->data[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 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);
+        }
+    }
+}
+
+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
+};
+
+void cxMempoolFree(CxMempool *pool) {
+    if (pool == NULL) return;
+    if (pool->allocator->cl == &cx_mempool_simple_allocator_class) {
+        cx_mempool_free_all_simple(pool);
+    } else {
+        // TODO: implement
+    }
+    cx_mempool_free_foreign(pool);
     cxFreeDefault(pool->data);
+    cxFreeDefault(pool->registered);
     cxFreeDefault((void*) pool->allocator);
     cxFreeDefault(pool);
 }
@@ -175,64 +231,56 @@
     *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL;
 }
 
-struct cx_mempool_foreign_mem_s {
-    cx_destructor_func destr;
-    void* mem;
-};
-
-static void cx_mempool_destr_foreign_mem(void* ptr) {
-    struct cx_mempool_foreign_mem_s *fm = ptr;
-    fm->destr(fm->mem);
-}
-
 int cxMempoolRegister(
         CxMempool *pool,
         void *memory,
         cx_destructor_func destr
 ) {
-    struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc(
-            pool,
-            sizeof(struct cx_mempool_foreign_mem_s)
-    );
-    if (fm == NULL) return 1;
+    if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) {
+        return 1; // LCOV_EXCL_LINE
+    }
 
-    fm->mem = memory;
-    fm->destr = destr;
-    *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem;
+    pool->registered[pool->registered_size++] =
+        (struct cx_mempool_foreign_memory_s) {
+            .mem = memory,
+            .destr = destr,
+            .destr2_data = NULL
+        };
 
     return 0;
 }
 
-static cx_allocator_class cx_mempool_allocator_class = {
-        cx_mempool_malloc,
-        cx_mempool_realloc,
-        cx_mempool_calloc,
-        cx_mempool_free
-};
-
 CxMempool *cxMempoolCreate(
         size_t capacity,
-        cx_destructor_func destr
+        enum cx_mempool_type type
 ) {
+    if (capacity == 0) capacity = 16;
     size_t poolsize;
-    if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+    if (cx_szmul(capacity, sizeof(void*), &poolsize)) {
         errno = EOVERFLOW;
         return NULL;
     }
 
-    struct cx_mempool_s *pool =
-            cxMallocDefault(sizeof(struct cx_mempool_s));
-    if (pool == NULL) return NULL;
-
     CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator));
     if (provided_allocator == NULL) { // LCOV_EXCL_START
-        cxFreeDefault(pool);
         return NULL;
     } // LCOV_EXCL_STOP
-    provided_allocator->cl = &cx_mempool_allocator_class;
+
+    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) {
+        // TODO: implement
+    } else {
+        // TODO: implement
+    }
 
     pool->data = cxMallocDefault(poolsize);
     if (pool->data == NULL) { // LCOV_EXCL_START
@@ -243,7 +291,6 @@
 
     pool->size = 0;
     pool->capacity = capacity;
-    pool->auto_destr = destr;
 
     return pool;
 }
@@ -256,11 +303,16 @@
         CxMempool *source,
         CxMempool *dest
 ) {
-    // safety check
+    // 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 + 1)) {
+    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
     }
 
@@ -269,23 +321,32 @@
     if (new_source_allocator == NULL) { // LCOV_EXCL_START
         return 1;
     } // LCOV_EXCL_STOP
-    new_source_allocator->cl = &cx_mempool_allocator_class;
+    new_source_allocator->cl = source->allocator->cl;
     new_source_allocator->data = source;
 
     // transfer all the data
-    memcpy(&dest->data[dest->size], source->data, sizeof(source->data[0])*source->size);
+    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);
+    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(source->data[0]));
+    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;
 }
--- a/tests/test_mempool.c	Thu May 22 16:23:55 2025 +0200
+++ b/tests/test_mempool.c	Thu May 22 16:25:32 2025 +0200
@@ -34,7 +34,9 @@
 CX_TEST(test_mempool_create) {
     CxMempool *pool = cxMempoolCreateSimple(16);
     CX_TEST_DO {
-        CX_TEST_ASSERT(pool->auto_destr == NULL);
+        CX_TEST_ASSERT(pool->destr == NULL);
+        CX_TEST_ASSERT(pool->destr2 == NULL);
+        CX_TEST_ASSERT(pool->destr2_data == NULL);
         CX_TEST_ASSERT(pool->allocator != NULL);
         CX_TEST_ASSERT(pool->allocator->cl != NULL);
         CX_TEST_ASSERT(pool->allocator->data == pool);
@@ -86,9 +88,10 @@
 }
 
 CX_TEST(test_mempool_realloc) {
-    CxMempool *pool = cxMempoolCreate(4, test_mempool_destructor);
+    CxMempool *pool = cxMempoolCreateSimple(4);
+    cxMempoolGlobalDestructor(pool, test_mempool_destructor);
     CX_TEST_DO {
-        CX_TEST_ASSERT(pool->auto_destr == test_mempool_destructor);
+        CX_TEST_ASSERT(pool->destr == test_mempool_destructor);
         int *data = cxMalloc(pool->allocator, sizeof(int));
         *data = 13;
 
@@ -168,19 +171,19 @@
     CxMempool *src = cxMempoolCreateSimple(4);
     CxMempool *dest = cxMempoolCreateSimple(4);
     CX_TEST_DO {
+        // allocate the first object
+        int *c = cxMalloc(src->allocator, sizeof(int));
+        // allocate the second object
+        c = cxMalloc(src->allocator, sizeof(int));
         // check that the destructor functions are also transferred
-        src->auto_destr = test_mempool_destructor;
-
-        // allocate first object
-        int *c = cxMalloc(src->allocator, sizeof(int));
-        // allocate second object
-        c = cxMalloc(src->allocator, sizeof(int));
+        cxMempoolSetDestructor(c, test_mempool_destructor);
         // register foreign object
         c = malloc(sizeof(int));
         cxMempoolRegister(src, c, test_mempool_destructor);
 
         // check source pool
-        CX_TEST_ASSERT(src->size == 3);
+        CX_TEST_ASSERT(src->size == 2);
+        CX_TEST_ASSERT(src->registered_size == 1);
         const CxAllocator *old_allocator = src->allocator;
         CX_TEST_ASSERT(old_allocator->data == src);
 
@@ -190,7 +193,9 @@
 
         // check transfer
         CX_TEST_ASSERT(src->size == 0);
-        CX_TEST_ASSERT(dest->size == 4); // 3 objects + the old allocator
+        CX_TEST_ASSERT(dest->size == 2);
+        CX_TEST_ASSERT(src->registered_size == 0);
+        CX_TEST_ASSERT(dest->registered_size == 2); // 1 object + old allocator
         CX_TEST_ASSERT(src->allocator != old_allocator);
         CX_TEST_ASSERT(old_allocator->data == dest);
 
@@ -204,9 +209,9 @@
         CX_TEST_ASSERT(result != 0);
 
         // verify that destroying new pool calls the destructors
-        // but only three times (the old allocator has a different destructor)
+        // but only two times (the old allocator has a different destructor)
         cxMempoolFree(dest);
-        CX_TEST_ASSERT(test_mempool_destructor_called == 3);
+        CX_TEST_ASSERT(test_mempool_destructor_called == 2);
 
         // free the foreign object
         free(c);
@@ -221,30 +226,32 @@
         b = cxMalloc(src->allocator, sizeof(int));
         int *c = malloc(sizeof(int));
         cxMempoolRegister(src, c, free);
+        CX_TEST_ASSERT(src->size == 2);
+        CX_TEST_ASSERT(src->registered_size == 1);
 
-        CX_TEST_ASSERT(src->size == 3);
         int result = cxMempoolTransferObject(src, dest, b);
         CX_TEST_ASSERT(result == 0);
-        CX_TEST_ASSERT(src->size == 2);
+        CX_TEST_ASSERT(src->size == 1);
         CX_TEST_ASSERT(dest->size == 1);
         result = cxMempoolTransferObject(src, dest, b);
         CX_TEST_ASSERT(result != 0);
-        CX_TEST_ASSERT(src->size == 2);
+        CX_TEST_ASSERT(src->size == 1);
         CX_TEST_ASSERT(dest->size == 1);
+
         // cannot transfer foreign memory this way
         result = cxMempoolTransferObject(src, dest, c);
         CX_TEST_ASSERT(result != 0);
-        CX_TEST_ASSERT(src->size == 2);
+        CX_TEST_ASSERT(src->size == 1);
         CX_TEST_ASSERT(dest->size == 1);
+
         result = cxMempoolTransferObject(dest, dest, b);
         CX_TEST_ASSERT(result != 0);
-        CX_TEST_ASSERT(src->size == 2);
+        CX_TEST_ASSERT(src->size == 1);
         CX_TEST_ASSERT(dest->size == 1);
-
-        cxMempoolFree(src);
-        cxMempoolFree(dest);
-        // let valgrind check that everything worked
     }
+    cxMempoolFree(src);
+    cxMempoolFree(dest);
+    // let valgrind check that everything worked
 }
 
 CxTestSuite *cx_test_suite_mempool(void) {

mercurial