summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/src/quicklist.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
commitdcacc00e3750300617ba6e16eb346713f91a783a (patch)
tree38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/redis-unstable/src/quicklist.c
parent58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff)
downloadcrep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz
Remove testing data
Diffstat (limited to 'examples/redis-unstable/src/quicklist.c')
-rw-r--r--examples/redis-unstable/src/quicklist.c3658
1 files changed, 0 insertions, 3658 deletions
diff --git a/examples/redis-unstable/src/quicklist.c b/examples/redis-unstable/src/quicklist.c
deleted file mode 100644
index 4c9fc68..0000000
--- a/examples/redis-unstable/src/quicklist.c
+++ /dev/null
@@ -1,3658 +0,0 @@
-/* quicklist.c - A doubly linked list of listpacks
- *
- * Copyright (c) 2014, Matt Stancliff <matt@genges.com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must start the above copyright notice,
- * this quicklist of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this quicklist of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of Redis nor the names of its contributors may be used
- * to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <stdio.h>
-#include <string.h> /* for memcpy */
-#include <limits.h>
-#include "quicklist.h"
-#include "zmalloc.h"
-#include "config.h"
-#include "listpack.h"
-#include "util.h" /* for ll2string */
-#include "lzf.h"
-#include "redisassert.h"
-
-#ifndef REDIS_STATIC
-#define REDIS_STATIC static
-#endif
-
-/* Optimization levels for size-based filling.
- * Note that the largest possible limit is 64k, so even if each record takes
- * just one byte, it still won't overflow the 16 bit count field. */
-static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
-
-/* This is for test suite development purposes only, 0 means disabled. */
-static size_t packed_threshold = 0;
-
-/* set threshold for PLAIN nodes for test suit, the real limit is based on `fill` */
-int quicklistSetPackedThreshold(size_t sz) {
- /* Don't allow threshold to be set above or even slightly below 4GB */
- if (sz > (1ull<<32) - (1<<20)) {
- return 0;
- }
- packed_threshold = sz;
- return 1;
-}
-
-/* Maximum size in bytes of any multi-element listpack.
- * Larger values will live in their own isolated listpacks.
- * This is used only if we're limited by record count. when we're limited by
- * size, the maximum limit is bigger, but still safe.
- * 8k is a recommended / default size limit */
-#define SIZE_SAFETY_LIMIT 8192
-
-/* Maximum estimate of the listpack entry overhead.
- * Although in the worst case(sz < 64), we will waste 6 bytes in one
- * quicklistNode, but can avoid memory waste due to internal fragmentation
- * when the listpack exceeds the size limit by a few bytes (e.g. being 16388). */
-#define SIZE_ESTIMATE_OVERHEAD 8
-
-/* Minimum listpack size in bytes for attempting compression. */
-#define MIN_COMPRESS_BYTES 48
-
-/* Minimum size reduction in bytes to store compressed quicklistNode data.
- * This also prevents us from storing compression if the compression
- * resulted in a larger size than the original data. */
-#define MIN_COMPRESS_IMPROVE 8
-
-/* If not verbose testing, remove all debug printing. */
-#ifndef REDIS_TEST_VERBOSE
-#define D(...)
-#else
-#define D(...) \
- do { \
- printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \
- printf(__VA_ARGS__); \
- printf("\n"); \
- } while (0)
-#endif
-
-/* Bookmarks forward declarations */
-#define QL_MAX_BM ((1 << QL_BM_BITS)-1)
-quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name);
-quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node);
-void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm);
-
-REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklist *quicklist, quicklistNode *node,
- int offset, int after);
-REDIS_STATIC quicklistNode *_quicklistMergeNodes(quicklist *quicklist, quicklistNode *center);
-
-/* Simple way to give quicklistEntry structs default values with one call. */
-#define initEntry(e) \
- do { \
- (e)->zi = (e)->value = NULL; \
- (e)->longval = -123456789; \
- (e)->quicklist = NULL; \
- (e)->node = NULL; \
- (e)->offset = 123456789; \
- (e)->sz = 0; \
- } while (0)
-
-/* Reset the quicklistIter to prevent it from being used again after
- * insert, replace, or other against quicklist operation. */
-#define resetIterator(iter) \
- do { \
- (iter)->current = NULL; \
- (iter)->zi = NULL; \
- } while (0)
-
-#define quicklistUpdateAllocSize(ql, new, old) \
- do { \
- (ql)->alloc_size += (new); \
- (ql)->alloc_size -= (old); \
- } while (0)
-
-/* Create a new quicklist.
- * Free with quicklistRelease(). */
-quicklist *quicklistCreate(void) {
- struct quicklist *quicklist;
- size_t quicklist_sz;
-
- quicklist = zmalloc_usable(sizeof(*quicklist), &quicklist_sz);
- quicklist->head = quicklist->tail = NULL;
- quicklist->len = 0;
- quicklist->count = 0;
- quicklist->alloc_size = quicklist_sz;
- quicklist->compress = 0;
- quicklist->fill = -2;
- quicklist->bookmark_count = 0;
- return quicklist;
-}
-
-#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
-void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
- if (compress > COMPRESS_MAX) {
- compress = COMPRESS_MAX;
- } else if (compress < 0) {
- compress = 0;
- }
- quicklist->compress = compress;
-}
-
-#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
-void quicklistSetFill(quicklist *quicklist, int fill) {
- if (fill > FILL_MAX) {
- fill = FILL_MAX;
- } else if (fill < -5) {
- fill = -5;
- }
- quicklist->fill = fill;
-}
-
-void quicklistSetOptions(quicklist *quicklist, int fill, int compress) {
- quicklistSetFill(quicklist, fill);
- quicklistSetCompressDepth(quicklist, compress);
-}
-
-/* Create a new quicklist with some default parameters. */
-quicklist *quicklistNew(int fill, int compress) {
- quicklist *quicklist = quicklistCreate();
- quicklistSetOptions(quicklist, fill, compress);
- return quicklist;
-}
-
-REDIS_STATIC quicklistNode *quicklistCreateNode(quicklist *quicklist) {
- size_t node_usable;
- quicklistNode *node;
- node = zmalloc_usable(sizeof(*node), &node_usable);
- quicklist->alloc_size += node_usable;
- node->entry = NULL;
- node->count = 0;
- node->sz = 0;
- node->next = node->prev = NULL;
- node->encoding = QUICKLIST_NODE_ENCODING_RAW;
- node->container = QUICKLIST_NODE_CONTAINER_PACKED;
- node->recompress = 0;
- node->dont_compress = 0;
- return node;
-}
-
-/* Return cached quicklist count */
-unsigned long quicklistCount(const quicklist *ql) { return ql->count; }
-
-/* Return cached quicklist total memory used (in bytes) */
-size_t quicklistAllocSize(const quicklist *ql) { return ql->alloc_size; }
-
-/* Free entire quicklist. */
-void quicklistRelease(quicklist *quicklist) {
- unsigned long len;
- quicklistNode *current, *next;
- size_t usable;
-
- current = quicklist->head;
- len = quicklist->len;
- while (len--) {
- next = current->next;
-
- if (current->entry) {
- if (current->encoding == QUICKLIST_NODE_ENCODING_LZF) {
- quicklistLZF *lzf = (quicklistLZF *)current->entry;
- quicklist->alloc_size -= sizeof(*lzf) + lzf->sz;
- } else {
- quicklist->alloc_size -= current->sz;
- }
- zfree(current->entry);
- }
- zfree_usable(current, &usable);
- quicklist->alloc_size -= usable;
-
- current = next;
- }
- quicklistBookmarksClear(quicklist);
- debugAssert(quicklist->alloc_size == zmalloc_usable_size(quicklist));
- zfree(quicklist);
-}
-
-/* Compress the listpack in 'node' and update encoding details.
- * Returns 1 if listpack compressed successfully.
- * Returns 0 if compression failed or if listpack too small to compress. */
-REDIS_STATIC int __quicklistCompressNode(quicklist *quicklist, quicklistNode *node) {
-#ifdef REDIS_TEST
- node->attempted_compress = 1;
-#endif
- if (node->dont_compress) return 0;
-
- /* validate that the node is neither
- * tail nor head (it has prev and next)*/
- assert(node->prev && node->next);
-
- node->recompress = 0;
- /* Don't bother compressing small values */
- if (node->sz < MIN_COMPRESS_BYTES)
- return 0;
-
- quicklistLZF *lzf = zmalloc(sizeof(*lzf) + node->sz);
-
- /* Cancel if compression fails or doesn't compress small enough */
- if (((lzf->sz = lzf_compress(node->entry, node->sz, lzf->compressed,
- node->sz)) == 0) ||
- lzf->sz + MIN_COMPRESS_IMPROVE >= node->sz) {
- /* lzf_compress aborts/rejects compression if value not compressible. */
- zfree(lzf);
- return 0;
- }
- lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz);
- zfree(node->entry);
- node->entry = (unsigned char *)lzf;
- node->encoding = QUICKLIST_NODE_ENCODING_LZF;
- quicklistUpdateAllocSize(quicklist, sizeof(*lzf) + lzf->sz, node->sz);
- return 1;
-}
-
-/* Compress only uncompressed nodes. */
-#define quicklistCompressNode(_ql, _node) \
- do { \
- if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_RAW) { \
- __quicklistCompressNode((_ql), (_node)); \
- } \
- } while (0)
-
-/* Uncompress the listpack in 'node' and update encoding details.
- * Returns 1 on successful decode, 0 on failure to decode. */
-REDIS_STATIC int __quicklistDecompressNode(quicklist *quicklist, quicklistNode *node) {
-#ifdef REDIS_TEST
- node->attempted_compress = 0;
-#endif
- node->recompress = 0;
-
- void *decompressed = zmalloc(node->sz);
- quicklistLZF *lzf = (quicklistLZF *)node->entry;
- if (lzf_decompress(lzf->compressed, lzf->sz, decompressed, node->sz) == 0) {
- /* Someone requested decompress, but we can't decompress. Not good. */
- zfree(decompressed);
- return 0;
- }
- size_t oldsize = sizeof(*lzf) + lzf->sz;
- zfree(lzf);
- quicklistUpdateAllocSize(quicklist, node->sz, oldsize);
- node->entry = decompressed;
- node->encoding = QUICKLIST_NODE_ENCODING_RAW;
- return 1;
-}
-
-/* Decompress only compressed nodes. */
-#define quicklistDecompressNode(_ql, _node) \
- do { \
- if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \
- __quicklistDecompressNode((_ql), (_node)); \
- } \
- } while (0)
-
-/* Force node to not be immediately re-compressible */
-#define quicklistDecompressNodeForUse(_ql, _node) \
- do { \
- if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \
- __quicklistDecompressNode((_ql), (_node)); \
- (_node)->recompress = 1; \
- } \
- } while (0)
-
-/* Extract the raw LZF data from this quicklistNode.
- * Pointer to LZF data is assigned to '*data'.
- * Return value is the length of compressed LZF data. */
-size_t quicklistGetLzf(const quicklistNode *node, void **data) {
- quicklistLZF *lzf = (quicklistLZF *)node->entry;
- *data = lzf->compressed;
- return lzf->sz;
-}
-
-#define quicklistAllowsCompression(_ql) ((_ql)->compress != 0)
-
-/* Force 'quicklist' to meet compression guidelines set by compress depth.
- * The only way to guarantee interior nodes get compressed is to iterate
- * to our "interior" compress depth then compress the next node we find.
- * If compress depth is larger than the entire list, we return immediately. */
-REDIS_STATIC void __quicklistCompress(quicklist *quicklist,
- quicklistNode *node) {
- if (quicklist->len == 0) return;
-
- /* The head and tail should never be compressed (we should not attempt to recompress them) */
- assert(quicklist->head->recompress == 0 && quicklist->tail->recompress == 0);
-
- /* If length is less than our compress depth (from both sides),
- * we can't compress anything. */
- if (!quicklistAllowsCompression(quicklist) ||
- quicklist->len < (unsigned int)(quicklist->compress * 2))
- return;
-
-#if 0
- /* Optimized cases for small depth counts */
- if (quicklist->compress == 1) {
- quicklistNode *h = quicklist->head, *t = quicklist->tail;
- quicklistDecompressNode(quicklist, h);
- quicklistDecompressNode(quicklist, t);
- if (h != node && t != node)
- quicklistCompressNode(quicklist, node);
- return;
- } else if (quicklist->compress == 2) {
- quicklistNode *h = quicklist->head, *hn = h->next, *hnn = hn->next;
- quicklistNode *t = quicklist->tail, *tp = t->prev, *tpp = tp->prev;
- quicklistDecompressNode(quicklist, h);
- quicklistDecompressNode(quicklist, hn);
- quicklistDecompressNode(quicklist, t);
- quicklistDecompressNode(quicklist, tp);
- if (h != node && hn != node && t != node && tp != node) {
- quicklistCompressNode(quicklist, node);
- }
- if (hnn != t) {
- quicklistCompressNode(quicklist, hnn);
- }
- if (tpp != h) {
- quicklistCompressNode(quicklist, tpp);
- }
- return;
- }
-#endif
-
- /* Iterate until we reach compress depth for both sides of the list.a
- * Note: because we do length checks at the *top* of this function,
- * we can skip explicit null checks below. Everything exists. */
- quicklistNode *forward = quicklist->head;
- quicklistNode *reverse = quicklist->tail;
- int depth = 0;
- int in_depth = 0;
- while (depth++ < quicklist->compress) {
- quicklistDecompressNode(quicklist, forward);
- quicklistDecompressNode(quicklist, reverse);
-
- if (forward == node || reverse == node)
- in_depth = 1;
-
- /* We passed into compress depth of opposite side of the quicklist
- * so there's no need to compress anything and we can exit. */
- if (forward == reverse || forward->next == reverse)
- return;
-
- forward = forward->next;
- reverse = reverse->prev;
- }
-
- if (!in_depth)
- quicklistCompressNode(quicklist, node);
-
- /* At this point, forward and reverse are one node beyond depth */
- quicklistCompressNode(quicklist, forward);
- quicklistCompressNode(quicklist, reverse);
-}
-
-/* This macro is used to compress a node.
- *
- * If the 'recompress' flag of the node is true, we compress it directly without
- * checking whether it is within the range of compress depth.
- * However, it's important to ensure that the 'recompress' flag of head and tail
- * is always false, as we always assume that head and tail are not compressed.
- *
- * If the 'recompress' flag of the node is false, we check whether the node is
- * within the range of compress depth before compressing it. */
-#define quicklistCompress(_ql, _node) \
- do { \
- if ((_node)->recompress) \
- quicklistCompressNode((_ql), (_node)); \
- else \
- __quicklistCompress((_ql), (_node)); \
- } while (0)
-
-/* If we previously used quicklistDecompressNodeForUse(), just recompress. */
-#define quicklistRecompressOnly(_ql, _node) \
- do { \
- if ((_node)->recompress) \
- quicklistCompressNode((_ql), (_node)); \
- } while (0)
-
-/* Insert 'new_node' after 'old_node' if 'after' is 1.
- * Insert 'new_node' before 'old_node' if 'after' is 0.
- * Note: 'new_node' is *always* uncompressed, so if we assign it to
- * head or tail, we do not need to uncompress it. */
-REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
- quicklistNode *old_node,
- quicklistNode *new_node, int after) {
- if (after) {
- new_node->prev = old_node;
- if (old_node) {
- new_node->next = old_node->next;
- if (old_node->next)
- old_node->next->prev = new_node;
- old_node->next = new_node;
- }
- if (quicklist->tail == old_node)
- quicklist->tail = new_node;
- } else {
- new_node->next = old_node;
- if (old_node) {
- new_node->prev = old_node->prev;
- if (old_node->prev)
- old_node->prev->next = new_node;
- old_node->prev = new_node;
- }
- if (quicklist->head == old_node)
- quicklist->head = new_node;
- }
- /* If this insert creates the only element so far, initialize head/tail. */
- if (quicklist->len == 0) {
- quicklist->head = quicklist->tail = new_node;
- }
-
- /* Update len first, so in __quicklistCompress we know exactly len */
- quicklist->len++;
-
- if (old_node)
- quicklistCompress(quicklist, old_node);
-
- quicklistCompress(quicklist, new_node);
-}
-
-/* Wrappers for node inserting around existing node. */
-REDIS_STATIC void _quicklistInsertNodeBefore(quicklist *quicklist,
- quicklistNode *old_node,
- quicklistNode *new_node) {
- __quicklistInsertNode(quicklist, old_node, new_node, 0);
-}
-
-REDIS_STATIC void _quicklistInsertNodeAfter(quicklist *quicklist,
- quicklistNode *old_node,
- quicklistNode *new_node) {
- __quicklistInsertNode(quicklist, old_node, new_node, 1);
-}
-
-#define sizeMeetsSafetyLimit(sz) ((sz) <= SIZE_SAFETY_LIMIT)
-
-/* Calculate the size limit of the quicklist node based on negative 'fill'. */
-static size_t quicklistNodeNegFillLimit(int fill) {
- assert(fill < 0);
- size_t offset = (-fill) - 1;
- size_t max_level = sizeof(optimization_level) / sizeof(*optimization_level);
- if (offset >= max_level) offset = max_level - 1;
- return optimization_level[offset];
-}
-
-/* Calculate the size limit or length limit of the quicklist node
- * based on 'fill', and is also used to limit list listpack. */
-void quicklistNodeLimit(int fill, size_t *size, unsigned int *count) {
- *size = SIZE_MAX;
- *count = UINT_MAX;
-
- if (fill >= 0) {
- /* Ensure that one node have at least one entry */
- *count = (fill == 0) ? 1 : fill;
- } else {
- *size = quicklistNodeNegFillLimit(fill);
- }
-}
-
-/* Check if the limit of the quicklist node has been reached to determine if
- * insertions, merges or other operations that would increase the size of
- * the node can be performed.
- * Return 1 if exceeds the limit, otherwise 0. */
-int quicklistNodeExceedsLimit(int fill, size_t new_sz, unsigned int new_count) {
- size_t sz_limit;
- unsigned int count_limit;
- quicklistNodeLimit(fill, &sz_limit, &count_limit);
-
- if (likely(sz_limit != SIZE_MAX)) {
- return new_sz > sz_limit;
- } else if (count_limit != UINT_MAX) {
- /* when we reach here we know that the limit is a size limit (which is
- * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
- if (!sizeMeetsSafetyLimit(new_sz)) return 1;
- return new_count > count_limit;
- }
-
- redis_unreachable();
-}
-
-/* Determines whether a given size qualifies as a large element based on a threshold
- * determined by the 'fill'. If the size is considered large, it will be stored in
- * a plain node. */
-static int isLargeElement(size_t sz, int fill) {
- if (unlikely(packed_threshold != 0)) return sz >= packed_threshold;
- if (fill >= 0)
- return !sizeMeetsSafetyLimit(sz);
- else
- return sz > quicklistNodeNegFillLimit(fill);
-}
-
-REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
- const int fill, const size_t sz) {
- if (unlikely(!node))
- return 0;
-
- if (unlikely(QL_NODE_IS_PLAIN(node) || isLargeElement(sz, fill)))
- return 0;
-
- /* Estimate how many bytes will be added to the listpack by this one entry.
- * We prefer an overestimation, which would at worse lead to a few bytes
- * below the lowest limit of 4k (see optimization_level).
- * Note: No need to check for overflow below since both `node->sz` and
- * `sz` are to be less than 1GB after the plain/large element check above. */
- size_t new_sz = node->sz + sz + SIZE_ESTIMATE_OVERHEAD;
- if (unlikely(quicklistNodeExceedsLimit(fill, new_sz, node->count + 1)))
- return 0;
- return 1;
-}
-
-REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
- const quicklistNode *b,
- const int fill) {
- if (!a || !b)
- return 0;
-
- if (unlikely(QL_NODE_IS_PLAIN(a) || QL_NODE_IS_PLAIN(b)))
- return 0;
-
- /* approximate merged listpack size (- 7 to remove one listpack
- * header/trailer, see LP_HDR_SIZE and LP_EOF) */
- unsigned int merge_sz = a->sz + b->sz - 7;
- if (unlikely(quicklistNodeExceedsLimit(fill, merge_sz, a->count + b->count)))
- return 0;
- return 1;
-}
-
-#define quicklistNodeUpdateSz(node) \
- do { \
- (node)->sz = lpBytes((node)->entry); \
- } while (0)
-
-static quicklistNode* __quicklistCreateNode(quicklist *quicklist, int container, void *value, size_t sz) {
- quicklistNode *new_node = quicklistCreateNode(quicklist);
- new_node->container = container;
- if (container == QUICKLIST_NODE_CONTAINER_PLAIN) {
- new_node->entry = zmalloc(sz);
- memcpy(new_node->entry, value, sz);
- new_node->sz = sz;
- } else {
- new_node->entry = lpPrepend(lpNew(0), value, sz);
- quicklistNodeUpdateSz(new_node);
- }
- quicklist->alloc_size += new_node->sz;
- new_node->count++;
- return new_node;
-}
-
-static void __quicklistInsertPlainNode(quicklist *quicklist, quicklistNode *old_node,
- void *value, size_t sz, int after)
-{
- quicklistNode *new_node = __quicklistCreateNode(quicklist, QUICKLIST_NODE_CONTAINER_PLAIN, value, sz);
- __quicklistInsertNode(quicklist, old_node, new_node, after);
- quicklist->count++;
-}
-
-/* Add new entry to head node of quicklist.
- *
- * Returns 0 if used existing head.
- * Returns 1 if new head created. */
-int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
- quicklistNode *orig_head = quicklist->head;
-
- if (unlikely(isLargeElement(sz, quicklist->fill))) {
- __quicklistInsertPlainNode(quicklist, quicklist->head, value, sz, 0);
- return 1;
- }
-
- if (likely(
- _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
- size_t oldsize = quicklist->head->sz;
- quicklist->head->entry = lpPrepend(quicklist->head->entry, value, sz);
- quicklistNodeUpdateSz(quicklist->head);
- quicklistUpdateAllocSize(quicklist, quicklist->head->sz, oldsize);
- } else {
- quicklistNode *node = quicklistCreateNode(quicklist);
- node->entry = lpPrepend(lpNew(0), value, sz);
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, 0);
- _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
- }
- quicklist->count++;
- quicklist->head->count++;
- return (orig_head != quicklist->head);
-}
-
-/* Add new entry to tail node of quicklist.
- *
- * Returns 0 if used existing tail.
- * Returns 1 if new tail created. */
-int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
- quicklistNode *orig_tail = quicklist->tail;
- if (unlikely(isLargeElement(sz, quicklist->fill))) {
- __quicklistInsertPlainNode(quicklist, quicklist->tail, value, sz, 1);
- return 1;
- }
-
- if (likely(
- _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
- size_t oldsize = quicklist->tail->sz;
- quicklist->tail->entry = lpAppend(quicklist->tail->entry, value, sz);
- quicklistNodeUpdateSz(quicklist->tail);
- quicklistUpdateAllocSize(quicklist, quicklist->tail->sz, oldsize);
- } else {
- quicklistNode *node = quicklistCreateNode(quicklist);
- node->entry = lpAppend(lpNew(0), value, sz);
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, 0);
- _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
- }
- quicklist->count++;
- quicklist->tail->count++;
- return (orig_tail != quicklist->tail);
-}
-
-/* Create new node consisting of a pre-formed listpack.
- * Used for loading RDBs where entire listpacks have been stored
- * to be retrieved later. */
-void quicklistAppendListpack(quicklist *quicklist, unsigned char *zl) {
- quicklistNode *node = quicklistCreateNode(quicklist);
-
- node->entry = zl;
- node->count = lpLength(node->entry);
- node->sz = lpBytes(zl);
-
- quicklist->alloc_size += node->sz;
- _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
- quicklist->count += node->count;
-}
-
-/* Create new node consisting of a pre-formed plain node.
- * Used for loading RDBs where entire plain node has been stored
- * to be retrieved later.
- * data - the data to add (pointer becomes the responsibility of quicklist) */
-void quicklistAppendPlainNode(quicklist *quicklist, unsigned char *data, size_t sz) {
- quicklistNode *node = quicklistCreateNode(quicklist);
-
- node->entry = data;
- node->count = 1;
- node->sz = sz;
- node->container = QUICKLIST_NODE_CONTAINER_PLAIN;
-
- quicklist->alloc_size += sz;
- _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
- quicklist->count += node->count;
-}
-
-#define quicklistDeleteIfEmpty(ql, n) \
- do { \
- if ((n)->count == 0) { \
- __quicklistDelNode((ql), (n)); \
- (n) = NULL; \
- } \
- } while (0)
-
-REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
- quicklistNode *node) {
- /* Update the bookmark if any */
- quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node);
- if (bm) {
- bm->node = node->next;
- /* if the bookmark was to the last node, delete it. */
- if (!bm->node)
- _quicklistBookmarkDelete(quicklist, bm);
- }
-
- if (node->next)
- node->next->prev = node->prev;
- if (node->prev)
- node->prev->next = node->next;
-
- if (node == quicklist->tail) {
- quicklist->tail = node->prev;
- }
-
- if (node == quicklist->head) {
- quicklist->head = node->next;
- }
-
- /* Update len first, so in __quicklistCompress we know exactly len */
- quicklist->len--;
- quicklist->count -= node->count;
-
- /* If we deleted a node within our compress depth, we
- * now have compressed nodes needing to be decompressed. */
- __quicklistCompress(quicklist, NULL);
-
- if (node->encoding == QUICKLIST_NODE_ENCODING_LZF) {
- quicklistLZF *lzf = (quicklistLZF *)node->entry;
- size_t lzf_sz = sizeof(*lzf) + lzf->sz;
- quicklist->alloc_size -= lzf_sz;
- } else {
- quicklist->alloc_size -= node->sz;
- }
- zfree(node->entry);
- size_t usable;
- zfree_usable(node, &usable);
- quicklist->alloc_size -= usable;
-}
-
-/* Delete one entry from list given the node for the entry and a pointer
- * to the entry in the node.
- *
- * Note: quicklistDelIndex() *requires* uncompressed nodes because you
- * already had to get *p from an uncompressed node somewhere.
- *
- * Returns 1 if the entire node was deleted, 0 if node still exists.
- * Also updates in/out param 'p' with the next offset in the listpack. */
-REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,
- unsigned char **p) {
- int gone = 0;
-
- if (unlikely(QL_NODE_IS_PLAIN(node))) {
- __quicklistDelNode(quicklist, node);
- return 1;
- }
- size_t oldsize = node->sz;
- node->entry = lpDelete(node->entry, *p, p);
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, oldsize);
- node->count--;
- if (node->count == 0) {
- gone = 1;
- __quicklistDelNode(quicklist, node);
- }
- quicklist->count--;
- /* If we deleted the node, the original node is no longer valid */
- return gone ? 1 : 0;
-}
-
-/* Delete one element represented by 'entry'
- *
- * 'entry' stores enough metadata to delete the proper position in
- * the correct listpack in the correct quicklist node. */
-void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
- quicklistNode *prev = entry->node->prev;
- quicklistNode *next = entry->node->next;
- int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
- entry->node, &entry->zi);
-
- /* after delete, the zi is now invalid for any future usage. */
- iter->zi = NULL;
-
- /* If current node is deleted, we must update iterator node and offset. */
- if (deleted_node) {
- if (iter->direction == AL_START_HEAD) {
- iter->current = next;
- iter->offset = 0;
- } else if (iter->direction == AL_START_TAIL) {
- iter->current = prev;
- iter->offset = -1;
- }
- }
- /* else if (!deleted_node), no changes needed.
- * we already reset iter->zi above, and the existing iter->offset
- * doesn't move again because:
- * - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1
- * - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0
- * if we deleted the last element at offset N and now
- * length of this listpack is N-1, the next call into
- * quicklistNext() will jump to the next node. */
-}
-
-/* Replace quicklist entry by 'data' with length 'sz'. */
-void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry,
- void *data, size_t sz)
-{
- quicklist* quicklist = iter->quicklist;
- quicklistNode *node = entry->node;
- unsigned char *newentry;
-
- if (likely(!QL_NODE_IS_PLAIN(entry->node) && !isLargeElement(sz, quicklist->fill) &&
- (newentry = lpReplace(entry->node->entry, &entry->zi, data, sz)) != NULL))
- {
- entry->node->entry = newentry;
- size_t oldsize = entry->node->sz;
- quicklistNodeUpdateSz(entry->node);
- quicklistUpdateAllocSize(quicklist, entry->node->sz, oldsize);
- /* quicklistNext() and quicklistInitIteratorEntryAtIdx() provide an uncompressed node */
- quicklistCompress(quicklist, entry->node);
- } else if (QL_NODE_IS_PLAIN(entry->node)) {
- if (isLargeElement(sz, quicklist->fill)) {
- zfree(entry->node->entry);
- entry->node->entry = zmalloc(sz);
- size_t oldsize = entry->node->sz;
- quicklistUpdateAllocSize(quicklist, sz, oldsize);
- entry->node->sz = sz;
- memcpy(entry->node->entry, data, sz);
- quicklistCompress(quicklist, entry->node);
- } else {
- quicklistInsertAfter(iter, entry, data, sz);
- __quicklistDelNode(quicklist, entry->node);
- }
- } else { /* The node is full or data is a large element */
- quicklistNode *split_node = NULL, *new_node;
- node->dont_compress = 1; /* Prevent compression in __quicklistInsertNode() */
-
- /* If the entry is not at the tail, split the node at the entry's offset. */
- if (entry->offset != node->count - 1 && entry->offset != -1)
- split_node = _quicklistSplitNode(quicklist, node, entry->offset, 1);
-
- /* Create a new node and insert it after the original node.
- * If the original node was split, insert the split node after the new node. */
- new_node = __quicklistCreateNode(quicklist, isLargeElement(sz, quicklist->fill) ?
- QUICKLIST_NODE_CONTAINER_PLAIN : QUICKLIST_NODE_CONTAINER_PACKED, data, sz);
- __quicklistInsertNode(quicklist, node, new_node, 1);
- if (split_node) __quicklistInsertNode(quicklist, new_node, split_node, 1);
- quicklist->count++;
-
- /* Delete the replaced element. */
- if (entry->node->count == 1) {
- __quicklistDelNode(quicklist, entry->node);
- } else {
- unsigned char *p = lpSeek(entry->node->entry, -1);
- quicklistDelIndex(quicklist, entry->node, &p);
- entry->node->dont_compress = 0; /* Re-enable compression */
- new_node = _quicklistMergeNodes(quicklist, new_node);
- /* We can't know if the current node and its sibling nodes are correctly compressed,
- * and we don't know if they are within the range of compress depth, so we need to
- * use quicklistCompress() for compression, which checks if node is within compress
- * depth before compressing. */
- quicklistCompress(quicklist, new_node);
- quicklistCompress(quicklist, new_node->prev);
- if (new_node->next) quicklistCompress(quicklist, new_node->next);
- }
- }
-
- /* In any case, we reset iterator to forbid use of iterator after insert.
- * Notice: iter->current has been compressed above. */
- resetIterator(iter);
-}
-
-/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'.
- *
- * Returns 1 if replace happened.
- * Returns 0 if replace failed and no changes happened. */
-int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
- size_t sz) {
- quicklistEntry entry;
- quicklistIter iter;
- if (likely(quicklistInitIteratorEntryAtIdx(&iter, quicklist, index, &entry))) {
- quicklistReplaceEntry(&iter, &entry, data, sz);
- quicklistResetIterator(&iter);
- return 1;
- } else {
- return 0;
- }
-}
-
-/* Given two nodes, try to merge their listpacks.
- *
- * This helps us not have a quicklist with 3 element listpacks if
- * our fill factor can handle much higher levels.
- *
- * Note: 'a' must be to the LEFT of 'b'.
- *
- * After calling this function, both 'a' and 'b' should be considered
- * unusable. The return value from this function must be used
- * instead of re-using any of the quicklistNode input arguments.
- *
- * Returns the input node picked to merge against or NULL if
- * merging was not possible. */
-REDIS_STATIC quicklistNode *_quicklistListpackMerge(quicklist *quicklist,
- quicklistNode *a,
- quicklistNode *b) {
- D("Requested merge (a,b) (%u, %u)", a->count, b->count);
-
- quicklistDecompressNode(quicklist, a);
- quicklistDecompressNode(quicklist, b);
- if ((lpMerge(&a->entry, &b->entry))) {
- /* We merged listpacks! Now remove the unused quicklistNode. */
- quicklistNode *keep = NULL, *nokeep = NULL;
- if (!a->entry) {
- nokeep = a;
- keep = b;
- } else if (!b->entry) {
- nokeep = b;
- keep = a;
- }
- keep->count = lpLength(keep->entry);
- size_t oldsize = keep->sz;
- quicklistNodeUpdateSz(keep);
- quicklistUpdateAllocSize(quicklist, keep->sz, oldsize);
- keep->recompress = 0; /* Prevent 'keep' from being recompressed if
- * it becomes head or tail after merging. */
-
- nokeep->count = 0;
- __quicklistDelNode(quicklist, nokeep);
- quicklistCompress(quicklist, keep);
- return keep;
- } else {
- /* else, the merge returned NULL and nothing changed. */
- return NULL;
- }
-}
-
-/* Attempt to merge listpacks within two nodes on either side of 'center'.
- *
- * We attempt to merge:
- * - (center->prev->prev, center->prev)
- * - (center->next, center->next->next)
- * - (center->prev, center)
- * - (center, center->next)
- *
- * Returns the new 'center' after merging.
- */
-REDIS_STATIC quicklistNode *_quicklistMergeNodes(quicklist *quicklist, quicklistNode *center) {
- int fill = quicklist->fill;
- quicklistNode *prev, *prev_prev, *next, *next_next, *target;
- prev = prev_prev = next = next_next = target = NULL;
-
- if (center->prev) {
- prev = center->prev;
- if (center->prev->prev)
- prev_prev = center->prev->prev;
- }
-
- if (center->next) {
- next = center->next;
- if (center->next->next)
- next_next = center->next->next;
- }
-
- /* Try to merge prev_prev and prev */
- if (_quicklistNodeAllowMerge(prev, prev_prev, fill)) {
- _quicklistListpackMerge(quicklist, prev_prev, prev);
- prev_prev = prev = NULL; /* they could have moved, invalidate them. */
- }
-
- /* Try to merge next and next_next */
- if (_quicklistNodeAllowMerge(next, next_next, fill)) {
- _quicklistListpackMerge(quicklist, next, next_next);
- next = next_next = NULL; /* they could have moved, invalidate them. */
- }
-
- /* Try to merge center node and previous node */
- if (_quicklistNodeAllowMerge(center, center->prev, fill)) {
- target = _quicklistListpackMerge(quicklist, center->prev, center);
- center = NULL; /* center could have been deleted, invalidate it. */
- } else {
- /* else, we didn't merge here, but target needs to be valid below. */
- target = center;
- }
-
- /* Use result of center merge (or original) to merge with next node. */
- if (_quicklistNodeAllowMerge(target, target->next, fill)) {
- target = _quicklistListpackMerge(quicklist, target, target->next);
- }
- return target;
-}
-
-/* Split 'node' into two parts, parameterized by 'offset' and 'after'.
- *
- * The 'after' argument controls which quicklistNode gets returned.
- * If 'after'==1, returned node has elements after 'offset'.
- * input node keeps elements up to 'offset', including 'offset'.
- * If 'after'==0, returned node has elements up to 'offset'.
- * input node keeps elements after 'offset', including 'offset'.
- *
- * Or in other words:
- * If 'after'==1, returned node will have elements after 'offset'.
- * The returned node will have elements [OFFSET+1, END].
- * The input node keeps elements [0, OFFSET].
- * If 'after'==0, returned node will keep elements up to but not including 'offset'.
- * The returned node will have elements [0, OFFSET-1].
- * The input node keeps elements [OFFSET, END].
- *
- * The input node keeps all elements not taken by the returned node.
- *
- * Returns newly created node or NULL if split not possible. */
-REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklist *quicklist, quicklistNode *node,
- int offset, int after) {
- size_t zl_sz = node->sz;
-
- /* New node is detached on return but all callers add it back to quicklist
- * so we account its allocation here and below directly on quicklist. */
- quicklistNode *new_node = quicklistCreateNode(quicklist);
- new_node->entry = zmalloc(zl_sz);
-
- /* Copy original listpack so we can split it */
- memcpy(new_node->entry, node->entry, zl_sz);
-
- /* Need positive offset for calculating extent below. */
- if (offset < 0) offset = node->count + offset;
-
- /* Ranges to be trimmed: -1 here means "continue deleting until the list ends" */
- int orig_start = after ? offset + 1 : 0;
- int orig_extent = after ? -1 : offset;
- int new_start = after ? 0 : offset;
- int new_extent = after ? offset + 1 : -1;
-
- D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start,
- orig_extent, new_start, new_extent);
-
- size_t oldsize = node->sz;
- node->entry = lpDeleteRange(node->entry, orig_start, orig_extent);
- node->count = lpLength(node->entry);
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, oldsize);
-
- new_node->entry = lpDeleteRange(new_node->entry, new_start, new_extent);
- new_node->count = lpLength(new_node->entry);
- quicklistNodeUpdateSz(new_node);
- quicklistUpdateAllocSize(quicklist, new_node->sz, 0);
-
- D("After split lengths: orig (%d), new (%d)", node->count, new_node->count);
- return new_node;
-}
-
-/* Insert a new entry before or after existing entry 'entry'.
- *
- * If after==1, the new value is inserted after 'entry', otherwise
- * the new value is inserted before 'entry'. */
-REDIS_STATIC void _quicklistInsert(quicklistIter *iter, quicklistEntry *entry,
- void *value, const size_t sz, int after)
-{
- quicklist *quicklist = iter->quicklist;
- int full = 0, at_tail = 0, at_head = 0, avail_next = 0, avail_prev = 0;
- int fill = quicklist->fill;
- quicklistNode *node = entry->node;
- quicklistNode *new_node = NULL;
-
- if (!node) {
- /* we have no reference node, so let's create only node in the list */
- D("No node given!");
- if (unlikely(isLargeElement(sz, quicklist->fill))) {
- __quicklistInsertPlainNode(quicklist, quicklist->tail, value, sz, after);
- return;
- }
- new_node = quicklistCreateNode(quicklist);
- new_node->entry = lpPrepend(lpNew(0), value, sz);
- quicklistNodeUpdateSz(new_node);
- quicklistUpdateAllocSize(quicklist, new_node->sz, 0);
- __quicklistInsertNode(quicklist, NULL, new_node, after);
- new_node->count++;
- quicklist->count++;
- return;
- }
-
- /* Populate accounting flags for easier boolean checks later */
- if (!_quicklistNodeAllowInsert(node, fill, sz)) {
- D("Current node is full with count %d with requested fill %d",
- node->count, fill);
- full = 1;
- }
-
- if (after && (entry->offset == node->count - 1 || entry->offset == -1)) {
- D("At Tail of current listpack");
- at_tail = 1;
- if (_quicklistNodeAllowInsert(node->next, fill, sz)) {
- D("Next node is available.");
- avail_next = 1;
- }
- }
-
- if (!after && (entry->offset == 0 || entry->offset == -(node->count))) {
- D("At Head");
- at_head = 1;
- if (_quicklistNodeAllowInsert(node->prev, fill, sz)) {
- D("Prev node is available.");
- avail_prev = 1;
- }
- }
-
- if (unlikely(isLargeElement(sz, quicklist->fill))) {
- if (QL_NODE_IS_PLAIN(node) || (at_tail && after) || (at_head && !after)) {
- __quicklistInsertPlainNode(quicklist, node, value, sz, after);
- } else {
- quicklistDecompressNodeForUse(quicklist, node);
- new_node = _quicklistSplitNode(quicklist, node, entry->offset, after);
- quicklistNode *entry_node = __quicklistCreateNode(quicklist, QUICKLIST_NODE_CONTAINER_PLAIN, value, sz);
- __quicklistInsertNode(quicklist, node, entry_node, after);
- __quicklistInsertNode(quicklist, entry_node, new_node, after);
- quicklist->count++;
- }
- return;
- }
-
- /* Now determine where and how to insert the new element */
- if (!full && after) {
- D("Not full, inserting after current position.");
- quicklistDecompressNodeForUse(quicklist, node);
- node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_AFTER, NULL);
- size_t oldsize = node->sz;
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, oldsize);
- node->count++;
- quicklistRecompressOnly(quicklist, node);
- } else if (!full && !after) {
- D("Not full, inserting before current position.");
- quicklistDecompressNodeForUse(quicklist, node);
- node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_BEFORE, NULL);
- size_t oldsize = node->sz;
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, oldsize);
- node->count++;
- quicklistRecompressOnly(quicklist, node);
- } else if (full && at_tail && avail_next && after) {
- /* If we are: at tail, next has free space, and inserting after:
- * - insert entry at head of next node. */
- D("Full and tail, but next isn't full; inserting next node head");
- new_node = node->next;
- quicklistDecompressNodeForUse(quicklist, new_node);
- new_node->entry = lpPrepend(new_node->entry, value, sz);
- size_t oldsize = new_node->sz;
- quicklistNodeUpdateSz(new_node);
- quicklistUpdateAllocSize(quicklist, new_node->sz, oldsize);
- new_node->count++;
- quicklistRecompressOnly(quicklist, new_node);
- quicklistRecompressOnly(quicklist, node);
- } else if (full && at_head && avail_prev && !after) {
- /* If we are: at head, previous has free space, and inserting before:
- * - insert entry at tail of previous node. */
- D("Full and head, but prev isn't full, inserting prev node tail");
- new_node = node->prev;
- quicklistDecompressNodeForUse(quicklist, new_node);
- new_node->entry = lpAppend(new_node->entry, value, sz);
- size_t oldsize = new_node->sz;
- quicklistNodeUpdateSz(new_node);
- quicklistUpdateAllocSize(quicklist, new_node->sz, oldsize);
- new_node->count++;
- quicklistRecompressOnly(quicklist, new_node);
- quicklistRecompressOnly(quicklist, node);
- } else if (full && ((at_tail && !avail_next && after) ||
- (at_head && !avail_prev && !after))) {
- /* If we are: full, and our prev/next has no available space, then:
- * - create new node and attach to quicklist */
- D("\tprovisioning new node...");
- new_node = quicklistCreateNode(quicklist);
- new_node->entry = lpPrepend(lpNew(0), value, sz);
- quicklistNodeUpdateSz(new_node);
- quicklistUpdateAllocSize(quicklist, new_node->sz, 0);
- new_node->count++;
- __quicklistInsertNode(quicklist, node, new_node, after);
- } else if (full) {
- /* else, node is full we need to split it. */
- /* covers both after and !after cases */
- D("\tsplitting node...");
- quicklistDecompressNodeForUse(quicklist, node);
- new_node = _quicklistSplitNode(quicklist, node, entry->offset, after);
- if (after)
- new_node->entry = lpPrepend(new_node->entry, value, sz);
- else
- new_node->entry = lpAppend(new_node->entry, value, sz);
- size_t oldsize = new_node->sz;
- quicklistNodeUpdateSz(new_node);
- quicklistUpdateAllocSize(quicklist, new_node->sz, oldsize);
- new_node->count++;
- __quicklistInsertNode(quicklist, node, new_node, after);
- _quicklistMergeNodes(quicklist, node);
- }
-
- quicklist->count++;
-
- /* In any case, we reset iterator to forbid use of iterator after insert.
- * Notice: iter->current has been compressed in _quicklistInsert(). */
- resetIterator(iter);
-}
-
-void quicklistInsertBefore(quicklistIter *iter, quicklistEntry *entry,
- void *value, const size_t sz)
-{
- _quicklistInsert(iter, entry, value, sz, 0);
-}
-
-void quicklistInsertAfter(quicklistIter *iter, quicklistEntry *entry,
- void *value, const size_t sz)
-{
- _quicklistInsert(iter, entry, value, sz, 1);
-}
-
-/* Delete a range of elements from the quicklist.
- *
- * elements may span across multiple quicklistNodes, so we
- * have to be careful about tracking where we start and end.
- *
- * Returns 1 if entries were deleted, 0 if nothing was deleted. */
-int quicklistDelRange(quicklist *quicklist, const long start,
- const long count) {
- if (count <= 0)
- return 0;
-
- unsigned long extent = count; /* range is inclusive of start position */
-
- if (start >= 0 && extent > (quicklist->count - start)) {
- /* if requesting delete more elements than exist, limit to list size. */
- extent = quicklist->count - start;
- } else if (start < 0 && extent > (unsigned long)(-start)) {
- /* else, if at negative offset, limit max size to rest of list. */
- extent = -start; /* c.f. LREM -29 29; just delete until end. */
- }
-
- quicklistIter iter;
- if (!quicklistInitIteratorAtIdx(&iter, quicklist, AL_START_TAIL, start))
- return 0;
-
- D("Quicklist delete request for start %ld, count %ld, extent: %ld", start,
- count, extent);
- quicklistNode *node = iter.current;
- long offset = iter.offset;
- quicklistResetIterator(&iter);
-
- /* iterate over next nodes until everything is deleted. */
- while (extent) {
- quicklistNode *next = node->next;
-
- unsigned long del;
- int delete_entire_node = 0;
- if (offset == 0 && extent >= node->count) {
- /* If we are deleting more than the count of this node, we
- * can just delete the entire node without listpack math. */
- delete_entire_node = 1;
- del = node->count;
- } else if (offset >= 0 && extent + offset >= node->count) {
- /* If deleting more nodes after this one, calculate delete based
- * on size of current node. */
- del = node->count - offset;
- } else if (offset < 0) {
- /* If offset is negative, we are in the first run of this loop
- * and we are deleting the entire range
- * from this start offset to end of list. Since the Negative
- * offset is the number of elements until the tail of the list,
- * just use it directly as the deletion count. */
- del = -offset;
-
- /* If the positive offset is greater than the remaining extent,
- * we only delete the remaining extent, not the entire offset.
- */
- if (del > extent)
- del = extent;
- } else {
- /* else, we are deleting less than the extent of this node, so
- * use extent directly. */
- del = extent;
- }
-
- D("[%ld]: asking to del: %ld because offset: %ld; (ENTIRE NODE: %d), "
- "node count: %u",
- extent, del, offset, delete_entire_node, node->count);
-
- if (delete_entire_node || QL_NODE_IS_PLAIN(node)) {
- __quicklistDelNode(quicklist, node);
- } else {
- quicklistDecompressNodeForUse(quicklist, node);
- node->entry = lpDeleteRange(node->entry, offset, del);
- size_t oldsize = node->sz;
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(quicklist, node->sz, oldsize);
- node->count -= del;
- quicklist->count -= del;
- quicklistDeleteIfEmpty(quicklist, node);
- if (node)
- quicklistRecompressOnly(quicklist, node);
- }
-
- extent -= del;
-
- node = next;
-
- offset = 0;
- }
- return 1;
-}
-
-/* Compare a quicklistEntry with a raw value.
- *
- * If the entry stores a string (entry->value != NULL), perform a binary-safe
- * comparison against p2.
- *
- * If the entry stores an integer (entry->value == NULL), lazily convert p2 to
- * a long long using string2ll() once and cache the result using cached_longval
- * and cached_valid.
- *
- * This optimization avoids repeatedly calling string2ll() in tight loops.
- * - If cached_valid == NULL: skip caching
- * - If cached_valid == 0: conversion attempted
- * - If cached_valid == 1/-1: cached result reused
- *
- * Returns 1 if equal, 0 otherwise.
- */
-int quicklistCompare(quicklistEntry *entry, unsigned char *p2, const size_t p2_len,
- long long *cached_longval, int *cached_valid) {
- if (entry->value) {
- return ((entry->sz == p2_len) && (memcmp(entry->value, p2, p2_len) == 0));
- } else {
- /* We use string2ll() to get an integer representation of the
- * string 'p2' and compare it to 'entry->longval', it's much
- * faster than convert integer to string and comparing. */
- if (cached_valid != NULL) {
- /* Use caching */
- if (*cached_valid == 0) {
- if (string2ll((const char *)p2, p2_len, cached_longval)) {
- *cached_valid = 1;
- } else {
- *cached_valid = -1;
- }
- }
- return (*cached_valid == 1 && entry->longval == *cached_longval);
- } else {
- /* No caching - direct conversion */
- long long sval;
- if (string2ll((const char *)p2, p2_len, &sval))
- return entry->longval == sval;
- }
- }
- return 0;
-}
-
-/* Initialize a quicklist iterator 'iter'. After the initialization every
- * call to quicklistNext() will return the next element of the quicklist. */
-void quicklistInitIterator(quicklistIter *iter, quicklist *quicklist, int direction) {
- if (direction == AL_START_HEAD) {
- iter->current = quicklist->head;
- iter->offset = 0;
- } else if (direction == AL_START_TAIL) {
- iter->current = quicklist->tail;
- iter->offset = -1;
- }
-
- iter->direction = direction;
- iter->quicklist = quicklist;
-
- iter->zi = NULL;
-}
-
-/* Initialize an iterator at a specific offset 'idx' and make the iterator
- * return nodes in 'direction' direction. Returns 1 on success, 0 if index out of range. */
-int quicklistInitIteratorAtIdx(quicklistIter *iter, quicklist *quicklist,
- const int direction, const long long idx)
-{
- quicklistNode *n;
- unsigned long long accum = 0;
- unsigned long long index;
- int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */
-
- quicklistInitIterator(iter, quicklist, direction);
-
- index = forward ? idx : (-idx) - 1;
- if (index >= quicklist->count) {
- iter->current = NULL;
- return 0;
- }
-
- /* Seek in the other direction if that way is shorter. */
- int seek_forward = forward;
- unsigned long long seek_index = index;
- if (index > (quicklist->count - 1) / 2) {
- seek_forward = !forward;
- seek_index = quicklist->count - 1 - index;
- }
-
- n = seek_forward ? quicklist->head : quicklist->tail;
- while (likely(n)) {
- if ((accum + n->count) > seek_index) {
- break;
- } else {
- D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
- accum);
- accum += n->count;
- n = seek_forward ? n->next : n->prev;
- }
- }
-
- if (!n) {
- iter->current = NULL;
- return 0;
- }
-
- /* Fix accum so it looks like we seeked in the other direction. */
- if (seek_forward != forward) accum = quicklist->count - n->count - accum;
-
- D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
- accum, index, index - accum, (-index) - 1 + accum);
-
- iter->current = n;
- if (forward) {
- /* forward = normal head-to-tail offset. */
- iter->offset = index - accum;
- } else {
- /* reverse = need negative offset for tail-to-head, so undo
- * the result of the original index = (-idx) - 1 above. */
- iter->offset = (-index) - 1 + accum;
- }
-
- return 1;
-}
-
-/* Reset iterator.
- * If we still have a valid current node, then re-encode current node. */
-void quicklistResetIterator(quicklistIter *iter) {
- if (iter->current)
- quicklistCompress(iter->quicklist, iter->current);
-}
-
-/* Get next element in iterator.
- *
- * Note: You must NOT insert into the list while iterating over it.
- * You *may* delete from the list while iterating using the
- * quicklistDelEntry() function.
- * If you insert into the quicklist while iterating, you should
- * re-create the iterator after your addition.
- *
- * iter = quicklistGetIterator(quicklist,<direction>);
- * quicklistEntry entry;
- * while (quicklistNext(iter, &entry)) {
- * if (entry.value)
- * [[ use entry.value with entry.sz ]]
- * else
- * [[ use entry.longval ]]
- * }
- *
- * Populates 'entry' with values for this iteration.
- * Returns 0 when iteration is complete or if iteration not possible.
- * If return value is 0, the contents of 'entry' are not valid.
- */
-int quicklistNext(quicklistIter *iter, quicklistEntry *entry) {
- initEntry(entry);
- entry->quicklist = iter->quicklist;
- entry->node = iter->current;
-
- if (!iter->current) {
- D("Returning because current node is NULL");
- return 0;
- }
-
- unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL;
- int offset_update = 0;
-
- int plain = QL_NODE_IS_PLAIN(iter->current);
- if (!iter->zi) {
- /* If !zi, use current index. */
- quicklistDecompressNodeForUse(iter->quicklist, iter->current);
- if (unlikely(plain))
- iter->zi = iter->current->entry;
- else
- iter->zi = lpSeek(iter->current->entry, iter->offset);
- } else if (unlikely(plain)) {
- iter->zi = NULL;
- } else {
- /* else, use existing iterator offset and get prev/next as necessary. */
- if (iter->direction == AL_START_HEAD) {
- nextFn = lpNext;
- offset_update = 1;
- } else if (iter->direction == AL_START_TAIL) {
- nextFn = lpPrev;
- offset_update = -1;
- }
- iter->zi = nextFn(iter->current->entry, iter->zi);
- iter->offset += offset_update;
- }
-
- entry->zi = iter->zi;
- entry->offset = iter->offset;
-
- if (iter->zi) {
- if (unlikely(plain)) {
- entry->value = entry->node->entry;
- entry->sz = entry->node->sz;
- return 1;
- }
- /* Populate value from existing listpack position */
- unsigned int sz = 0;
- entry->value = lpGetValue(entry->zi, &sz, &entry->longval);
- entry->sz = sz;
- return 1;
- } else {
- /* We ran out of listpack entries.
- * Pick next node, update offset, then re-run retrieval. */
- quicklistCompress(iter->quicklist, iter->current);
- if (iter->direction == AL_START_HEAD) {
- /* Forward traversal */
- D("Jumping to start of next node");
- iter->current = iter->current->next;
- iter->offset = 0;
- } else if (iter->direction == AL_START_TAIL) {
- /* Reverse traversal */
- D("Jumping to end of previous node");
- iter->current = iter->current->prev;
- iter->offset = -1;
- }
- iter->zi = NULL;
- return quicklistNext(iter, entry);
- }
-}
-
-/* Sets the direction of a quicklist iterator. */
-void quicklistSetDirection(quicklistIter *iter, int direction) {
- iter->direction = direction;
-}
-
-/* Duplicate the quicklist.
- * On success a copy of the original quicklist is returned.
- *
- * The original quicklist both on success or error is never modified.
- *
- * Returns newly allocated quicklist. */
-quicklist *quicklistDup(quicklist *orig) {
- quicklist *copy;
-
- copy = quicklistNew(orig->fill, orig->compress);
-
- for (quicklistNode *current = orig->head; current;
- current = current->next) {
- quicklistNode *node = quicklistCreateNode(copy);
-
- if (current->encoding == QUICKLIST_NODE_ENCODING_LZF) {
- quicklistLZF *lzf = (quicklistLZF *)current->entry;
- size_t lzf_sz = sizeof(*lzf) + lzf->sz;
- node->entry = zmalloc(lzf_sz);
- memcpy(node->entry, current->entry, lzf_sz);
- copy->alloc_size += lzf_sz;
- } else if (current->encoding == QUICKLIST_NODE_ENCODING_RAW) {
- node->entry = zmalloc(current->sz);
- memcpy(node->entry, current->entry, current->sz);
- copy->alloc_size += current->sz;
- }
-
- node->count = current->count;
- node->sz = current->sz;
- node->encoding = current->encoding;
- node->container = current->container;
- copy->count += node->count;
-
- _quicklistInsertNodeAfter(copy, copy->tail, node);
- }
-
- /* copy->count must equal orig->count here */
- return copy;
-}
-
-/* Populate 'entry' with the element at the specified zero-based index
- * where 0 is the head, 1 is the element next to head
- * and so on. Negative integers are used in order to count
- * from the tail, -1 is the last element, -2 the penultimate
- * and so on. If the index is out of range 0 is returned.
- *
- * Returns 1 if iterator initialized at specific offset 'idx' and element found
- * Returns 0 if element not found */
-int quicklistInitIteratorEntryAtIdx(quicklistIter *iter, quicklist *quicklist,
- const long long idx, quicklistEntry *entry)
-{
- if (!quicklistInitIteratorAtIdx(iter, quicklist, AL_START_TAIL, idx))
- return 0;
- assert(quicklistNext(iter, entry));
- return 1;
-}
-
-static void quicklistRotatePlain(quicklist *quicklist) {
- quicklistNode *new_head = quicklist->tail;
- quicklistNode *new_tail = quicklist->tail->prev;
- quicklist->head->prev = new_head;
- new_tail->next = NULL;
- new_head->next = quicklist->head;
- new_head->prev = NULL;
- quicklist->head = new_head;
- quicklist->tail = new_tail;
-}
-
-/* Rotate quicklist by moving the tail element to the head. */
-void quicklistRotate(quicklist *quicklist) {
- if (quicklist->count <= 1)
- return;
-
- if (unlikely(QL_NODE_IS_PLAIN(quicklist->tail))) {
- quicklistRotatePlain(quicklist);
- return;
- }
-
- /* First, get the tail entry */
- unsigned char *p = lpSeek(quicklist->tail->entry, -1);
- unsigned char *value, *tmp;
- long long longval;
- unsigned int sz;
- char longstr[32] = {0};
- tmp = lpGetValue(p, &sz, &longval);
-
- /* If value found is NULL, then lpGet populated longval instead */
- if (!tmp) {
- /* Write the longval as a string so we can re-add it */
- sz = ll2string(longstr, sizeof(longstr), longval);
- value = (unsigned char *)longstr;
- } else if (quicklist->len == 1) {
- /* Copy buffer since there could be a memory overlap when move
- * entity from tail to head in the same listpack. */
- value = zmalloc(sz);
- memcpy(value, tmp, sz);
- } else {
- value = tmp;
- }
-
- /* Add tail entry to head (must happen before tail is deleted). */
- quicklistPushHead(quicklist, value, sz);
-
- /* If quicklist has only one node, the head listpack is also the
- * tail listpack and PushHead() could have reallocated our single listpack,
- * which would make our pre-existing 'p' unusable. */
- if (quicklist->len == 1) {
- p = lpSeek(quicklist->tail->entry, -1);
- }
-
- /* Remove tail entry. */
- quicklistDelIndex(quicklist, quicklist->tail, &p);
- if (value != (unsigned char*)longstr && value != tmp)
- zfree(value);
-}
-
-/* pop from quicklist and return result in 'data' ptr. Value of 'data'
- * is the return value of 'saver' function pointer if the data is NOT a number.
- *
- * If the quicklist element is a long long, then the return value is returned in
- * 'sval'.
- *
- * Return value of 0 means no elements available.
- * Return value of 1 means check 'data' and 'sval' for values.
- * If 'data' is set, use 'data' and 'sz'. Otherwise, use 'sval'. */
-int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
- size_t *sz, long long *sval,
- void *(*saver)(unsigned char *data, size_t sz)) {
- unsigned char *p;
- unsigned char *vstr;
- unsigned int vlen;
- long long vlong;
- int pos = (where == QUICKLIST_HEAD) ? 0 : -1;
-
- if (quicklist->count == 0)
- return 0;
-
- if (data)
- *data = NULL;
- if (sz)
- *sz = 0;
- if (sval)
- *sval = -123456789;
-
- quicklistNode *node;
- if (where == QUICKLIST_HEAD && quicklist->head) {
- node = quicklist->head;
- } else if (where == QUICKLIST_TAIL && quicklist->tail) {
- node = quicklist->tail;
- } else {
- return 0;
- }
-
- /* The head and tail should never be compressed */
- assert(node->encoding != QUICKLIST_NODE_ENCODING_LZF);
-
- if (unlikely(QL_NODE_IS_PLAIN(node))) {
- if (data)
- *data = saver(node->entry, node->sz);
- if (sz)
- *sz = node->sz;
- quicklistDelIndex(quicklist, node, NULL);
- return 1;
- }
-
- p = lpSeek(node->entry, pos);
- vstr = lpGetValue(p, &vlen, &vlong);
- if (vstr) {
- if (data)
- *data = saver(vstr, vlen);
- if (sz)
- *sz = vlen;
- } else {
- if (data)
- *data = NULL;
- if (sval)
- *sval = vlong;
- }
- quicklistDelIndex(quicklist, node, &p);
- return 1;
-}
-
-/* Return a malloc'd copy of data passed in */
-REDIS_STATIC void *_quicklistSaver(unsigned char *data, size_t sz) {
- unsigned char *vstr;
- if (data) {
- vstr = zmalloc(sz);
- memcpy(vstr, data, sz);
- return vstr;
- }
- return NULL;
-}
-
-/* Default pop function
- *
- * Returns malloc'd value from quicklist */
-int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
- size_t *sz, long long *slong) {
- unsigned char *vstr = NULL;
- size_t vlen = 0;
- long long vlong = 0;
- if (quicklist->count == 0)
- return 0;
- int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong,
- _quicklistSaver);
- if (data)
- *data = vstr;
- if (slong)
- *slong = vlong;
- if (sz)
- *sz = vlen;
- return ret;
-}
-
-/* Wrapper to allow argument-based switching between HEAD/TAIL pop */
-void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
- int where) {
- /* The head and tail should never be compressed (we don't attempt to decompress them) */
- if (quicklist->head)
- assert(quicklist->head->encoding != QUICKLIST_NODE_ENCODING_LZF);
- if (quicklist->tail)
- assert(quicklist->tail->encoding != QUICKLIST_NODE_ENCODING_LZF);
-
- if (where == QUICKLIST_HEAD) {
- quicklistPushHead(quicklist, value, sz);
- } else if (where == QUICKLIST_TAIL) {
- quicklistPushTail(quicklist, value, sz);
- }
-}
-
-/* Print info of quicklist which is used in debugCommand. */
-void quicklistRepr(unsigned char *ql, int full) {
- int i = 0;
- quicklist *quicklist = (struct quicklist*) ql;
- printf("{count : %ld}\n", quicklist->count);
- printf("{len : %ld}\n", quicklist->len);
- printf("{fill : %d}\n", quicklist->fill);
- printf("{compress : %d}\n", quicklist->compress);
- printf("{bookmark_count : %d}\n", quicklist->bookmark_count);
- quicklistNode* node = quicklist->head;
-
- while(node != NULL) {
- printf("{quicklist node(%d)\n", i++);
- printf("{container : %s, encoding: %s, size: %zu, count: %d, recompress: %d, attempted_compress: %d}\n",
- QL_NODE_IS_PLAIN(node) ? "PLAIN": "PACKED",
- (node->encoding == QUICKLIST_NODE_ENCODING_RAW) ? "RAW": "LZF",
- node->sz,
- node->count,
- node->recompress,
- node->attempted_compress);
-
- if (full) {
- quicklistDecompressNode(quicklist, node);
- if (node->container == QUICKLIST_NODE_CONTAINER_PACKED) {
- printf("{ listpack:\n");
- lpRepr(node->entry);
- printf("}\n");
-
- } else if (QL_NODE_IS_PLAIN(node)) {
- printf("{ entry : %s }\n", node->entry);
- }
- printf("}\n");
- quicklistRecompressOnly(quicklist, node);
- }
- node = node->next;
- }
-}
-
-/* Create or update a bookmark in the list which will be updated to the next node
- * automatically when the one referenced gets deleted.
- * Returns 1 on success (creation of new bookmark or override of an existing one).
- * Returns 0 on failure (reached the maximum supported number of bookmarks).
- * NOTE: use short simple names, so that string compare on find is quick.
- * NOTE: bookmark creation may re-allocate the quicklist, so the input pointer
- may change and it's the caller responsibility to update the reference.
- */
-int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) {
- quicklist *ql = *ql_ref;
- if (ql->bookmark_count >= QL_MAX_BM)
- return 0;
- quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
- if (bm) {
- bm->node = node;
- return 1;
- }
- size_t new_size, old_size;
- ql = zrealloc_usable(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark),
- &new_size, &old_size);
- *ql_ref = ql;
- ql->bookmarks[ql->bookmark_count].node = node;
- size_t name_sz;
- ql->bookmarks[ql->bookmark_count].name = zstrdup_usable(name, &name_sz);
- ql->bookmark_count++;
- quicklistUpdateAllocSize(ql, new_size + name_sz, old_size);
- return 1;
-}
-
-/* Find the quicklist node referenced by a named bookmark.
- * When the bookmarked node is deleted the bookmark is updated to the next node,
- * and if that's the last node, the bookmark is deleted (so find returns NULL). */
-quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) {
- quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
- if (!bm) return NULL;
- return bm->node;
-}
-
-/* Delete a named bookmark.
- * returns 0 if bookmark was not found, and 1 if deleted.
- * Note that the bookmark memory is not freed yet, and is kept for future use. */
-int quicklistBookmarkDelete(quicklist *ql, const char *name) {
- quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
- if (!bm)
- return 0;
- _quicklistBookmarkDelete(ql, bm);
- return 1;
-}
-
-quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) {
- unsigned i;
- for (i=0; i<ql->bookmark_count; i++) {
- if (!strcmp(ql->bookmarks[i].name, name)) {
- return &ql->bookmarks[i];
- }
- }
- return NULL;
-}
-
-quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) {
- unsigned i;
- for (i=0; i<ql->bookmark_count; i++) {
- if (ql->bookmarks[i].node == node) {
- return &ql->bookmarks[i];
- }
- }
- return NULL;
-}
-
-void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) {
- int index = bm - ql->bookmarks;
- size_t name_sz;
- zfree_usable(bm->name, &name_sz);
- ql->bookmark_count--;
- ql->alloc_size -= name_sz;
- memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm));
- /* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance,
- * it may be re-used later (a call to realloc may NOP). */
-}
-
-void quicklistBookmarksClear(quicklist *ql) {
- size_t name_sz;
- while (ql->bookmark_count) {
- zfree_usable(ql->bookmarks[--ql->bookmark_count].name, &name_sz);
- ql->alloc_size -= name_sz;
- }
- /* NOTE: We do not shrink (realloc) the quick list. main use case for this
- * function is just before releasing the allocation. */
-}
-
-/* The rest of this file is test cases and test helpers. */
-#ifdef REDIS_TEST
-#include <stdint.h>
-#include <sys/time.h>
-#include "testhelp.h"
-#include <stdlib.h>
-
-#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
-
-#define ERROR \
- do { \
- printf("\tERROR!\n"); \
- err++; \
- } while (0)
-
-#define ERR(x, ...) \
- do { \
- printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \
- printf("ERROR! " x "\n", __VA_ARGS__); \
- err++; \
- } while (0)
-
-#define TEST(name) printf("test — %s\n", name);
-#define TEST_DESC(name, ...) printf("test — " name "\n", __VA_ARGS__);
-
-#define QL_TEST_VERBOSE 0
-
-#define UNUSED(x) (void)(x)
-static void ql_info(quicklist *ql) {
-#if QL_TEST_VERBOSE
- printf("Container length: %lu\n", ql->len);
- printf("Container size: %lu\n", ql->count);
- if (ql->head)
- printf("\t(zsize head: %lu)\n", lpLength(ql->head->entry));
- if (ql->tail)
- printf("\t(zsize tail: %lu)\n", lpLength(ql->tail->entry));
- printf("\n");
-#else
- UNUSED(ql);
-#endif
-}
-
-/* Return the UNIX time in microseconds */
-static long long ustime(void) {
- struct timeval tv;
- long long ust;
-
- gettimeofday(&tv, NULL);
- ust = ((long long)tv.tv_sec) * 1000000;
- ust += tv.tv_usec;
- return ust;
-}
-
-/* Return the UNIX time in milliseconds */
-static long long mstime(void) { return ustime() / 1000; }
-
-/* Iterate over an entire quicklist.
- * Print the list if 'print' == 1.
- *
- * Returns physical count of elements found by iterating over the list. */
-static int _itrprintr(quicklist *ql, int print, int forward) {
- quicklistIter iter;
- quicklistEntry entry;
- quicklistInitIterator(&iter, ql, forward ? AL_START_HEAD : AL_START_TAIL);
- int i = 0;
- int p = 0;
- quicklistNode *prev = NULL;
- while (quicklistNext(&iter, &entry)) {
- if (entry.node != prev) {
- /* Count the number of list nodes too */
- p++;
- prev = entry.node;
- }
- if (print) {
- int size = (entry.sz > (1<<20)) ? 1<<20 : entry.sz;
- printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, size,
- (char *)entry.value, entry.longval);
- }
- i++;
- }
- quicklistResetIterator(&iter);
- return i;
-}
-static int itrprintr(quicklist *ql, int print) {
- return _itrprintr(ql, print, 1);
-}
-
-static int itrprintr_rev(quicklist *ql, int print) {
- return _itrprintr(ql, print, 0);
-}
-
-#define ql_verify(a, b, c, d, e) \
- do { \
- err += _ql_verify((a), (b), (c), (d), (e)); \
- } while (0)
-
-static int _ql_verify_compress(quicklist *ql) {
- int errors = 0;
- if (quicklistAllowsCompression(ql)) {
- quicklistNode *node = ql->head;
- unsigned int low_raw = ql->compress;
- unsigned int high_raw = ql->len - ql->compress;
-
- for (unsigned int at = 0; at < ql->len; at++, node = node->next) {
- if (node && (at < low_raw || at >= high_raw)) {
- if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
- yell("Incorrect compression: node %d is "
- "compressed at depth %d ((%u, %u); total "
- "nodes: %lu; size: %zu; recompress: %d)",
- at, ql->compress, low_raw, high_raw, ql->len, node->sz,
- node->recompress);
- errors++;
- }
- } else {
- if (node->encoding != QUICKLIST_NODE_ENCODING_LZF &&
- !node->attempted_compress) {
- yell("Incorrect non-compression: node %d is NOT "
- "compressed at depth %d ((%u, %u); total "
- "nodes: %lu; size: %zu; recompress: %d; attempted: %d)",
- at, ql->compress, low_raw, high_raw, ql->len, node->sz,
- node->recompress, node->attempted_compress);
- errors++;
- }
- }
- }
- }
- return errors;
-}
-
-static int _ql_verify_alloc_size(quicklist *ql) {
- int errors = 0;
- size_t alloc_size = zmalloc_usable_size(ql);
-
- quicklistNode* node = ql->head;
- while (node != NULL) {
- alloc_size += zmalloc_usable_size(node);
- if (node->encoding == QUICKLIST_NODE_ENCODING_LZF) {
- quicklistLZF *lzf = (quicklistLZF *)node->entry;
- alloc_size += sizeof(*lzf) + lzf->sz;
- } else {
- alloc_size += node->sz;
- }
- node = node->next;
- }
-
- for (unsigned i = 0; i < ql->bookmark_count; i++) {
- alloc_size += zmalloc_usable_size(ql->bookmarks[i].name);
- }
-
- if (ql->alloc_size != alloc_size) {
- yell("quicklist alloc_size wrong: expected %zu, got %zu", alloc_size, ql->alloc_size);
- errors++;
- }
-
- return errors;
-}
-
-/* Verify list metadata matches physical list contents. */
-static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
- uint32_t head_count, uint32_t tail_count) {
- int errors = 0;
-
- ql_info(ql);
- if (len != ql->len) {
- yell("quicklist length wrong: expected %d, got %lu", len, ql->len);
- errors++;
- }
-
- if (count != ql->count) {
- yell("quicklist count wrong: expected %d, got %lu", count, ql->count);
- errors++;
- }
-
- int loopr = itrprintr(ql, 0);
- if (loopr != (int)ql->count) {
- yell("quicklist cached count not match actual count: expected %lu, got "
- "%d",
- ql->count, loopr);
- errors++;
- }
-
- int rloopr = itrprintr_rev(ql, 0);
- if (loopr != rloopr) {
- yell("quicklist has different forward count than reverse count! "
- "Forward count is %d, reverse count is %d.",
- loopr, rloopr);
- errors++;
- }
-
- if (ql->len == 0 && !errors) {
- return errors;
- }
-
- if (ql->head && head_count != ql->head->count &&
- head_count != lpLength(ql->head->entry)) {
- yell("quicklist head count wrong: expected %d, "
- "got cached %d vs. actual %lu",
- head_count, ql->head->count, lpLength(ql->head->entry));
- errors++;
- }
-
- if (ql->tail && tail_count != ql->tail->count &&
- tail_count != lpLength(ql->tail->entry)) {
- yell("quicklist tail count wrong: expected %d, "
- "got cached %u vs. actual %lu",
- tail_count, ql->tail->count, lpLength(ql->tail->entry));
- errors++;
- }
-
- errors += _ql_verify_alloc_size(ql);
- errors += _ql_verify_compress(ql);
- return errors;
-}
-
-/* Reset iterator and verify compress correctly. */
-static void ql_reset_iterator(quicklistIter *iter) {
- quicklist *ql = NULL;
- if (iter) ql = iter->quicklist;
- quicklistResetIterator(iter);
- if (ql) assert(!_ql_verify_compress(ql));
-}
-
-/* Generate new string concatenating integer i against string 'prefix' */
-static char *genstr(char *prefix, int i) {
- static char result[64] = {0};
- snprintf(result, sizeof(result), "%s%d", prefix, i);
- return result;
-}
-
-static void randstring(unsigned char *target, size_t sz) {
- size_t p = 0;
- int minval, maxval;
- switch(rand() % 3) {
- case 0:
- minval = 'a';
- maxval = 'z';
- break;
- case 1:
- minval = '0';
- maxval = '9';
- break;
- case 2:
- minval = 'A';
- maxval = 'Z';
- break;
- default:
- assert(NULL);
- }
-
- while(p < sz)
- target[p++] = minval+rand()%(maxval-minval+1);
-}
-
-/* main test, but callable from other files */
-int quicklistTest(int argc, char *argv[], int flags) {
- UNUSED(argc);
- UNUSED(argv);
-
- int accurate = (flags & REDIS_TEST_ACCURATE);
- unsigned int err = 0;
- int optimize_start =
- -(int)(sizeof(optimization_level) / sizeof(*optimization_level));
-
- printf("Starting optimization offset at: %d\n", optimize_start);
-
- int options[] = {0, 1, 2, 3, 4, 5, 6, 10};
- int fills[] = {-5, -4, -3, -2, -1, 0,
- 1, 2, 32, 66, 128, 999};
- size_t option_count = sizeof(options) / sizeof(*options);
- int fill_count = (int)(sizeof(fills) / sizeof(*fills));
- long long runtime[option_count];
-
- for (int _i = 0; _i < (int)option_count; _i++) {
- printf("Testing Compression option %d\n", options[_i]);
- long long start = mstime();
- quicklistIter iter;
-
- TEST("create list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("add to tail of empty list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushTail(ql, "hello", 6);
- /* 1 for head and 1 for tail because 1 node = head = tail */
- ql_verify(ql, 1, 1, 1, 1);
- quicklistRelease(ql);
- }
-
- TEST("add to head of empty list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushHead(ql, "hello", 6);
- /* 1 for head and 1 for tail because 1 node = head = tail */
- ql_verify(ql, 1, 1, 1, 1);
- quicklistRelease(ql);
- }
-
- TEST_DESC("add to tail 5x at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 5; i++)
- quicklistPushTail(ql, genstr("hello", i), 32);
- if (ql->count != 5)
- ERROR;
- if (fills[f] == 32)
- ql_verify(ql, 1, 5, 5, 5);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("add to head 5x at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 5; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- if (ql->count != 5)
- ERROR;
- if (fills[f] == 32)
- ql_verify(ql, 1, 5, 5, 5);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("add to tail 500x at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i), 64);
- if (ql->count != 500)
- ERROR;
- if (fills[f] == 32)
- ql_verify(ql, 16, 500, 32, 20);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("add to head 500x at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- if (ql->count != 500)
- ERROR;
- if (fills[f] == 32)
- ql_verify(ql, 16, 500, 20, 32);
- quicklistRelease(ql);
- }
- }
-
- TEST("rotate empty") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistRotate(ql);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("Compression Plain node") {
- for (int f = 0; f < fill_count; f++) {
- size_t large_limit = (fills[f] < 0) ? quicklistNodeNegFillLimit(fills[f]) + 1 : SIZE_SAFETY_LIMIT + 1;
-
- char buf[large_limit];
- quicklist *ql = quicklistNew(fills[f], 1);
- for (int i = 0; i < 500; i++) {
- /* Set to 256 to allow the node to be triggered to compress,
- * if it is less than 48(nocompress), the test will be successful. */
- snprintf(buf, sizeof(buf), "hello%d", i);
- quicklistPushHead(ql, buf, large_limit);
- }
-
- quicklistIter iter;
- quicklistEntry entry;
- quicklistInitIterator(&iter, ql, AL_START_TAIL);
- int i = 0;
- while (quicklistNext(&iter, &entry)) {
- assert(QL_NODE_IS_PLAIN(entry.node));
- snprintf(buf, sizeof(buf), "hello%d", i);
- if (strcmp((char *)entry.value, buf))
- ERR("value [%s] didn't match [%s] at position %d",
- entry.value, buf, i);
- i++;
- }
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
- }
-
- TEST("NEXT plain node") {
- for (int f = 0; f < fill_count; f++) {
- size_t large_limit = (fills[f] < 0) ? quicklistNodeNegFillLimit(fills[f]) + 1 : SIZE_SAFETY_LIMIT + 1;
- quicklist *ql = quicklistNew(fills[f], options[_i]);
-
- char buf[large_limit];
- memcpy(buf, "plain", 5);
- quicklistPushHead(ql, buf, large_limit);
- quicklistPushHead(ql, buf, large_limit);
- quicklistPushHead(ql, "packed3", 7);
- quicklistPushHead(ql, "packed4", 7);
- quicklistPushHead(ql, buf, large_limit);
-
- quicklistEntry entry;
- quicklistIter iter;
- quicklistInitIterator(&iter, ql, AL_START_TAIL);
-
- while(quicklistNext(&iter, &entry) != 0) {
- if (QL_NODE_IS_PLAIN(entry.node))
- assert(!memcmp(entry.value, "plain", 5));
- else
- assert(!memcmp(entry.value, "packed", 6));
- }
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
- }
-
- TEST("rotate plain node ") {
- for (int f = 0; f < fill_count; f++) {
- size_t large_limit = (fills[f] < 0) ? quicklistNodeNegFillLimit(fills[f]) + 1 : SIZE_SAFETY_LIMIT + 1;
-
- unsigned char *data = NULL;
- size_t sz;
- long long lv;
- int i =0;
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- char buf[large_limit];
- memcpy(buf, "hello1", 6);
- quicklistPushHead(ql, buf, large_limit);
- memcpy(buf, "hello4", 6);
- quicklistPushHead(ql, buf, large_limit);
- memcpy(buf, "hello3", 6);
- quicklistPushHead(ql, buf, large_limit);
- memcpy(buf, "hello2", 6);
- quicklistPushHead(ql, buf, large_limit);
- quicklistRotate(ql);
-
- for(i = 1 ; i < 5; i++) {
- assert(QL_NODE_IS_PLAIN(ql->tail));
- quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
- int temp_char = data[5];
- zfree(data);
- assert(temp_char == ('0' + i));
- }
-
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
- }
-
- TEST("rotate one val once") {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- quicklistPushHead(ql, "hello", 6);
- quicklistRotate(ql);
- /* Ignore compression verify because listpack is
- * too small to compress. */
- ql_verify(ql, 1, 1, 1, 1);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("rotate 500 val 5000 times at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- quicklistPushHead(ql, "900", 3);
- quicklistPushHead(ql, "7000", 4);
- quicklistPushHead(ql, "-1200", 5);
- quicklistPushHead(ql, "42", 2);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 64);
- ql_info(ql);
- for (int i = 0; i < 5000; i++) {
- ql_info(ql);
- quicklistRotate(ql);
- }
- if (fills[f] == 1)
- ql_verify(ql, 504, 504, 1, 1);
- else if (fills[f] == 2)
- ql_verify(ql, 252, 504, 2, 2);
- else if (fills[f] == 32)
- ql_verify(ql, 16, 504, 32, 24);
- quicklistRelease(ql);
- }
- }
-
- TEST("pop empty") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("pop 1 string from 1") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- char *populate = genstr("hello", 331);
- quicklistPushHead(ql, populate, 32);
- unsigned char *data;
- size_t sz;
- long long lv;
- ql_info(ql);
- assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv));
- assert(data != NULL);
- assert(sz == 32);
- if (strcmp(populate, (char *)data)) {
- int size = sz;
- ERR("Pop'd value (%.*s) didn't equal original value (%s)", size,
- data, populate);
- }
- zfree(data);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("pop head 1 number from 1") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushHead(ql, "55513", 5);
- unsigned char *data;
- size_t sz;
- long long lv;
- ql_info(ql);
- assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv));
- assert(data == NULL);
- assert(lv == 55513);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("pop head 500 from 500") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- ql_info(ql);
- for (int i = 0; i < 500; i++) {
- unsigned char *data;
- size_t sz;
- long long lv;
- int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
- assert(ret == 1);
- assert(data != NULL);
- assert(sz == 32);
- if (strcmp(genstr("hello", 499 - i), (char *)data)) {
- int size = sz;
- ERR("Pop'd value (%.*s) didn't equal original value (%s)",
- size, data, genstr("hello", 499 - i));
- }
- zfree(data);
- }
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("pop head 5000 from 500") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- for (int i = 0; i < 5000; i++) {
- unsigned char *data;
- size_t sz;
- long long lv;
- int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv);
- if (i < 500) {
- assert(ret == 1);
- assert(data != NULL);
- assert(sz == 32);
- if (strcmp(genstr("hello", 499 - i), (char *)data)) {
- int size = sz;
- ERR("Pop'd value (%.*s) didn't equal original value "
- "(%s)",
- size, data, genstr("hello", 499 - i));
- }
- zfree(data);
- } else {
- assert(ret == 0);
- }
- }
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("iterate forward over 500 list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- quicklistIter iter_local;
- quicklistEntry entry;
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- int i = 499, count = 0;
- while (quicklistNext(&iter_local, &entry)) {
- char *h = genstr("hello", i);
- if (strcmp((char *)entry.value, h))
- ERR("value [%s] didn't match [%s] at position %d",
- entry.value, h, i);
- i--;
- count++;
- }
- if (count != 500)
- ERR("Didn't iterate over exactly 500 elements (%d)", i);
- ql_verify(ql, 16, 500, 20, 32);
- ql_reset_iterator(&iter_local);
- quicklistRelease(ql);
- }
-
- TEST("iterate reverse over 500 list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- quicklistIter iter_local;
- quicklistEntry entry;
- quicklistInitIterator(&iter_local, ql, AL_START_TAIL);
- int i = 0;
- while (quicklistNext(&iter_local, &entry)) {
- char *h = genstr("hello", i);
- if (strcmp((char *)entry.value, h))
- ERR("value [%s] didn't match [%s] at position %d",
- entry.value, h, i);
- i++;
- }
- if (i != 500)
- ERR("Didn't iterate over exactly 500 elements (%d)", i);
- ql_verify(ql, 16, 500, 20, 32);
- ql_reset_iterator(&iter_local);
- quicklistRelease(ql);
- }
-
- TEST("insert after 1 element") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushHead(ql, "hello", 6);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- quicklistInsertAfter(&iter, &entry, "abc", 4);
- ql_reset_iterator(&iter);
- ql_verify(ql, 1, 2, 2, 2);
-
- /* verify results */
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- int sz = entry.sz;
- if (strncmp((char *)entry.value, "hello", 5)) {
- ERR("Value 0 didn't match, instead got: %.*s", sz,
- entry.value);
- }
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry);
- sz = entry.sz;
- if (strncmp((char *)entry.value, "abc", 3)) {
- ERR("Value 1 didn't match, instead got: %.*s", sz,
- entry.value);
- }
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST("insert before 1 element") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushHead(ql, "hello", 6);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- quicklistInsertBefore(&iter, &entry, "abc", 4);
- ql_reset_iterator(&iter);
- ql_verify(ql, 1, 2, 2, 2);
-
- /* verify results */
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- int sz = entry.sz;
- if (strncmp((char *)entry.value, "abc", 3)) {
- ERR("Value 0 didn't match, instead got: %.*s", sz,
- entry.value);
- }
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry);
- sz = entry.sz;
- if (strncmp((char *)entry.value, "hello", 5)) {
- ERR("Value 1 didn't match, instead got: %.*s", sz,
- entry.value);
- }
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST("insert head while head node is full") {
- quicklist *ql = quicklistNew(4, options[_i]);
- for (int i = 0; i < 10; i++)
- quicklistPushTail(ql, genstr("hello", i), 6);
- quicklistSetFill(ql, -1);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, -10, &entry);
- char buf[4096] = {0};
- quicklistInsertBefore(&iter, &entry, buf, 4096);
- ql_reset_iterator(&iter);
- ql_verify(ql, 4, 11, 1, 2);
- quicklistRelease(ql);
- }
-
- TEST("insert tail while tail node is full") {
- quicklist *ql = quicklistNew(4, options[_i]);
- for (int i = 0; i < 10; i++)
- quicklistPushHead(ql, genstr("hello", i), 6);
- quicklistSetFill(ql, -1);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry);
- char buf[4096] = {0};
- quicklistInsertAfter(&iter, &entry, buf, 4096);
- ql_reset_iterator(&iter);
- ql_verify(ql, 4, 11, 2, 1);
- quicklistRelease(ql);
- }
-
- TEST_DESC("insert once in elements while iterating at compress %d",
- options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- quicklistPushTail(ql, "abc", 3);
- quicklistSetFill(ql, 1);
- quicklistPushTail(ql, "def", 3); /* force to unique node */
- quicklistSetFill(ql, f);
- quicklistPushTail(ql, "bob", 3); /* force to reset for +3 */
- quicklistPushTail(ql, "foo", 3);
- quicklistPushTail(ql, "zoo", 3);
-
- itrprintr(ql, 0);
- /* insert "bar" before "bob" while iterating over list. */
- quicklistIter iter_local;
- quicklistEntry entry;
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- while (quicklistNext(&iter_local, &entry)) {
- if (!strncmp((char *)entry.value, "bob", 3)) {
- /* Insert as fill = 1 so it spills into new node. */
- quicklistInsertBefore(&iter_local, &entry, "bar", 3);
- break; /* didn't we fix insert-while-iterating? */
- }
- }
- ql_reset_iterator(&iter_local);
- itrprintr(ql, 0);
-
- /* verify results */
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- int sz = entry.sz;
-
- if (strncmp((char *)entry.value, "abc", 3))
- ERR("Value 0 didn't match, instead got: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry);
- if (strncmp((char *)entry.value, "def", 3))
- ERR("Value 1 didn't match, instead got: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 2, &entry);
- if (strncmp((char *)entry.value, "bar", 3))
- ERR("Value 2 didn't match, instead got: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 3, &entry);
- if (strncmp((char *)entry.value, "bob", 3))
- ERR("Value 3 didn't match, instead got: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 4, &entry);
- if (strncmp((char *)entry.value, "foo", 3))
- ERR("Value 4 didn't match, instead got: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 5, &entry);
- if (strncmp((char *)entry.value, "zoo", 3))
- ERR("Value 5 didn't match, instead got: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("insert [before] 250 new in middle of 500 elements at compress %d",
- options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i), 32);
- for (int i = 0; i < 250; i++) {
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 250, &entry);
- quicklistInsertBefore(&iter, &entry, genstr("abc", i), 32);
- ql_reset_iterator(&iter);
- }
- if (fills[f] == 32)
- ql_verify(ql, 25, 750, 32, 20);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("insert [after] 250 new in middle of 500 elements at compress %d",
- options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- for (int i = 0; i < 250; i++) {
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 250, &entry);
- quicklistInsertAfter(&iter, &entry, genstr("abc", i), 32);
- ql_reset_iterator(&iter);
- }
-
- if (ql->count != 750)
- ERR("List size not 750, but rather %ld", ql->count);
-
- if (fills[f] == 32)
- ql_verify(ql, 26, 750, 20, 32);
- quicklistRelease(ql);
- }
- }
-
- TEST("duplicate empty list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- ql_verify(ql, 0, 0, 0, 0);
- quicklist *copy = quicklistDup(ql);
- ql_verify(copy, 0, 0, 0, 0);
- quicklistRelease(ql);
- quicklistRelease(copy);
- }
-
- TEST("duplicate list of 1 element") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushHead(ql, genstr("hello", 3), 32);
- ql_verify(ql, 1, 1, 1, 1);
- quicklist *copy = quicklistDup(ql);
- ql_verify(copy, 1, 1, 1, 1);
- quicklistRelease(ql);
- quicklistRelease(copy);
- }
-
- TEST("duplicate list of 500") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- ql_verify(ql, 16, 500, 20, 32);
-
- quicklist *copy = quicklistDup(ql);
- ql_verify(copy, 16, 500, 20, 32);
- quicklistRelease(ql);
- quicklistRelease(copy);
- }
-
- for (int f = 0; f < fill_count; f++) {
- TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f,
- options[_i]) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry);
- if (strcmp((char *)entry.value, "hello2") != 0)
- ERR("Value: %s", entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 200, &entry);
- if (strcmp((char *)entry.value, "hello201") != 0)
- ERR("Value: %s", entry.value);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d",
- fills[f], options[_i]) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry);
- if (strcmp((char *)entry.value, "hello500") != 0)
- ERR("Value: %s", entry.value);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -2, &entry);
- if (strcmp((char *)entry.value, "hello499") != 0)
- ERR("Value: %s", entry.value);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST_DESC("index -100 from 500 list at fill %d at compress %d",
- fills[f], options[_i]) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, -100, &entry);
- if (strcmp((char *)entry.value, "hello401") != 0)
- ERR("Value: %s", entry.value);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST_DESC("index too big +1 from 50 list at fill %d at compress %d",
- fills[f], options[_i]) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- for (int i = 0; i < 50; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- quicklistEntry entry;
- int sz = entry.sz;
- if (quicklistInitIteratorEntryAtIdx(&iter, ql, 50, &entry))
- ERR("Index found at 50 with 50 list: %.*s", sz,
- entry.value);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
- }
-
- TEST("delete range empty list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistDelRange(ql, 5, 20);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("delete range of entire node in list of one node") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- for (int i = 0; i < 32; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- ql_verify(ql, 1, 32, 32, 32);
- quicklistDelRange(ql, 0, 32);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("delete range of entire node with overflow counts") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- for (int i = 0; i < 32; i++)
- quicklistPushHead(ql, genstr("hello", i), 32);
- ql_verify(ql, 1, 32, 32, 32);
- quicklistDelRange(ql, 0, 128);
- ql_verify(ql, 0, 0, 0, 0);
- quicklistRelease(ql);
- }
-
- TEST("delete middle 100 of 500 list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- ql_verify(ql, 16, 500, 32, 20);
- quicklistDelRange(ql, 200, 100);
- ql_verify(ql, 14, 400, 32, 20);
- quicklistRelease(ql);
- }
-
- TEST("delete less than fill but across nodes") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- ql_verify(ql, 16, 500, 32, 20);
- quicklistDelRange(ql, 60, 10);
- ql_verify(ql, 16, 490, 32, 20);
- quicklistRelease(ql);
- }
-
- TEST("delete negative 1 from 500 list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- ql_verify(ql, 16, 500, 32, 20);
- quicklistDelRange(ql, -1, 1);
- ql_verify(ql, 16, 499, 32, 19);
- quicklistRelease(ql);
- }
-
- TEST("delete negative 1 from 500 list with overflow counts") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- ql_verify(ql, 16, 500, 32, 20);
- quicklistDelRange(ql, -1, 128);
- ql_verify(ql, 16, 499, 32, 19);
- quicklistRelease(ql);
- }
-
- TEST("delete negative 100 from 500 list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 500; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- quicklistDelRange(ql, -100, 100);
- ql_verify(ql, 13, 400, 32, 16);
- quicklistRelease(ql);
- }
-
- TEST("delete -10 count 5 from 50 list") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- for (int i = 0; i < 50; i++)
- quicklistPushTail(ql, genstr("hello", i + 1), 32);
- ql_verify(ql, 2, 50, 32, 18);
- quicklistDelRange(ql, -10, 5);
- ql_verify(ql, 2, 45, 32, 13);
- quicklistRelease(ql);
- }
-
- TEST("numbers only list read") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushTail(ql, "1111", 4);
- quicklistPushTail(ql, "2222", 4);
- quicklistPushTail(ql, "3333", 4);
- quicklistPushTail(ql, "4444", 4);
- ql_verify(ql, 1, 4, 4, 4);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- if (entry.longval != 1111)
- ERR("Not 1111, %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry);
- if (entry.longval != 2222)
- ERR("Not 2222, %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 2, &entry);
- if (entry.longval != 3333)
- ERR("Not 3333, %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 3, &entry);
- if (entry.longval != 4444)
- ERR("Not 4444, %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- if (quicklistInitIteratorEntryAtIdx(&iter, ql, 4, &entry))
- ERR("Index past elements: %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry);
- if (entry.longval != 4444)
- ERR("Not 4444 (reverse), %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -2, &entry);
- if (entry.longval != 3333)
- ERR("Not 3333 (reverse), %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -3, &entry);
- if (entry.longval != 2222)
- ERR("Not 2222 (reverse), %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -4, &entry);
- if (entry.longval != 1111)
- ERR("Not 1111 (reverse), %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- if (quicklistInitIteratorEntryAtIdx(&iter, ql, -5, &entry))
- ERR("Index past elements (reverse), %lld", entry.longval);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST("numbers larger list read") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistSetFill(ql, 32);
- char num[32];
- long long nums[5000];
- for (int i = 0; i < 5000; i++) {
- nums[i] = -5157318210846258176 + i;
- int sz = ll2string(num, sizeof(num), nums[i]);
- quicklistPushTail(ql, num, sz);
- }
- quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20);
- quicklistEntry entry;
- for (int i = 0; i < 5000; i++) {
- quicklistInitIteratorEntryAtIdx(&iter, ql, i, &entry);
- if (entry.longval != nums[i])
- ERR("[%d] Not longval %lld but rather %lld", i, nums[i],
- entry.longval);
- entry.longval = 0xdeadbeef;
- ql_reset_iterator(&iter);
- }
- quicklistInitIteratorEntryAtIdx(&iter, ql, 5000, &entry);
- if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20))
- ERR("String val not match: %s", entry.value);
- ql_verify(ql, 157, 5001, 32, 9);
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
-
- TEST("numbers larger list read B") {
- quicklist *ql = quicklistNew(-2, options[_i]);
- quicklistPushTail(ql, "99", 2);
- quicklistPushTail(ql, "98", 2);
- quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20);
- quicklistPushTail(ql, "96", 2);
- quicklistPushTail(ql, "95", 2);
- quicklistReplaceAtIndex(ql, 1, "foo", 3);
- quicklistReplaceAtIndex(ql, -1, "bar", 3);
- quicklistRelease(ql);
- }
-
- TEST_DESC("lrem test at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- char *words[] = {"abc", "foo", "bar", "foobar", "foobared",
- "zap", "bar", "test", "foo"};
- char *result[] = {"abc", "foo", "foobar", "foobared",
- "zap", "test", "foo"};
- char *resultB[] = {"abc", "foo", "foobar",
- "foobared", "zap", "test"};
- for (int i = 0; i < 9; i++)
- quicklistPushTail(ql, words[i], strlen(words[i]));
-
- /* lrem 0 bar */
- quicklistIter iter_local;
- quicklistEntry entry;
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- int i = 0;
- while (quicklistNext(&iter_local, &entry)) {
- if (quicklistCompare(&entry, (unsigned char *)"bar", 3,
- NULL, NULL)) {
- quicklistDelEntry(&iter_local, &entry);
- }
- i++;
- }
- ql_reset_iterator(&iter_local);
-
- /* check result of lrem 0 bar */
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- i = 0;
- while (quicklistNext(&iter_local, &entry)) {
- /* Result must be: abc, foo, foobar, foobared, zap, test,
- * foo */
- int sz = entry.sz;
- if (strncmp((char *)entry.value, result[i], entry.sz)) {
- ERR("No match at position %d, got %.*s instead of %s",
- i, sz, entry.value, result[i]);
- }
- i++;
- }
- ql_reset_iterator(&iter_local);
-
- quicklistPushTail(ql, "foo", 3);
-
- /* lrem -2 foo */
- quicklistInitIterator(&iter_local, ql, AL_START_TAIL);
- i = 0;
- int del = 2;
- while (quicklistNext(&iter_local, &entry)) {
- if (quicklistCompare(&entry, (unsigned char *)"foo", 3,
- NULL, NULL)) {
- quicklistDelEntry(&iter_local, &entry);
- del--;
- }
- if (!del)
- break;
- i++;
- }
- ql_reset_iterator(&iter_local);
-
- /* check result of lrem -2 foo */
- /* (we're ignoring the '2' part and still deleting all foo
- * because
- * we only have two foo) */
- quicklistInitIterator(&iter_local, ql, AL_START_TAIL);
- i = 0;
- size_t resB = sizeof(resultB) / sizeof(*resultB);
- while (quicklistNext(&iter_local, &entry)) {
- /* Result must be: abc, foo, foobar, foobared, zap, test,
- * foo */
- int sz = entry.sz;
- if (strncmp((char *)entry.value, resultB[resB - 1 - i],
- sz)) {
- ERR("No match at position %d, got %.*s instead of %s",
- i, sz, entry.value, resultB[resB - 1 - i]);
- }
- i++;
- }
-
- ql_reset_iterator(&iter_local);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("iterate reverse + delete at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- quicklistPushTail(ql, "abc", 3);
- quicklistPushTail(ql, "def", 3);
- quicklistPushTail(ql, "hij", 3);
- quicklistPushTail(ql, "jkl", 3);
- quicklistPushTail(ql, "oop", 3);
-
- quicklistEntry entry;
- quicklistIter iter_local;
- quicklistInitIterator(&iter_local, ql, AL_START_TAIL);
- int i = 0;
- while (quicklistNext(&iter_local, &entry)) {
- if (quicklistCompare(&entry, (unsigned char *)"hij", 3,
- NULL, NULL)) {
- quicklistDelEntry(&iter_local, &entry);
- }
- i++;
- }
- ql_reset_iterator(&iter_local);
-
- if (i != 5)
- ERR("Didn't iterate 5 times, iterated %d times.", i);
-
- /* Check results after deletion of "hij" */
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- i = 0;
- char *vals[] = {"abc", "def", "jkl", "oop"};
- while (quicklistNext(&iter_local, &entry)) {
- if (!quicklistCompare(&entry, (unsigned char *)vals[i],
- 3, NULL, NULL)) {
- ERR("Value at %d didn't match %s\n", i, vals[i]);
- }
- i++;
- }
- ql_reset_iterator(&iter_local);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("iterator at index test at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- char num[32];
- long long nums[5000];
- for (int i = 0; i < 760; i++) {
- nums[i] = -5157318210846258176 + i;
- int sz = ll2string(num, sizeof(num), nums[i]);
- quicklistPushTail(ql, num, sz);
- }
-
- quicklistEntry entry;
- quicklistIter iter_local;
- quicklistInitIteratorAtIdx(&iter_local, ql, AL_START_HEAD, 437);
- int i = 437;
- while (quicklistNext(&iter_local, &entry)) {
- if (entry.longval != nums[i])
- ERR("Expected %lld, but got %lld", entry.longval,
- nums[i]);
- i++;
- }
- ql_reset_iterator(&iter_local);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("ltrim test A at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- char num[32];
- long long nums[5000];
- for (int i = 0; i < 32; i++) {
- nums[i] = -5157318210846258176 + i;
- int sz = ll2string(num, sizeof(num), nums[i]);
- quicklistPushTail(ql, num, sz);
- }
- if (fills[f] == 32)
- ql_verify(ql, 1, 32, 32, 32);
- /* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */
- quicklistDelRange(ql, 0, 25);
- quicklistDelRange(ql, 0, 0);
- quicklistEntry entry;
- for (int i = 0; i < 7; i++) {
- quicklistInitIteratorEntryAtIdx(&iter, ql, i, &entry);
- if (entry.longval != nums[25 + i])
- ERR("Deleted invalid range! Expected %lld but got "
- "%lld",
- entry.longval, nums[25 + i]);
- ql_reset_iterator(&iter);
- }
- if (fills[f] == 32)
- ql_verify(ql, 1, 7, 7, 7);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("ltrim test B at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- /* Force-disable compression because our 33 sequential
- * integers don't compress and the check always fails. */
- quicklist *ql = quicklistNew(fills[f], QUICKLIST_NOCOMPRESS);
- char num[32];
- long long nums[5000];
- for (int i = 0; i < 33; i++) {
- nums[i] = i;
- int sz = ll2string(num, sizeof(num), nums[i]);
- quicklistPushTail(ql, num, sz);
- }
- if (fills[f] == 32)
- ql_verify(ql, 2, 33, 32, 1);
- /* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */
- quicklistDelRange(ql, 0, 5);
- quicklistDelRange(ql, -16, 16);
- if (fills[f] == 32)
- ql_verify(ql, 1, 12, 12, 12);
- quicklistEntry entry;
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- if (entry.longval != 5)
- ERR("A: longval not 5, but %lld", entry.longval);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry);
- if (entry.longval != 16)
- ERR("B! got instead: %lld", entry.longval);
- quicklistPushTail(ql, "bobobob", 7);
- ql_reset_iterator(&iter);
-
- quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry);
- int sz = entry.sz;
- if (strncmp((char *)entry.value, "bobobob", 7))
- ERR("Tail doesn't match bobobob, it's %.*s instead",
- sz, entry.value);
- ql_reset_iterator(&iter);
-
- for (int i = 0; i < 12; i++) {
- quicklistInitIteratorEntryAtIdx(&iter, ql, i, &entry);
- if (entry.longval != nums[5 + i])
- ERR("Deleted invalid range! Expected %lld but got "
- "%lld",
- entry.longval, nums[5 + i]);
- ql_reset_iterator(&iter);
- }
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("ltrim test C at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- char num[32];
- long long nums[5000];
- for (int i = 0; i < 33; i++) {
- nums[i] = -5157318210846258176 + i;
- int sz = ll2string(num, sizeof(num), nums[i]);
- quicklistPushTail(ql, num, sz);
- }
- if (fills[f] == 32)
- ql_verify(ql, 2, 33, 32, 1);
- /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */
- quicklistDelRange(ql, 0, 3);
- quicklistDelRange(ql, -29,
- 4000); /* make sure not loop forever */
- if (fills[f] == 32)
- ql_verify(ql, 1, 1, 1, 1);
- quicklistEntry entry;
- quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry);
- if (entry.longval != -5157318210846258173)
- ERROR;
- ql_reset_iterator(&iter);
- quicklistRelease(ql);
- }
- }
-
- TEST_DESC("ltrim test D at compress %d", options[_i]) {
- for (int f = 0; f < fill_count; f++) {
- quicklist *ql = quicklistNew(fills[f], options[_i]);
- char num[32];
- long long nums[5000];
- for (int i = 0; i < 33; i++) {
- nums[i] = -5157318210846258176 + i;
- int sz = ll2string(num, sizeof(num), nums[i]);
- quicklistPushTail(ql, num, sz);
- }
- if (fills[f] == 32)
- ql_verify(ql, 2, 33, 32, 1);
- quicklistDelRange(ql, -12, 3);
- if (ql->count != 30)
- ERR("Didn't delete exactly three elements! Count is: %lu",
- ql->count);
- quicklistRelease(ql);
- }
- }
-
- long long stop = mstime();
- runtime[_i] = stop - start;
- }
-
- /* Run a longer test of compression depth outside of primary test loop. */
- int list_sizes[] = {250, 251, 500, 999, 1000};
- long long start = mstime();
- int list_count = accurate ? (int)(sizeof(list_sizes) / sizeof(*list_sizes)) : 1;
- for (int list = 0; list < list_count; list++) {
- TEST_DESC("verify specific compression of interior nodes with %d list ",
- list_sizes[list]) {
- for (int f = 0; f < fill_count; f++) {
- for (int depth = 1; depth < 40; depth++) {
- /* skip over many redundant test cases */
- quicklist *ql = quicklistNew(fills[f], depth);
- for (int i = 0; i < list_sizes[list]; i++) {
- quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64);
- quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64);
- }
-
- for (int step = 0; step < 2; step++) {
- /* test remove node */
- if (step == 1) {
- for (int i = 0; i < list_sizes[list] / 2; i++) {
- unsigned char *data;
- assert(quicklistPop(ql, QUICKLIST_HEAD, &data,
- NULL, NULL));
- zfree(data);
- assert(quicklistPop(ql, QUICKLIST_TAIL, &data,
- NULL, NULL));
- zfree(data);
- }
- }
- quicklistNode *node = ql->head;
- unsigned int low_raw = ql->compress;
- unsigned int high_raw = ql->len - ql->compress;
-
- for (unsigned int at = 0; at < ql->len;
- at++, node = node->next) {
- if (at < low_raw || at >= high_raw) {
- if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
- ERR("Incorrect compression: node %d is "
- "compressed at depth %d ((%u, %u); total "
- "nodes: %lu; size: %zu)",
- at, depth, low_raw, high_raw, ql->len,
- node->sz);
- }
- } else {
- if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
- ERR("Incorrect non-compression: node %d is NOT "
- "compressed at depth %d ((%u, %u); total "
- "nodes: %lu; size: %zu; attempted: %d)",
- at, depth, low_raw, high_raw, ql->len,
- node->sz, node->attempted_compress);
- }
- }
- }
- }
-
- quicklistRelease(ql);
- }
- }
- }
- }
- long long stop = mstime();
-
- printf("\n");
- for (size_t i = 0; i < option_count; i++)
- printf("Test Loop %02d: %0.2f seconds.\n", options[i],
- (float)runtime[i] / 1000);
- printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000);
- printf("\n");
-
- TEST("bookmark get updated to next item") {
- quicklist *ql = quicklistNew(1, 0);
- quicklistPushTail(ql, "1", 1);
- quicklistPushTail(ql, "2", 1);
- quicklistPushTail(ql, "3", 1);
- quicklistPushTail(ql, "4", 1);
- quicklistPushTail(ql, "5", 1);
- assert(ql->len==5);
- /* add two bookmarks, one pointing to the node before the last. */
- assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next));
- assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev));
- /* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */
- assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev);
- assert(quicklistDelRange(ql, -2, 1));
- assert(quicklistBookmarkFind(ql, "_test") == ql->tail);
- /* delete the last node, and see that the bookmark was deleted. */
- assert(quicklistDelRange(ql, -1, 1));
- assert(quicklistBookmarkFind(ql, "_test") == NULL);
- /* test that other bookmarks aren't affected */
- assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next);
- assert(quicklistBookmarkFind(ql, "_missing") == NULL);
- assert(ql->len==3);
- quicklistBookmarksClear(ql); /* for coverage */
- assert(quicklistBookmarkFind(ql, "_dummy") == NULL);
- quicklistRelease(ql);
- }
-
- TEST("bookmark limit") {
- int i;
- quicklist *ql = quicklistNew(1, 0);
- quicklistPushHead(ql, "1", 1);
- for (i=0; i<QL_MAX_BM; i++)
- assert(quicklistBookmarkCreate(&ql, genstr("",i), ql->head));
- /* when all bookmarks are used, creation fails */
- assert(!quicklistBookmarkCreate(&ql, "_test", ql->head));
- /* delete one and see that we can now create another */
- assert(quicklistBookmarkDelete(ql, "0"));
- assert(quicklistBookmarkCreate(&ql, "_test", ql->head));
- /* delete one and see that the rest survive */
- assert(quicklistBookmarkDelete(ql, "_test"));
- for (i=1; i<QL_MAX_BM; i++)
- assert(quicklistBookmarkFind(ql, genstr("",i)) == ql->head);
- /* make sure the deleted ones are indeed gone */
- assert(!quicklistBookmarkFind(ql, "0"));
- assert(!quicklistBookmarkFind(ql, "_test"));
- quicklistRelease(ql);
- }
-
- TEST("quicklistCompare cached string2ll optimization") {
- quicklist *ql = quicklistNew(-2, 0);
-
- /* Create a list with mixed integer and string entries */
- quicklistPushTail(ql, "123", 3); /* integer as string */
- quicklistPushTail(ql, "456", 3); /* integer as string */
- quicklistPushTail(ql, "hello", 5); /* non-numeric string */
- quicklistPushTail(ql, "789", 3); /* integer as string */
- quicklistPushTail(ql, "world", 5); /* non-numeric string */
-
- quicklistEntry entry;
- quicklistIter iter_local;
-
- /* Test 1: NULL parameters should work without crashing */
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- assert(quicklistNext(&iter_local, &entry));
- assert(quicklistCompare(&entry, (unsigned char *)"123", 3, NULL, NULL) == 1);
- assert(quicklistCompare(&entry, (unsigned char *)"456", 3, NULL, NULL) == 0);
- ql_reset_iterator(&iter_local);
-
- /* Test 2: Caching with numeric strings */
- long long cached_val = 0;
- int cached_valid = 0;
-
- /* First comparison should cache the value */
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- assert(quicklistNext(&iter_local, &entry)); /* entry = "123" */
- assert(quicklistCompare(&entry, (unsigned char *)"123", 3, &cached_val, &cached_valid) == 1);
- assert(cached_valid == 1); /* Should be cached as valid */
- assert(cached_val == 123); /* Should have cached value */
-
- /* Second comparison with same search string should use cache */
- assert(quicklistNext(&iter_local, &entry)); /* entry = "456" */
- assert(quicklistCompare(&entry, (unsigned char *)"123", 3, &cached_val, &cached_valid) == 0);
- assert(cached_valid == 1); /* Cache should still be valid */
- assert(cached_val == 123); /* Cache value should be unchanged */
-
- /* Third comparison with same search string should use cache */
- assert(quicklistNext(&iter_local, &entry)); /* entry = "hello" (string) */
- assert(quicklistCompare(&entry, (unsigned char *)"123", 3, &cached_val, &cached_valid) == 0);
- assert(cached_valid == 1); /* Cache should still be valid */
- ql_reset_iterator(&iter_local);
-
- /* Test 3: Caching with non-numeric strings */
- cached_val = 0;
- cached_valid = 0;
-
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- assert(quicklistNext(&iter_local, &entry)); /* entry = "123" */
- assert(quicklistCompare(&entry, (unsigned char *)"abc", 3, &cached_val, &cached_valid) == 0);
- assert(cached_valid == -1); /* Should be cached as invalid */
-
- /* Second comparison with same non-numeric string should use cache */
- assert(quicklistNext(&iter_local, &entry)); /* entry = "456" */
- assert(quicklistCompare(&entry, (unsigned char *)"abc", 3, &cached_val, &cached_valid) == 0);
- assert(cached_valid == -1); /* Cache should still be invalid */
- ql_reset_iterator(&iter_local);
-
- /* Test 4: String entries should work correctly with both NULL and caching */
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- quicklistNext(&iter_local, &entry); /* skip "123" */
- quicklistNext(&iter_local, &entry); /* skip "456" */
- assert(quicklistNext(&iter_local, &entry)); /* entry = "hello" */
-
- /* String comparison with NULL parameters */
- assert(quicklistCompare(&entry, (unsigned char *)"hello", 5, NULL, NULL) == 1);
- assert(quicklistCompare(&entry, (unsigned char *)"world", 5, NULL, NULL) == 0);
-
- /* String comparison with caching parameters (cache not used for strings) */
- cached_val = 0;
- cached_valid = 0;
- assert(quicklistCompare(&entry, (unsigned char *)"hello", 5, &cached_val, &cached_valid) == 1);
- assert(cached_valid == 0); /* Cache should not be used for string entries */
- ql_reset_iterator(&iter_local);
-
- /* Test 5: Performance verification - cache should reduce conversions */
- /* This test demonstrates the optimization by showing cache reuse */
- cached_val = 0;
- cached_valid = 0;
- int comparisons = 0;
-
- /* Search for "456" across all integer entries */
- quicklistInitIterator(&iter_local, ql, AL_START_HEAD);
- while (quicklistNext(&iter_local, &entry)) {
- if (entry.value == NULL) { /* Only test integer entries */
- quicklistCompare(&entry, (unsigned char *)"456", 3, &cached_val, &cached_valid);
- comparisons++;
- }
- }
- ql_reset_iterator(&iter_local);
-
- /* After first comparison, cache should be valid and reused for subsequent ones */
- assert(cached_valid == 1);
- assert(cached_val == 456);
- assert(comparisons >= 2); /* Should have compared against multiple integer entries */
-
- quicklistRelease(ql);
- }
-
- /* Benchmarks for quicklistCompare caching optimization */
- {
- printf("\n=== quicklistCompare Caching Benchmarks ===\n");
-
- /* Create a quicklist with 10K integer elements */
- quicklist *ql = quicklistNew(-2, 0);
- char buf[16];
- for (int i = 1; i <= 10000; i++) {
- snprintf(buf, sizeof(buf), "%d", i);
- quicklistPushTail(ql, buf, strlen(buf));
- }
- printf("Created quicklist with %lu integer elements\n", ql->count);
-
- /* Search string that exists in the middle */
- unsigned char *search_str = (unsigned char *)"5000";
- size_t search_len = 4;
- int iterations = accurate ? 50000 : 10000;
-
- /* Benchmark 1: quicklistCompare WITHOUT caching (NULL parameters) */
- TEST("Benchmark quicklistCompare without caching") {
- long long start = ustime();
- int matches = 0;
-
- for (int iter = 0; iter < iterations; iter++) {
- quicklistIter iter_ptr;
- quicklistEntry entry;
- quicklistInitIterator(&iter_ptr, ql, AL_START_HEAD);
-
- while (quicklistNext(&iter_ptr, &entry)) {
- if (entry.value == NULL) { /* Only test integer entries */
- if (quicklistCompare(&entry, search_str, search_len, NULL, NULL)) {
- matches++;
- }
- }
- }
- ql_reset_iterator(&iter_ptr);
- }
-
- long long elapsed = ustime() - start;
- printf("Found %d matches in %d iterations\n", matches, iterations);
- printf("Without caching: %lld usec (%.2f usec per iteration)\n",
- elapsed, (double)elapsed / iterations);
- }
-
- /* Benchmark 2: quicklistCompare WITH caching */
- TEST("Benchmark quicklistCompare with caching") {
- long long start = ustime();
- int matches = 0;
-
- for (int iter = 0; iter < iterations; iter++) {
- /* Reset cache for each iteration to simulate real usage */
- long long cached_val = 0;
- int cached_valid = 0;
-
- quicklistIter iter_ptr;
- quicklistEntry entry;
- quicklistInitIterator(&iter_ptr, ql, AL_START_HEAD);
-
- while (quicklistNext(&iter_ptr, &entry)) {
- if (entry.value == NULL) { /* Only test integer entries */
- if (quicklistCompare(&entry, search_str, search_len, &cached_val, &cached_valid)) {
- matches++;
- }
- }
- }
- ql_reset_iterator(&iter_ptr);
- }
-
- long long elapsed = ustime() - start;
- printf("Found %d matches in %d iterations\n", matches, iterations);
- printf("With caching: %lld usec (%.2f usec per iteration)\n",
- elapsed, (double)elapsed / iterations);
- }
-
- quicklistRelease(ql);
- printf("=== End quicklistCompare Benchmarks ===\n\n");
- }
-
- if (flags & REDIS_TEST_LARGE_MEMORY) {
- TEST("compress and decompress quicklist listpack node") {
- quicklist *ql = quicklistNew(1, 0);
- quicklistNode *node = quicklistCreateNode(ql);
- node->entry = lpNew(0);
-
- /* Just to avoid triggering the assertion in __quicklistCompressNode(),
- * it disables the passing of quicklist head or tail node. */
- node->prev = quicklistCreateNode(ql);
- node->next = quicklistCreateNode(ql);
-
- /* Create a rand string */
- size_t sz = (1 << 25); /* 32MB per one entry */
- unsigned char *s = zmalloc(sz);
- randstring(s, sz);
-
- /* Keep filling the node, until it reaches 1GB */
- for (int i = 0; i < 32; i++) {
- node->entry = lpAppend(node->entry, s, sz);
- size_t oldsize = node->sz;
- quicklistNodeUpdateSz(node);
- quicklistUpdateAllocSize(ql, node->sz, oldsize);
-
- long long start = mstime();
- assert(__quicklistCompressNode(ql, node));
- assert(__quicklistDecompressNode(ql, node));
- printf("Compress and decompress: %zu MB in %.2f seconds.\n",
- node->sz/1024/1024, (float)(mstime() - start) / 1000);
- }
-
- zfree(s);
- zfree(node->prev);
- zfree(node->next);
- zfree(node->entry);
- zfree(node);
- quicklistRelease(ql);
- }
-
-#if ULONG_MAX >= 0xffffffffffffffff
- TEST("compress and decompress quicklist plain node larger than UINT32_MAX") {
- quicklist *ql = quicklistNew(1, 0);
- size_t sz = (1ull << 32);
- unsigned char *s = zmalloc(sz);
- randstring(s, sz);
- memcpy(s, "helloworld", 10);
- memcpy(s + sz - 10, "1234567890", 10);
-
- quicklistNode *node = __quicklistCreateNode(ql, QUICKLIST_NODE_CONTAINER_PLAIN, s, sz);
-
- /* Just to avoid triggering the assertion in __quicklistCompressNode(),
- * it disables the passing of quicklist head or tail node. */
- node->prev = quicklistCreateNode(ql);
- node->next = quicklistCreateNode(ql);
-
- long long start = mstime();
- assert(__quicklistCompressNode(ql, node));
- assert(__quicklistDecompressNode(ql, node));
- printf("Compress and decompress: %zu MB in %.2f seconds.\n",
- node->sz/1024/1024, (float)(mstime() - start) / 1000);
-
- assert(memcmp(node->entry, "helloworld", 10) == 0);
- assert(memcmp(node->entry + sz - 10, "1234567890", 10) == 0);
- zfree(node->prev);
- zfree(node->next);
- zfree(node->entry);
- zfree(node);
- quicklistRelease(ql);
- }
-#endif
- }
-
- if (!err)
- printf("ALL TESTS PASSED!\n");
- else
- ERR("Sorry, not all tests passed! In fact, %d tests failed.", err);
-
- return err;
-}
-#endif