]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
uint-spbset: reused empty chunks
authorStefan Eissing <stefan@eissing.org>
Fri, 17 Jul 2026 12:10:03 +0000 (14:10 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 20 Jul 2026 22:38:26 +0000 (00:38 +0200)
Fix chunk allocations by reusing existing empty chunks when a new offset
is needed. Before this fix, spbsets would only ever grow with added
numbers outside the range of existing chunks.

Closes #22340

lib/uint-spbset.c

index d4e6c8e70bf51cd23a8486dc49b488350a9974e0..66b0fcb6708cb9a59cc7963ced9ed64536e93399 100644 (file)
@@ -74,90 +74,142 @@ uint32_t Curl_uint32_spbset_count(struct uint32_spbset *bset)
   return n;
 }
 
-static struct uint32_spbset_chunk *uint32_spbset_get_chunk(
-  struct uint32_spbset *bset, uint32_t i, bool grow)
+static bool uint32_spbset_empty_chunk(struct uint32_spbset_chunk *chunk)
 {
-  struct uint32_spbset_chunk *chunk, **panchor = NULL;
-  uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK);
-
-  if(!bset)
-    return NULL;
+  uint32_t i;
+  for(i = 0; i < CURL_UINT32_SPBSET_CH_SLOTS; ++i) {
+    if(chunk->slots[i])
+      return FALSE;
+  }
+  return TRUE;
+}
 
+static struct uint32_spbset_chunk *uint32_spbset_unlink_empty(
+  struct uint32_spbset *bset, uint32_t for_offset)
+{
+  struct uint32_spbset_chunk *chunk, **panchor = NULL;
   for(chunk = &bset->head; chunk;
       panchor = &chunk->next, chunk = chunk->next) {
-    if(chunk->offset == i_offset) {
-      return chunk;
-    }
-    else if(chunk->offset > i_offset) {
-      /* need new chunk here */
-      chunk = NULL;
+    if(uint32_spbset_empty_chunk(chunk))
       break;
+  }
+  if(chunk) {
+    if(chunk == &bset->head) { /* head chunk is empty */
+      if(!bset->head.next || (for_offset < bset->head.next->offset)) {
+        return &bset->head;
+      }
+      /* swap head and next, unlink */
+      chunk = bset->head.next;
+      memcpy(&bset->head, chunk, sizeof(bset->head));
+      memset(chunk, 0, sizeof(*chunk));
+    }
+    else {
+      *panchor = chunk->next; /* unlink */
+      memset(chunk, 0, sizeof(*chunk));
+    }
+  }
+  return chunk;
+}
+
+static struct uint32_spbset_chunk *uint32_spbset_insert_chunk(
+  struct uint32_spbset *bset, struct uint32_spbset_chunk *nchunk)
+{
+  struct uint32_spbset_chunk *chunk, **panchor;
+
+  /* insert nchunk into set's ordered chunk list */
+  if(nchunk->offset < bset->head.offset) {
+    /* swap chunk and head */
+    uint32_t offset = nchunk->offset;
+    memcpy(nchunk, &bset->head, sizeof(*nchunk));
+    memset(&bset->head, 0, sizeof(bset->head));
+    bset->head.next = nchunk;
+    bset->head.offset = offset;
+    return &bset->head;
+  }
+  DEBUGASSERT(nchunk->offset > bset->head.offset);
+  panchor = &bset->head.next;
+  for(chunk = *panchor; chunk;
+      panchor = &chunk->next, chunk = chunk->next) {
+    if(chunk->offset > nchunk->offset) { /* insert before this chunk */
+      nchunk->next = chunk;
+      *panchor = nchunk;
+      return nchunk;
     }
   }
+  /* no chunk with larger offset, append */
+  *panchor = nchunk;
+  return nchunk;
+}
 
-  if(!grow)
+static struct uint32_spbset_chunk *uint32_spbset_get_chunk(
+  struct uint32_spbset *bset, uint32_t i, bool grow)
+{
+  struct uint32_spbset_chunk *chunk;
+  uint32_t i_offset = (i & ~CURL_UINT32_SPBSET_CH_MASK);
+
+  if(!bset)
     return NULL;
 
-  /* need a new one */
-  chunk = curlx_calloc(1, sizeof(*chunk));
-  if(!chunk)
+  for(chunk = &bset->head; chunk; chunk = chunk->next) {
+    if(chunk->offset == i_offset)
+      return chunk;
+    else if(chunk->offset > i_offset)
+      break; /* need new chunk here */
+  }
+  if(!grow) /* just a check if the chunk exists */
     return NULL;
 
-  if(panchor) {  /* insert between panchor and *panchor */
-    chunk->next = *panchor;
-    *panchor = chunk;
+  /* Is there an empty chunk to reuse? */
+  chunk = uint32_spbset_unlink_empty(bset, i_offset);
+  if(chunk) {
+    chunk->offset = i_offset;
+    if(chunk == &bset->head) /* head chunk is empty, stayed linked */
+      return &bset->head;
+    /* was really unlinked, need to insert below */
   }
-  else {  /* prepend to head, switching places */
-    memcpy(chunk, &bset->head, sizeof(*chunk));
-    memset(&bset->head, 0, sizeof(bset->head));
-    bset->head.next = chunk;
+  else {
+    /* need a new one */
+    chunk = curlx_calloc(1, sizeof(*chunk));
+    if(!chunk)
+      return NULL;
+    chunk->offset = i_offset;
   }
-  chunk->offset = i_offset;
-  return chunk;
+
+  return uint32_spbset_insert_chunk(bset, chunk);
 }
 
 bool Curl_uint32_spbset_add(struct uint32_spbset *bset, uint32_t i)
 {
-  struct uint32_spbset_chunk *chunk;
-  uint32_t i_chunk;
-
-  chunk = uint32_spbset_get_chunk(bset, i, TRUE);
+  struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, TRUE);
   if(!chunk)
     return FALSE;
 
   DEBUGASSERT(i >= chunk->offset);
-  i_chunk = (i - chunk->offset);
-  DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS);
-  chunk->slots[(i_chunk / 64)] |= ((uint64_t)1 << (i_chunk % 64));
+  i -= chunk->offset;
+  DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64));
+  chunk->slots[(i / 64)] |= ((uint64_t)1 << (i % 64));
   return TRUE;
 }
 
 void Curl_uint32_spbset_remove(struct uint32_spbset *bset, uint32_t i)
 {
-  struct uint32_spbset_chunk *chunk;
-  uint32_t i_chunk;
-
-  chunk = uint32_spbset_get_chunk(bset, i, FALSE);
+  struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE);
   if(chunk) {
     DEBUGASSERT(i >= chunk->offset);
-    i_chunk = (i - chunk->offset);
-    DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS);
-    chunk->slots[(i_chunk / 64)] &= ~((uint64_t)1 << (i_chunk % 64));
+    i -= chunk->offset;
+    DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64));
+    chunk->slots[(i / 64)] &= ~((uint64_t)1 << (i % 64));
   }
 }
 
 bool Curl_uint32_spbset_contains(struct uint32_spbset *bset, uint32_t i)
 {
-  struct uint32_spbset_chunk *chunk;
-  uint32_t i_chunk;
-
-  chunk = uint32_spbset_get_chunk(bset, i, FALSE);
+  struct uint32_spbset_chunk *chunk = uint32_spbset_get_chunk(bset, i, FALSE);
   if(chunk) {
     DEBUGASSERT(i >= chunk->offset);
-    i_chunk = (i - chunk->offset);
-    DEBUGASSERT((i_chunk / 64) < CURL_UINT32_SPBSET_CH_SLOTS);
-    return (chunk->slots[i_chunk / 64] &
-            ((uint64_t)1 << (i_chunk % 64))) != 0;
+    i -= chunk->offset;
+    DEBUGASSERT(i < (CURL_UINT32_SPBSET_CH_SLOTS * 64));
+    return (chunk->slots[i / 64] & ((uint64_t)1 << (i % 64))) != 0;
   }
   return FALSE;
 }