add copy-on-extend feature to UCX buffer - fixes #533

Thu, 19 Dec 2024 21:33:45 +0100

author
Mike Becker <universe@uap-core.de>
date
Thu, 19 Dec 2024 21:33:45 +0100
changeset 1028
3e4905241838
parent 1027
b1373253e202
child 1029
c065d196a2c8

add copy-on-extend feature to UCX buffer - fixes #533

CHANGELOG file | annotate | diff | comparison | revisions
src/buffer.c file | annotate | diff | comparison | revisions
src/cx/buffer.h file | annotate | diff | comparison | revisions
tests/test_buffer.c file | annotate | diff | comparison | revisions
--- a/CHANGELOG	Thu Dec 19 12:00:20 2024 +0100
+++ b/CHANGELOG	Thu Dec 19 21:33:45 2024 +0100
@@ -9,7 +9,7 @@
  * adds cx_array_reallocator() and cx_array_default_reallocator
  * adds several new array and list functions
  * adds cxBufferReset()
- * adds CX_BUFFER_COPY_ON_WRITE flag
+ * adds CX_BUFFER_COPY_ON_WRITE and CX_BUFFER_COPY_ON_EXTEND flags
  * adds cx_cmp_ptr()
  * adds cx_sprintf() and several more variants
  * adds runtime constants to read out the actual SBO sizes
--- a/src/buffer.c	Thu Dec 19 12:00:20 2024 +0100
+++ b/src/buffer.c	Thu Dec 19 21:33:45 2024 +0100
@@ -31,14 +31,12 @@
 #include <stdio.h>
 #include <string.h>
 
-static int buffer_copy_on_write(CxBuffer* buffer, size_t newcap) {
+static int buffer_copy_on_write(CxBuffer* buffer) {
     if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0;
-    if (newcap == 0) newcap = buffer->capacity;
-    void *newspace = cxMalloc(buffer->allocator, newcap);
+    void *newspace = cxMalloc(buffer->allocator, buffer->capacity);
     if (NULL == newspace) return -1;
     memcpy(newspace, buffer->space, buffer->size);
     buffer->space = newspace;
-    buffer->capacity = newcap;
     buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE;
     buffer->flags |= CX_BUFFER_FREE_CONTENTS;
     return 0;
@@ -54,6 +52,9 @@
     if (allocator == NULL) {
         allocator = cxDefaultAllocator;
     }
+    if (flags & CX_BUFFER_COPY_ON_EXTEND) {
+        flags |= CX_BUFFER_AUTO_EXTEND;
+    }
     buffer->allocator = allocator;
     buffer->flags = flags;
     if (!space) {
@@ -170,8 +171,16 @@
         return 0;
     }
 
-    if (buffer->flags & CX_BUFFER_COPY_ON_WRITE) {
-        return buffer_copy_on_write(buffer, newcap);
+    const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND;
+    if (buffer->flags & force_copy_flags) {
+        void *newspace = cxMalloc(buffer->allocator, newcap);
+        if (NULL == newspace) return -1;
+        memcpy(newspace, buffer->space, buffer->size);
+        buffer->space = newspace;
+        buffer->capacity = newcap;
+        buffer->flags &= ~force_copy_flags;
+        buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+        return 0;
     } else if (cxReallocate(buffer->allocator,
                      (void **) &buffer->bytes, newcap) == 0) {
         buffer->capacity = newcap;
@@ -224,7 +233,7 @@
 ) {
     // optimize for easy case
     if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
-        if (buffer_copy_on_write(buffer, 0)) return 0;
+        if (buffer_copy_on_write(buffer)) return 0;
         memcpy(buffer->bytes + buffer->pos, ptr, nitems);
         buffer->pos += nitems;
         if (buffer->pos > buffer->size) {
@@ -311,7 +320,7 @@
             return cxBufferWrite(ptr, size, nitems, buffer);
         }
     } else {
-        if (buffer_copy_on_write(buffer, 0)) return 0;
+        if (buffer_copy_on_write(buffer)) return 0;
         memcpy(buffer->bytes + buffer->pos, ptr, len);
         buffer->pos += len;
         if (buffer->pos > buffer->size) {
@@ -395,7 +404,7 @@
     if (shift >= buffer->size) {
         buffer->pos = buffer->size = 0;
     } else {
-        if (buffer_copy_on_write(buffer, 0)) return -1;
+        if (buffer_copy_on_write(buffer)) return -1;
         memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
         buffer->size -= shift;
 
@@ -430,7 +439,7 @@
     }
 
     if (movebytes > 0) {
-        if (buffer_copy_on_write(buffer, 0)) return -1;
+        if (buffer_copy_on_write(buffer)) return -1;
         memmove(buffer->bytes + shift, buffer->bytes, movebytes);
         buffer->size = shift + movebytes;
     }
--- a/src/cx/buffer.h	Thu Dec 19 12:00:20 2024 +0100
+++ b/src/cx/buffer.h	Thu Dec 19 21:33:45 2024 +0100
@@ -79,6 +79,15 @@
  */
 #define CX_BUFFER_COPY_ON_WRITE 0x04
 
+/**
+ * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation.
+ *
+ * After performing the copy, the flag is automatically cleared.
+ * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why
+ * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled.
+ */
+#define CX_BUFFER_COPY_ON_EXTEND 0x08
+
 /** Structure for the UCX buffer data. */
 typedef struct {
     /** A pointer to the buffer contents. */
@@ -156,6 +165,11 @@
  * you will need to cast the pointer, and you should set the
  * #CX_BUFFER_COPY_ON_WRITE flag.
  *
+ * When you specify stack memory as \p space and decide to use
+ * the auto-extension feature, you \em must use the
+ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
+ * #CX_BUFFER_AUTO_EXTEND flag.
+ *
  * \note You may provide \c NULL as argument for \p space.
  * Then this function will allocate the space and enforce
  * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying
@@ -213,6 +227,10 @@
  * You may also provide a read-only \p space, in which case
  * you will need to cast the pointer, and you should set the
  * #CX_BUFFER_COPY_ON_WRITE flag.
+ * When you specify stack memory as \p space and decide to use
+ * the auto-extension feature, you \em must use the
+ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
+ * #CX_BUFFER_AUTO_EXTEND flag.
  *
  * \note You may provide \c NULL as argument for \p space.
  * Then this function will allocate the space and enforce
--- a/tests/test_buffer.c	Thu Dec 19 12:00:20 2024 +0100
+++ b/tests/test_buffer.c	Thu Dec 19 21:33:45 2024 +0100
@@ -933,7 +933,39 @@
     cxBufferDestroy(&buf);
 }
 
-CX_TEST(test_buffer_put_string_extend_copy_on_write) {
+CX_TEST(test_buffer_put_string_copy_on_extend) {
+    CxTestingAllocator talloc;
+    cx_testing_allocator_init(&talloc);
+    const CxAllocator *alloc = &talloc.base;
+    CxBuffer buf;
+    char original[16] = "preparedXXXXXXX\0";
+    CX_TEST_DO {
+        cxBufferInit(&buf, original, 16, alloc, CX_BUFFER_COPY_ON_EXTEND);
+        buf.capacity = 8;
+        buf.size = buf.pos = 4;
+        size_t written = cxBufferPutString(&buf, "test");
+        CX_TEST_ASSERT(written == 4);
+        CX_TEST_ASSERT(buf.size == 8);
+        CX_TEST_ASSERT(buf.pos == 8);
+        CX_TEST_ASSERT(buf.capacity == 8);
+        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptest", 8));
+        CX_TEST_ASSERT(original == buf.space);
+        written = cxBufferPutString(&buf, "ing");
+        CX_TEST_ASSERT(written == 3);
+        CX_TEST_ASSERT(buf.size == 11);
+        CX_TEST_ASSERT(buf.pos == 11);
+        CX_TEST_ASSERT(buf.capacity >= 11);
+        CX_TEST_ASSERT(0 == memcmp(buf.space, "preptesting", 11));
+        CX_TEST_ASSERT(original != buf.space);
+        CX_TEST_ASSERT(0 == memcmp(original, "preptestXXXXXXX\0", 16));
+        CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc));
+        cxBufferDestroy(&buf);
+        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
+        cx_testing_allocator_destroy(&talloc);
+    }
+}
+
+CX_TEST(test_buffer_put_string_copy_on_write) {
     CxBuffer buf;
     char original[16] = "preparedXXXXXXX\0";
     cxBufferInit(&buf, original, 16, cxDefaultAllocator, CX_BUFFER_COPY_ON_WRITE);
@@ -1289,7 +1321,8 @@
     cx_test_register(suite, test_buffer_put_string_fit);
     cx_test_register(suite, test_buffer_put_string_discard);
     cx_test_register(suite, test_buffer_put_string_extend);
-    cx_test_register(suite, test_buffer_put_string_extend_copy_on_write);
+    cx_test_register(suite, test_buffer_put_string_copy_on_extend);
+    cx_test_register(suite, test_buffer_put_string_copy_on_write);
     cx_test_register(suite, test_buffer_terminate);
     cx_test_register(suite, test_buffer_write_size_overflow);
     cx_test_register(suite, test_buffer_write_capacity_overflow);

mercurial