diff options
Diffstat (limited to 'examples/redis-unstable/src/quicklist.c')
| -rw-r--r-- | examples/redis-unstable/src/quicklist.c | 3658 |
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 @@ | |||
| 1 | /* quicklist.c - A doubly linked list of listpacks | ||
| 2 | * | ||
| 3 | * Copyright (c) 2014, Matt Stancliff <matt@genges.com> | ||
| 4 | * All rights reserved. | ||
| 5 | * | ||
| 6 | * Redistribution and use in source and binary forms, with or without | ||
| 7 | * modification, are permitted provided that the following conditions are met: | ||
| 8 | * | ||
| 9 | * * Redistributions of source code must start the above copyright notice, | ||
| 10 | * this quicklist of conditions and the following disclaimer. | ||
| 11 | * * Redistributions in binary form must reproduce the above copyright | ||
| 12 | * notice, this quicklist of conditions and the following disclaimer in the | ||
| 13 | * documentation and/or other materials provided with the distribution. | ||
| 14 | * * Neither the name of Redis nor the names of its contributors may be used | ||
| 15 | * to endorse or promote products derived from this software without | ||
| 16 | * specific prior written permission. | ||
| 17 | * | ||
| 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
| 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
| 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
| 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
| 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
| 28 | * POSSIBILITY OF SUCH DAMAGE. | ||
| 29 | */ | ||
| 30 | |||
| 31 | #include <stdio.h> | ||
| 32 | #include <string.h> /* for memcpy */ | ||
| 33 | #include <limits.h> | ||
| 34 | #include "quicklist.h" | ||
| 35 | #include "zmalloc.h" | ||
| 36 | #include "config.h" | ||
| 37 | #include "listpack.h" | ||
| 38 | #include "util.h" /* for ll2string */ | ||
| 39 | #include "lzf.h" | ||
| 40 | #include "redisassert.h" | ||
| 41 | |||
| 42 | #ifndef REDIS_STATIC | ||
| 43 | #define REDIS_STATIC static | ||
| 44 | #endif | ||
| 45 | |||
| 46 | /* Optimization levels for size-based filling. | ||
| 47 | * Note that the largest possible limit is 64k, so even if each record takes | ||
| 48 | * just one byte, it still won't overflow the 16 bit count field. */ | ||
| 49 | static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536}; | ||
| 50 | |||
| 51 | /* This is for test suite development purposes only, 0 means disabled. */ | ||
| 52 | static size_t packed_threshold = 0; | ||
| 53 | |||
| 54 | /* set threshold for PLAIN nodes for test suit, the real limit is based on `fill` */ | ||
| 55 | int quicklistSetPackedThreshold(size_t sz) { | ||
| 56 | /* Don't allow threshold to be set above or even slightly below 4GB */ | ||
| 57 | if (sz > (1ull<<32) - (1<<20)) { | ||
| 58 | return 0; | ||
| 59 | } | ||
| 60 | packed_threshold = sz; | ||
| 61 | return 1; | ||
| 62 | } | ||
| 63 | |||
| 64 | /* Maximum size in bytes of any multi-element listpack. | ||
| 65 | * Larger values will live in their own isolated listpacks. | ||
| 66 | * This is used only if we're limited by record count. when we're limited by | ||
| 67 | * size, the maximum limit is bigger, but still safe. | ||
| 68 | * 8k is a recommended / default size limit */ | ||
| 69 | #define SIZE_SAFETY_LIMIT 8192 | ||
| 70 | |||
| 71 | /* Maximum estimate of the listpack entry overhead. | ||
| 72 | * Although in the worst case(sz < 64), we will waste 6 bytes in one | ||
| 73 | * quicklistNode, but can avoid memory waste due to internal fragmentation | ||
| 74 | * when the listpack exceeds the size limit by a few bytes (e.g. being 16388). */ | ||
| 75 | #define SIZE_ESTIMATE_OVERHEAD 8 | ||
| 76 | |||
| 77 | /* Minimum listpack size in bytes for attempting compression. */ | ||
| 78 | #define MIN_COMPRESS_BYTES 48 | ||
| 79 | |||
| 80 | /* Minimum size reduction in bytes to store compressed quicklistNode data. | ||
| 81 | * This also prevents us from storing compression if the compression | ||
| 82 | * resulted in a larger size than the original data. */ | ||
| 83 | #define MIN_COMPRESS_IMPROVE 8 | ||
| 84 | |||
| 85 | /* If not verbose testing, remove all debug printing. */ | ||
| 86 | #ifndef REDIS_TEST_VERBOSE | ||
| 87 | #define D(...) | ||
| 88 | #else | ||
| 89 | #define D(...) \ | ||
| 90 | do { \ | ||
| 91 | printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \ | ||
| 92 | printf(__VA_ARGS__); \ | ||
| 93 | printf("\n"); \ | ||
| 94 | } while (0) | ||
| 95 | #endif | ||
| 96 | |||
| 97 | /* Bookmarks forward declarations */ | ||
| 98 | #define QL_MAX_BM ((1 << QL_BM_BITS)-1) | ||
| 99 | quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name); | ||
| 100 | quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node); | ||
| 101 | void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm); | ||
| 102 | |||
| 103 | REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklist *quicklist, quicklistNode *node, | ||
| 104 | int offset, int after); | ||
| 105 | REDIS_STATIC quicklistNode *_quicklistMergeNodes(quicklist *quicklist, quicklistNode *center); | ||
| 106 | |||
| 107 | /* Simple way to give quicklistEntry structs default values with one call. */ | ||
| 108 | #define initEntry(e) \ | ||
| 109 | do { \ | ||
| 110 | (e)->zi = (e)->value = NULL; \ | ||
| 111 | (e)->longval = -123456789; \ | ||
| 112 | (e)->quicklist = NULL; \ | ||
| 113 | (e)->node = NULL; \ | ||
| 114 | (e)->offset = 123456789; \ | ||
| 115 | (e)->sz = 0; \ | ||
| 116 | } while (0) | ||
| 117 | |||
| 118 | /* Reset the quicklistIter to prevent it from being used again after | ||
| 119 | * insert, replace, or other against quicklist operation. */ | ||
| 120 | #define resetIterator(iter) \ | ||
| 121 | do { \ | ||
| 122 | (iter)->current = NULL; \ | ||
| 123 | (iter)->zi = NULL; \ | ||
| 124 | } while (0) | ||
| 125 | |||
| 126 | #define quicklistUpdateAllocSize(ql, new, old) \ | ||
| 127 | do { \ | ||
| 128 | (ql)->alloc_size += (new); \ | ||
| 129 | (ql)->alloc_size -= (old); \ | ||
| 130 | } while (0) | ||
| 131 | |||
| 132 | /* Create a new quicklist. | ||
| 133 | * Free with quicklistRelease(). */ | ||
| 134 | quicklist *quicklistCreate(void) { | ||
| 135 | struct quicklist *quicklist; | ||
| 136 | size_t quicklist_sz; | ||
| 137 | |||
| 138 | quicklist = zmalloc_usable(sizeof(*quicklist), &quicklist_sz); | ||
| 139 | quicklist->head = quicklist->tail = NULL; | ||
| 140 | quicklist->len = 0; | ||
| 141 | quicklist->count = 0; | ||
| 142 | quicklist->alloc_size = quicklist_sz; | ||
| 143 | quicklist->compress = 0; | ||
| 144 | quicklist->fill = -2; | ||
| 145 | quicklist->bookmark_count = 0; | ||
| 146 | return quicklist; | ||
| 147 | } | ||
| 148 | |||
| 149 | #define COMPRESS_MAX ((1 << QL_COMP_BITS)-1) | ||
| 150 | void quicklistSetCompressDepth(quicklist *quicklist, int compress) { | ||
| 151 | if (compress > COMPRESS_MAX) { | ||
| 152 | compress = COMPRESS_MAX; | ||
| 153 | } else if (compress < 0) { | ||
| 154 | compress = 0; | ||
| 155 | } | ||
| 156 | quicklist->compress = compress; | ||
| 157 | } | ||
| 158 | |||
| 159 | #define FILL_MAX ((1 << (QL_FILL_BITS-1))-1) | ||
| 160 | void quicklistSetFill(quicklist *quicklist, int fill) { | ||
| 161 | if (fill > FILL_MAX) { | ||
| 162 | fill = FILL_MAX; | ||
| 163 | } else if (fill < -5) { | ||
| 164 | fill = -5; | ||
| 165 | } | ||
| 166 | quicklist->fill = fill; | ||
| 167 | } | ||
| 168 | |||
| 169 | void quicklistSetOptions(quicklist *quicklist, int fill, int compress) { | ||
| 170 | quicklistSetFill(quicklist, fill); | ||
| 171 | quicklistSetCompressDepth(quicklist, compress); | ||
| 172 | } | ||
| 173 | |||
| 174 | /* Create a new quicklist with some default parameters. */ | ||
| 175 | quicklist *quicklistNew(int fill, int compress) { | ||
| 176 | quicklist *quicklist = quicklistCreate(); | ||
| 177 | quicklistSetOptions(quicklist, fill, compress); | ||
| 178 | return quicklist; | ||
| 179 | } | ||
| 180 | |||
| 181 | REDIS_STATIC quicklistNode *quicklistCreateNode(quicklist *quicklist) { | ||
| 182 | size_t node_usable; | ||
| 183 | quicklistNode *node; | ||
| 184 | node = zmalloc_usable(sizeof(*node), &node_usable); | ||
| 185 | quicklist->alloc_size += node_usable; | ||
| 186 | node->entry = NULL; | ||
| 187 | node->count = 0; | ||
| 188 | node->sz = 0; | ||
| 189 | node->next = node->prev = NULL; | ||
| 190 | node->encoding = QUICKLIST_NODE_ENCODING_RAW; | ||
| 191 | node->container = QUICKLIST_NODE_CONTAINER_PACKED; | ||
| 192 | node->recompress = 0; | ||
| 193 | node->dont_compress = 0; | ||
| 194 | return node; | ||
| 195 | } | ||
| 196 | |||
| 197 | /* Return cached quicklist count */ | ||
| 198 | unsigned long quicklistCount(const quicklist *ql) { return ql->count; } | ||
| 199 | |||
| 200 | /* Return cached quicklist total memory used (in bytes) */ | ||
| 201 | size_t quicklistAllocSize(const quicklist *ql) { return ql->alloc_size; } | ||
| 202 | |||
| 203 | /* Free entire quicklist. */ | ||
| 204 | void quicklistRelease(quicklist *quicklist) { | ||
| 205 | unsigned long len; | ||
| 206 | quicklistNode *current, *next; | ||
| 207 | size_t usable; | ||
| 208 | |||
| 209 | current = quicklist->head; | ||
| 210 | len = quicklist->len; | ||
| 211 | while (len--) { | ||
| 212 | next = current->next; | ||
| 213 | |||
| 214 | if (current->entry) { | ||
| 215 | if (current->encoding == QUICKLIST_NODE_ENCODING_LZF) { | ||
| 216 | quicklistLZF *lzf = (quicklistLZF *)current->entry; | ||
| 217 | quicklist->alloc_size -= sizeof(*lzf) + lzf->sz; | ||
| 218 | } else { | ||
| 219 | quicklist->alloc_size -= current->sz; | ||
| 220 | } | ||
| 221 | zfree(current->entry); | ||
| 222 | } | ||
| 223 | zfree_usable(current, &usable); | ||
| 224 | quicklist->alloc_size -= usable; | ||
| 225 | |||
| 226 | current = next; | ||
| 227 | } | ||
| 228 | quicklistBookmarksClear(quicklist); | ||
| 229 | debugAssert(quicklist->alloc_size == zmalloc_usable_size(quicklist)); | ||
| 230 | zfree(quicklist); | ||
| 231 | } | ||
| 232 | |||
| 233 | /* Compress the listpack in 'node' and update encoding details. | ||
| 234 | * Returns 1 if listpack compressed successfully. | ||
| 235 | * Returns 0 if compression failed or if listpack too small to compress. */ | ||
| 236 | REDIS_STATIC int __quicklistCompressNode(quicklist *quicklist, quicklistNode *node) { | ||
| 237 | #ifdef REDIS_TEST | ||
| 238 | node->attempted_compress = 1; | ||
| 239 | #endif | ||
| 240 | if (node->dont_compress) return 0; | ||
| 241 | |||
| 242 | /* validate that the node is neither | ||
| 243 | * tail nor head (it has prev and next)*/ | ||
| 244 | assert(node->prev && node->next); | ||
| 245 | |||
| 246 | node->recompress = 0; | ||
| 247 | /* Don't bother compressing small values */ | ||
| 248 | if (node->sz < MIN_COMPRESS_BYTES) | ||
| 249 | return 0; | ||
| 250 | |||
| 251 | quicklistLZF *lzf = zmalloc(sizeof(*lzf) + node->sz); | ||
| 252 | |||
| 253 | /* Cancel if compression fails or doesn't compress small enough */ | ||
| 254 | if (((lzf->sz = lzf_compress(node->entry, node->sz, lzf->compressed, | ||
| 255 | node->sz)) == 0) || | ||
| 256 | lzf->sz + MIN_COMPRESS_IMPROVE >= node->sz) { | ||
| 257 | /* lzf_compress aborts/rejects compression if value not compressible. */ | ||
| 258 | zfree(lzf); | ||
| 259 | return 0; | ||
| 260 | } | ||
| 261 | lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz); | ||
| 262 | zfree(node->entry); | ||
| 263 | node->entry = (unsigned char *)lzf; | ||
| 264 | node->encoding = QUICKLIST_NODE_ENCODING_LZF; | ||
| 265 | quicklistUpdateAllocSize(quicklist, sizeof(*lzf) + lzf->sz, node->sz); | ||
| 266 | return 1; | ||
| 267 | } | ||
| 268 | |||
| 269 | /* Compress only uncompressed nodes. */ | ||
| 270 | #define quicklistCompressNode(_ql, _node) \ | ||
| 271 | do { \ | ||
| 272 | if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_RAW) { \ | ||
| 273 | __quicklistCompressNode((_ql), (_node)); \ | ||
| 274 | } \ | ||
| 275 | } while (0) | ||
| 276 | |||
| 277 | /* Uncompress the listpack in 'node' and update encoding details. | ||
| 278 | * Returns 1 on successful decode, 0 on failure to decode. */ | ||
| 279 | REDIS_STATIC int __quicklistDecompressNode(quicklist *quicklist, quicklistNode *node) { | ||
| 280 | #ifdef REDIS_TEST | ||
| 281 | node->attempted_compress = 0; | ||
| 282 | #endif | ||
| 283 | node->recompress = 0; | ||
| 284 | |||
| 285 | void *decompressed = zmalloc(node->sz); | ||
| 286 | quicklistLZF *lzf = (quicklistLZF *)node->entry; | ||
| 287 | if (lzf_decompress(lzf->compressed, lzf->sz, decompressed, node->sz) == 0) { | ||
| 288 | /* Someone requested decompress, but we can't decompress. Not good. */ | ||
| 289 | zfree(decompressed); | ||
| 290 | return 0; | ||
| 291 | } | ||
| 292 | size_t oldsize = sizeof(*lzf) + lzf->sz; | ||
| 293 | zfree(lzf); | ||
| 294 | quicklistUpdateAllocSize(quicklist, node->sz, oldsize); | ||
| 295 | node->entry = decompressed; | ||
| 296 | node->encoding = QUICKLIST_NODE_ENCODING_RAW; | ||
| 297 | return 1; | ||
| 298 | } | ||
| 299 | |||
| 300 | /* Decompress only compressed nodes. */ | ||
| 301 | #define quicklistDecompressNode(_ql, _node) \ | ||
| 302 | do { \ | ||
| 303 | if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \ | ||
| 304 | __quicklistDecompressNode((_ql), (_node)); \ | ||
| 305 | } \ | ||
| 306 | } while (0) | ||
| 307 | |||
| 308 | /* Force node to not be immediately re-compressible */ | ||
| 309 | #define quicklistDecompressNodeForUse(_ql, _node) \ | ||
| 310 | do { \ | ||
| 311 | if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \ | ||
| 312 | __quicklistDecompressNode((_ql), (_node)); \ | ||
| 313 | (_node)->recompress = 1; \ | ||
| 314 | } \ | ||
| 315 | } while (0) | ||
| 316 | |||
| 317 | /* Extract the raw LZF data from this quicklistNode. | ||
| 318 | * Pointer to LZF data is assigned to '*data'. | ||
| 319 | * Return value is the length of compressed LZF data. */ | ||
| 320 | size_t quicklistGetLzf(const quicklistNode *node, void **data) { | ||
| 321 | quicklistLZF *lzf = (quicklistLZF *)node->entry; | ||
| 322 | *data = lzf->compressed; | ||
| 323 | return lzf->sz; | ||
| 324 | } | ||
| 325 | |||
| 326 | #define quicklistAllowsCompression(_ql) ((_ql)->compress != 0) | ||
| 327 | |||
| 328 | /* Force 'quicklist' to meet compression guidelines set by compress depth. | ||
| 329 | * The only way to guarantee interior nodes get compressed is to iterate | ||
| 330 | * to our "interior" compress depth then compress the next node we find. | ||
| 331 | * If compress depth is larger than the entire list, we return immediately. */ | ||
| 332 | REDIS_STATIC void __quicklistCompress(quicklist *quicklist, | ||
| 333 | quicklistNode *node) { | ||
| 334 | if (quicklist->len == 0) return; | ||
| 335 | |||
| 336 | /* The head and tail should never be compressed (we should not attempt to recompress them) */ | ||
| 337 | assert(quicklist->head->recompress == 0 && quicklist->tail->recompress == 0); | ||
| 338 | |||
| 339 | /* If length is less than our compress depth (from both sides), | ||
| 340 | * we can't compress anything. */ | ||
| 341 | if (!quicklistAllowsCompression(quicklist) || | ||
| 342 | quicklist->len < (unsigned int)(quicklist->compress * 2)) | ||
| 343 | return; | ||
| 344 | |||
| 345 | #if 0 | ||
| 346 | /* Optimized cases for small depth counts */ | ||
| 347 | if (quicklist->compress == 1) { | ||
| 348 | quicklistNode *h = quicklist->head, *t = quicklist->tail; | ||
| 349 | quicklistDecompressNode(quicklist, h); | ||
| 350 | quicklistDecompressNode(quicklist, t); | ||
| 351 | if (h != node && t != node) | ||
| 352 | quicklistCompressNode(quicklist, node); | ||
| 353 | return; | ||
| 354 | } else if (quicklist->compress == 2) { | ||
| 355 | quicklistNode *h = quicklist->head, *hn = h->next, *hnn = hn->next; | ||
| 356 | quicklistNode *t = quicklist->tail, *tp = t->prev, *tpp = tp->prev; | ||
| 357 | quicklistDecompressNode(quicklist, h); | ||
| 358 | quicklistDecompressNode(quicklist, hn); | ||
| 359 | quicklistDecompressNode(quicklist, t); | ||
| 360 | quicklistDecompressNode(quicklist, tp); | ||
| 361 | if (h != node && hn != node && t != node && tp != node) { | ||
| 362 | quicklistCompressNode(quicklist, node); | ||
| 363 | } | ||
| 364 | if (hnn != t) { | ||
| 365 | quicklistCompressNode(quicklist, hnn); | ||
| 366 | } | ||
| 367 | if (tpp != h) { | ||
| 368 | quicklistCompressNode(quicklist, tpp); | ||
| 369 | } | ||
| 370 | return; | ||
| 371 | } | ||
| 372 | #endif | ||
| 373 | |||
| 374 | /* Iterate until we reach compress depth for both sides of the list.a | ||
| 375 | * Note: because we do length checks at the *top* of this function, | ||
| 376 | * we can skip explicit null checks below. Everything exists. */ | ||
| 377 | quicklistNode *forward = quicklist->head; | ||
| 378 | quicklistNode *reverse = quicklist->tail; | ||
| 379 | int depth = 0; | ||
| 380 | int in_depth = 0; | ||
| 381 | while (depth++ < quicklist->compress) { | ||
| 382 | quicklistDecompressNode(quicklist, forward); | ||
| 383 | quicklistDecompressNode(quicklist, reverse); | ||
| 384 | |||
| 385 | if (forward == node || reverse == node) | ||
| 386 | in_depth = 1; | ||
| 387 | |||
| 388 | /* We passed into compress depth of opposite side of the quicklist | ||
| 389 | * so there's no need to compress anything and we can exit. */ | ||
| 390 | if (forward == reverse || forward->next == reverse) | ||
| 391 | return; | ||
| 392 | |||
| 393 | forward = forward->next; | ||
| 394 | reverse = reverse->prev; | ||
| 395 | } | ||
| 396 | |||
| 397 | if (!in_depth) | ||
| 398 | quicklistCompressNode(quicklist, node); | ||
| 399 | |||
| 400 | /* At this point, forward and reverse are one node beyond depth */ | ||
| 401 | quicklistCompressNode(quicklist, forward); | ||
| 402 | quicklistCompressNode(quicklist, reverse); | ||
| 403 | } | ||
| 404 | |||
| 405 | /* This macro is used to compress a node. | ||
| 406 | * | ||
| 407 | * If the 'recompress' flag of the node is true, we compress it directly without | ||
| 408 | * checking whether it is within the range of compress depth. | ||
| 409 | * However, it's important to ensure that the 'recompress' flag of head and tail | ||
| 410 | * is always false, as we always assume that head and tail are not compressed. | ||
| 411 | * | ||
| 412 | * If the 'recompress' flag of the node is false, we check whether the node is | ||
| 413 | * within the range of compress depth before compressing it. */ | ||
| 414 | #define quicklistCompress(_ql, _node) \ | ||
| 415 | do { \ | ||
| 416 | if ((_node)->recompress) \ | ||
| 417 | quicklistCompressNode((_ql), (_node)); \ | ||
| 418 | else \ | ||
| 419 | __quicklistCompress((_ql), (_node)); \ | ||
| 420 | } while (0) | ||
| 421 | |||
| 422 | /* If we previously used quicklistDecompressNodeForUse(), just recompress. */ | ||
| 423 | #define quicklistRecompressOnly(_ql, _node) \ | ||
| 424 | do { \ | ||
| 425 | if ((_node)->recompress) \ | ||
| 426 | quicklistCompressNode((_ql), (_node)); \ | ||
| 427 | } while (0) | ||
| 428 | |||
| 429 | /* Insert 'new_node' after 'old_node' if 'after' is 1. | ||
| 430 | * Insert 'new_node' before 'old_node' if 'after' is 0. | ||
| 431 | * Note: 'new_node' is *always* uncompressed, so if we assign it to | ||
| 432 | * head or tail, we do not need to uncompress it. */ | ||
| 433 | REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist, | ||
| 434 | quicklistNode *old_node, | ||
| 435 | quicklistNode *new_node, int after) { | ||
| 436 | if (after) { | ||
| 437 | new_node->prev = old_node; | ||
| 438 | if (old_node) { | ||
| 439 | new_node->next = old_node->next; | ||
| 440 | if (old_node->next) | ||
| 441 | old_node->next->prev = new_node; | ||
| 442 | old_node->next = new_node; | ||
| 443 | } | ||
| 444 | if (quicklist->tail == old_node) | ||
| 445 | quicklist->tail = new_node; | ||
| 446 | } else { | ||
| 447 | new_node->next = old_node; | ||
| 448 | if (old_node) { | ||
| 449 | new_node->prev = old_node->prev; | ||
| 450 | if (old_node->prev) | ||
| 451 | old_node->prev->next = new_node; | ||
| 452 | old_node->prev = new_node; | ||
| 453 | } | ||
| 454 | if (quicklist->head == old_node) | ||
| 455 | quicklist->head = new_node; | ||
| 456 | } | ||
| 457 | /* If this insert creates the only element so far, initialize head/tail. */ | ||
| 458 | if (quicklist->len == 0) { | ||
| 459 | quicklist->head = quicklist->tail = new_node; | ||
| 460 | } | ||
| 461 | |||
| 462 | /* Update len first, so in __quicklistCompress we know exactly len */ | ||
| 463 | quicklist->len++; | ||
| 464 | |||
| 465 | if (old_node) | ||
| 466 | quicklistCompress(quicklist, old_node); | ||
| 467 | |||
| 468 | quicklistCompress(quicklist, new_node); | ||
| 469 | } | ||
| 470 | |||
| 471 | /* Wrappers for node inserting around existing node. */ | ||
| 472 | REDIS_STATIC void _quicklistInsertNodeBefore(quicklist *quicklist, | ||
| 473 | quicklistNode *old_node, | ||
| 474 | quicklistNode *new_node) { | ||
| 475 | __quicklistInsertNode(quicklist, old_node, new_node, 0); | ||
| 476 | } | ||
| 477 | |||
| 478 | REDIS_STATIC void _quicklistInsertNodeAfter(quicklist *quicklist, | ||
| 479 | quicklistNode *old_node, | ||
| 480 | quicklistNode *new_node) { | ||
| 481 | __quicklistInsertNode(quicklist, old_node, new_node, 1); | ||
| 482 | } | ||
| 483 | |||
| 484 | #define sizeMeetsSafetyLimit(sz) ((sz) <= SIZE_SAFETY_LIMIT) | ||
| 485 | |||
| 486 | /* Calculate the size limit of the quicklist node based on negative 'fill'. */ | ||
| 487 | static size_t quicklistNodeNegFillLimit(int fill) { | ||
| 488 | assert(fill < 0); | ||
| 489 | size_t offset = (-fill) - 1; | ||
| 490 | size_t max_level = sizeof(optimization_level) / sizeof(*optimization_level); | ||
| 491 | if (offset >= max_level) offset = max_level - 1; | ||
| 492 | return optimization_level[offset]; | ||
| 493 | } | ||
| 494 | |||
| 495 | /* Calculate the size limit or length limit of the quicklist node | ||
| 496 | * based on 'fill', and is also used to limit list listpack. */ | ||
| 497 | void quicklistNodeLimit(int fill, size_t *size, unsigned int *count) { | ||
| 498 | *size = SIZE_MAX; | ||
| 499 | *count = UINT_MAX; | ||
| 500 | |||
| 501 | if (fill >= 0) { | ||
| 502 | /* Ensure that one node have at least one entry */ | ||
| 503 | *count = (fill == 0) ? 1 : fill; | ||
| 504 | } else { | ||
| 505 | *size = quicklistNodeNegFillLimit(fill); | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | /* Check if the limit of the quicklist node has been reached to determine if | ||
| 510 | * insertions, merges or other operations that would increase the size of | ||
| 511 | * the node can be performed. | ||
| 512 | * Return 1 if exceeds the limit, otherwise 0. */ | ||
| 513 | int quicklistNodeExceedsLimit(int fill, size_t new_sz, unsigned int new_count) { | ||
| 514 | size_t sz_limit; | ||
| 515 | unsigned int count_limit; | ||
| 516 | quicklistNodeLimit(fill, &sz_limit, &count_limit); | ||
| 517 | |||
| 518 | if (likely(sz_limit != SIZE_MAX)) { | ||
| 519 | return new_sz > sz_limit; | ||
| 520 | } else if (count_limit != UINT_MAX) { | ||
| 521 | /* when we reach here we know that the limit is a size limit (which is | ||
| 522 | * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */ | ||
| 523 | if (!sizeMeetsSafetyLimit(new_sz)) return 1; | ||
| 524 | return new_count > count_limit; | ||
| 525 | } | ||
| 526 | |||
| 527 | redis_unreachable(); | ||
| 528 | } | ||
| 529 | |||
| 530 | /* Determines whether a given size qualifies as a large element based on a threshold | ||
| 531 | * determined by the 'fill'. If the size is considered large, it will be stored in | ||
| 532 | * a plain node. */ | ||
| 533 | static int isLargeElement(size_t sz, int fill) { | ||
| 534 | if (unlikely(packed_threshold != 0)) return sz >= packed_threshold; | ||
| 535 | if (fill >= 0) | ||
| 536 | return !sizeMeetsSafetyLimit(sz); | ||
| 537 | else | ||
| 538 | return sz > quicklistNodeNegFillLimit(fill); | ||
| 539 | } | ||
| 540 | |||
| 541 | REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node, | ||
| 542 | const int fill, const size_t sz) { | ||
| 543 | if (unlikely(!node)) | ||
| 544 | return 0; | ||
| 545 | |||
| 546 | if (unlikely(QL_NODE_IS_PLAIN(node) || isLargeElement(sz, fill))) | ||
| 547 | return 0; | ||
| 548 | |||
| 549 | /* Estimate how many bytes will be added to the listpack by this one entry. | ||
| 550 | * We prefer an overestimation, which would at worse lead to a few bytes | ||
| 551 | * below the lowest limit of 4k (see optimization_level). | ||
| 552 | * Note: No need to check for overflow below since both `node->sz` and | ||
| 553 | * `sz` are to be less than 1GB after the plain/large element check above. */ | ||
| 554 | size_t new_sz = node->sz + sz + SIZE_ESTIMATE_OVERHEAD; | ||
| 555 | if (unlikely(quicklistNodeExceedsLimit(fill, new_sz, node->count + 1))) | ||
| 556 | return 0; | ||
| 557 | return 1; | ||
| 558 | } | ||
| 559 | |||
| 560 | REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a, | ||
| 561 | const quicklistNode *b, | ||
| 562 | const int fill) { | ||
| 563 | if (!a || !b) | ||
| 564 | return 0; | ||
| 565 | |||
| 566 | if (unlikely(QL_NODE_IS_PLAIN(a) || QL_NODE_IS_PLAIN(b))) | ||
| 567 | return 0; | ||
| 568 | |||
| 569 | /* approximate merged listpack size (- 7 to remove one listpack | ||
| 570 | * header/trailer, see LP_HDR_SIZE and LP_EOF) */ | ||
| 571 | unsigned int merge_sz = a->sz + b->sz - 7; | ||
| 572 | if (unlikely(quicklistNodeExceedsLimit(fill, merge_sz, a->count + b->count))) | ||
| 573 | return 0; | ||
| 574 | return 1; | ||
| 575 | } | ||
| 576 | |||
| 577 | #define quicklistNodeUpdateSz(node) \ | ||
| 578 | do { \ | ||
| 579 | (node)->sz = lpBytes((node)->entry); \ | ||
| 580 | } while (0) | ||
| 581 | |||
| 582 | static quicklistNode* __quicklistCreateNode(quicklist *quicklist, int container, void *value, size_t sz) { | ||
| 583 | quicklistNode *new_node = quicklistCreateNode(quicklist); | ||
| 584 | new_node->container = container; | ||
| 585 | if (container == QUICKLIST_NODE_CONTAINER_PLAIN) { | ||
| 586 | new_node->entry = zmalloc(sz); | ||
| 587 | memcpy(new_node->entry, value, sz); | ||
| 588 | new_node->sz = sz; | ||
| 589 | } else { | ||
| 590 | new_node->entry = lpPrepend(lpNew(0), value, sz); | ||
| 591 | quicklistNodeUpdateSz(new_node); | ||
| 592 | } | ||
| 593 | quicklist->alloc_size += new_node->sz; | ||
| 594 | new_node->count++; | ||
| 595 | return new_node; | ||
| 596 | } | ||
| 597 | |||
| 598 | static void __quicklistInsertPlainNode(quicklist *quicklist, quicklistNode *old_node, | ||
| 599 | void *value, size_t sz, int after) | ||
| 600 | { | ||
| 601 | quicklistNode *new_node = __quicklistCreateNode(quicklist, QUICKLIST_NODE_CONTAINER_PLAIN, value, sz); | ||
| 602 | __quicklistInsertNode(quicklist, old_node, new_node, after); | ||
| 603 | quicklist->count++; | ||
| 604 | } | ||
| 605 | |||
| 606 | /* Add new entry to head node of quicklist. | ||
| 607 | * | ||
| 608 | * Returns 0 if used existing head. | ||
| 609 | * Returns 1 if new head created. */ | ||
| 610 | int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) { | ||
| 611 | quicklistNode *orig_head = quicklist->head; | ||
| 612 | |||
| 613 | if (unlikely(isLargeElement(sz, quicklist->fill))) { | ||
| 614 | __quicklistInsertPlainNode(quicklist, quicklist->head, value, sz, 0); | ||
| 615 | return 1; | ||
| 616 | } | ||
| 617 | |||
| 618 | if (likely( | ||
| 619 | _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) { | ||
| 620 | size_t oldsize = quicklist->head->sz; | ||
| 621 | quicklist->head->entry = lpPrepend(quicklist->head->entry, value, sz); | ||
| 622 | quicklistNodeUpdateSz(quicklist->head); | ||
| 623 | quicklistUpdateAllocSize(quicklist, quicklist->head->sz, oldsize); | ||
| 624 | } else { | ||
| 625 | quicklistNode *node = quicklistCreateNode(quicklist); | ||
| 626 | node->entry = lpPrepend(lpNew(0), value, sz); | ||
| 627 | quicklistNodeUpdateSz(node); | ||
| 628 | quicklistUpdateAllocSize(quicklist, node->sz, 0); | ||
| 629 | _quicklistInsertNodeBefore(quicklist, quicklist->head, node); | ||
| 630 | } | ||
| 631 | quicklist->count++; | ||
| 632 | quicklist->head->count++; | ||
| 633 | return (orig_head != quicklist->head); | ||
| 634 | } | ||
| 635 | |||
| 636 | /* Add new entry to tail node of quicklist. | ||
| 637 | * | ||
| 638 | * Returns 0 if used existing tail. | ||
| 639 | * Returns 1 if new tail created. */ | ||
| 640 | int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) { | ||
| 641 | quicklistNode *orig_tail = quicklist->tail; | ||
| 642 | if (unlikely(isLargeElement(sz, quicklist->fill))) { | ||
| 643 | __quicklistInsertPlainNode(quicklist, quicklist->tail, value, sz, 1); | ||
| 644 | return 1; | ||
| 645 | } | ||
| 646 | |||
| 647 | if (likely( | ||
| 648 | _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) { | ||
| 649 | size_t oldsize = quicklist->tail->sz; | ||
| 650 | quicklist->tail->entry = lpAppend(quicklist->tail->entry, value, sz); | ||
| 651 | quicklistNodeUpdateSz(quicklist->tail); | ||
| 652 | quicklistUpdateAllocSize(quicklist, quicklist->tail->sz, oldsize); | ||
| 653 | } else { | ||
| 654 | quicklistNode *node = quicklistCreateNode(quicklist); | ||
| 655 | node->entry = lpAppend(lpNew(0), value, sz); | ||
| 656 | quicklistNodeUpdateSz(node); | ||
| 657 | quicklistUpdateAllocSize(quicklist, node->sz, 0); | ||
| 658 | _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); | ||
| 659 | } | ||
| 660 | quicklist->count++; | ||
| 661 | quicklist->tail->count++; | ||
| 662 | return (orig_tail != quicklist->tail); | ||
| 663 | } | ||
| 664 | |||
| 665 | /* Create new node consisting of a pre-formed listpack. | ||
| 666 | * Used for loading RDBs where entire listpacks have been stored | ||
| 667 | * to be retrieved later. */ | ||
| 668 | void quicklistAppendListpack(quicklist *quicklist, unsigned char *zl) { | ||
| 669 | quicklistNode *node = quicklistCreateNode(quicklist); | ||
| 670 | |||
| 671 | node->entry = zl; | ||
| 672 | node->count = lpLength(node->entry); | ||
| 673 | node->sz = lpBytes(zl); | ||
| 674 | |||
| 675 | quicklist->alloc_size += node->sz; | ||
| 676 | _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); | ||
| 677 | quicklist->count += node->count; | ||
| 678 | } | ||
| 679 | |||
| 680 | /* Create new node consisting of a pre-formed plain node. | ||
| 681 | * Used for loading RDBs where entire plain node has been stored | ||
| 682 | * to be retrieved later. | ||
| 683 | * data - the data to add (pointer becomes the responsibility of quicklist) */ | ||
| 684 | void quicklistAppendPlainNode(quicklist *quicklist, unsigned char *data, size_t sz) { | ||
| 685 | quicklistNode *node = quicklistCreateNode(quicklist); | ||
| 686 | |||
| 687 | node->entry = data; | ||
| 688 | node->count = 1; | ||
| 689 | node->sz = sz; | ||
| 690 | node->container = QUICKLIST_NODE_CONTAINER_PLAIN; | ||
| 691 | |||
| 692 | quicklist->alloc_size += sz; | ||
| 693 | _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); | ||
| 694 | quicklist->count += node->count; | ||
| 695 | } | ||
| 696 | |||
| 697 | #define quicklistDeleteIfEmpty(ql, n) \ | ||
| 698 | do { \ | ||
| 699 | if ((n)->count == 0) { \ | ||
| 700 | __quicklistDelNode((ql), (n)); \ | ||
| 701 | (n) = NULL; \ | ||
| 702 | } \ | ||
| 703 | } while (0) | ||
| 704 | |||
| 705 | REDIS_STATIC void __quicklistDelNode(quicklist *quicklist, | ||
| 706 | quicklistNode *node) { | ||
| 707 | /* Update the bookmark if any */ | ||
| 708 | quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node); | ||
| 709 | if (bm) { | ||
| 710 | bm->node = node->next; | ||
| 711 | /* if the bookmark was to the last node, delete it. */ | ||
| 712 | if (!bm->node) | ||
| 713 | _quicklistBookmarkDelete(quicklist, bm); | ||
| 714 | } | ||
| 715 | |||
| 716 | if (node->next) | ||
| 717 | node->next->prev = node->prev; | ||
| 718 | if (node->prev) | ||
| 719 | node->prev->next = node->next; | ||
| 720 | |||
| 721 | if (node == quicklist->tail) { | ||
| 722 | quicklist->tail = node->prev; | ||
| 723 | } | ||
| 724 | |||
| 725 | if (node == quicklist->head) { | ||
| 726 | quicklist->head = node->next; | ||
| 727 | } | ||
| 728 | |||
| 729 | /* Update len first, so in __quicklistCompress we know exactly len */ | ||
| 730 | quicklist->len--; | ||
| 731 | quicklist->count -= node->count; | ||
| 732 | |||
| 733 | /* If we deleted a node within our compress depth, we | ||
| 734 | * now have compressed nodes needing to be decompressed. */ | ||
| 735 | __quicklistCompress(quicklist, NULL); | ||
| 736 | |||
| 737 | if (node->encoding == QUICKLIST_NODE_ENCODING_LZF) { | ||
| 738 | quicklistLZF *lzf = (quicklistLZF *)node->entry; | ||
| 739 | size_t lzf_sz = sizeof(*lzf) + lzf->sz; | ||
| 740 | quicklist->alloc_size -= lzf_sz; | ||
| 741 | } else { | ||
| 742 | quicklist->alloc_size -= node->sz; | ||
| 743 | } | ||
| 744 | zfree(node->entry); | ||
| 745 | size_t usable; | ||
| 746 | zfree_usable(node, &usable); | ||
| 747 | quicklist->alloc_size -= usable; | ||
| 748 | } | ||
| 749 | |||
| 750 | /* Delete one entry from list given the node for the entry and a pointer | ||
| 751 | * to the entry in the node. | ||
| 752 | * | ||
| 753 | * Note: quicklistDelIndex() *requires* uncompressed nodes because you | ||
| 754 | * already had to get *p from an uncompressed node somewhere. | ||
| 755 | * | ||
| 756 | * Returns 1 if the entire node was deleted, 0 if node still exists. | ||
| 757 | * Also updates in/out param 'p' with the next offset in the listpack. */ | ||
| 758 | REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node, | ||
| 759 | unsigned char **p) { | ||
| 760 | int gone = 0; | ||
| 761 | |||
| 762 | if (unlikely(QL_NODE_IS_PLAIN(node))) { | ||
| 763 | __quicklistDelNode(quicklist, node); | ||
| 764 | return 1; | ||
| 765 | } | ||
| 766 | size_t oldsize = node->sz; | ||
| 767 | node->entry = lpDelete(node->entry, *p, p); | ||
| 768 | quicklistNodeUpdateSz(node); | ||
| 769 | quicklistUpdateAllocSize(quicklist, node->sz, oldsize); | ||
| 770 | node->count--; | ||
| 771 | if (node->count == 0) { | ||
| 772 | gone = 1; | ||
| 773 | __quicklistDelNode(quicklist, node); | ||
| 774 | } | ||
| 775 | quicklist->count--; | ||
| 776 | /* If we deleted the node, the original node is no longer valid */ | ||
| 777 | return gone ? 1 : 0; | ||
| 778 | } | ||
| 779 | |||
| 780 | /* Delete one element represented by 'entry' | ||
| 781 | * | ||
| 782 | * 'entry' stores enough metadata to delete the proper position in | ||
| 783 | * the correct listpack in the correct quicklist node. */ | ||
| 784 | void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) { | ||
| 785 | quicklistNode *prev = entry->node->prev; | ||
| 786 | quicklistNode *next = entry->node->next; | ||
| 787 | int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist, | ||
| 788 | entry->node, &entry->zi); | ||
| 789 | |||
| 790 | /* after delete, the zi is now invalid for any future usage. */ | ||
| 791 | iter->zi = NULL; | ||
| 792 | |||
| 793 | /* If current node is deleted, we must update iterator node and offset. */ | ||
| 794 | if (deleted_node) { | ||
| 795 | if (iter->direction == AL_START_HEAD) { | ||
| 796 | iter->current = next; | ||
| 797 | iter->offset = 0; | ||
| 798 | } else if (iter->direction == AL_START_TAIL) { | ||
| 799 | iter->current = prev; | ||
| 800 | iter->offset = -1; | ||
| 801 | } | ||
| 802 | } | ||
| 803 | /* else if (!deleted_node), no changes needed. | ||
| 804 | * we already reset iter->zi above, and the existing iter->offset | ||
| 805 | * doesn't move again because: | ||
| 806 | * - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1 | ||
| 807 | * - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0 | ||
| 808 | * if we deleted the last element at offset N and now | ||
| 809 | * length of this listpack is N-1, the next call into | ||
| 810 | * quicklistNext() will jump to the next node. */ | ||
| 811 | } | ||
| 812 | |||
| 813 | /* Replace quicklist entry by 'data' with length 'sz'. */ | ||
| 814 | void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry, | ||
| 815 | void *data, size_t sz) | ||
| 816 | { | ||
| 817 | quicklist* quicklist = iter->quicklist; | ||
| 818 | quicklistNode *node = entry->node; | ||
| 819 | unsigned char *newentry; | ||
| 820 | |||
| 821 | if (likely(!QL_NODE_IS_PLAIN(entry->node) && !isLargeElement(sz, quicklist->fill) && | ||
| 822 | (newentry = lpReplace(entry->node->entry, &entry->zi, data, sz)) != NULL)) | ||
| 823 | { | ||
| 824 | entry->node->entry = newentry; | ||
| 825 | size_t oldsize = entry->node->sz; | ||
| 826 | quicklistNodeUpdateSz(entry->node); | ||
| 827 | quicklistUpdateAllocSize(quicklist, entry->node->sz, oldsize); | ||
| 828 | /* quicklistNext() and quicklistInitIteratorEntryAtIdx() provide an uncompressed node */ | ||
| 829 | quicklistCompress(quicklist, entry->node); | ||
| 830 | } else if (QL_NODE_IS_PLAIN(entry->node)) { | ||
| 831 | if (isLargeElement(sz, quicklist->fill)) { | ||
| 832 | zfree(entry->node->entry); | ||
| 833 | entry->node->entry = zmalloc(sz); | ||
| 834 | size_t oldsize = entry->node->sz; | ||
| 835 | quicklistUpdateAllocSize(quicklist, sz, oldsize); | ||
| 836 | entry->node->sz = sz; | ||
| 837 | memcpy(entry->node->entry, data, sz); | ||
| 838 | quicklistCompress(quicklist, entry->node); | ||
| 839 | } else { | ||
| 840 | quicklistInsertAfter(iter, entry, data, sz); | ||
| 841 | __quicklistDelNode(quicklist, entry->node); | ||
| 842 | } | ||
| 843 | } else { /* The node is full or data is a large element */ | ||
| 844 | quicklistNode *split_node = NULL, *new_node; | ||
| 845 | node->dont_compress = 1; /* Prevent compression in __quicklistInsertNode() */ | ||
| 846 | |||
| 847 | /* If the entry is not at the tail, split the node at the entry's offset. */ | ||
| 848 | if (entry->offset != node->count - 1 && entry->offset != -1) | ||
| 849 | split_node = _quicklistSplitNode(quicklist, node, entry->offset, 1); | ||
| 850 | |||
| 851 | /* Create a new node and insert it after the original node. | ||
| 852 | * If the original node was split, insert the split node after the new node. */ | ||
| 853 | new_node = __quicklistCreateNode(quicklist, isLargeElement(sz, quicklist->fill) ? | ||
| 854 | QUICKLIST_NODE_CONTAINER_PLAIN : QUICKLIST_NODE_CONTAINER_PACKED, data, sz); | ||
| 855 | __quicklistInsertNode(quicklist, node, new_node, 1); | ||
| 856 | if (split_node) __quicklistInsertNode(quicklist, new_node, split_node, 1); | ||
| 857 | quicklist->count++; | ||
| 858 | |||
| 859 | /* Delete the replaced element. */ | ||
| 860 | if (entry->node->count == 1) { | ||
| 861 | __quicklistDelNode(quicklist, entry->node); | ||
| 862 | } else { | ||
| 863 | unsigned char *p = lpSeek(entry->node->entry, -1); | ||
| 864 | quicklistDelIndex(quicklist, entry->node, &p); | ||
| 865 | entry->node->dont_compress = 0; /* Re-enable compression */ | ||
| 866 | new_node = _quicklistMergeNodes(quicklist, new_node); | ||
| 867 | /* We can't know if the current node and its sibling nodes are correctly compressed, | ||
| 868 | * and we don't know if they are within the range of compress depth, so we need to | ||
| 869 | * use quicklistCompress() for compression, which checks if node is within compress | ||
| 870 | * depth before compressing. */ | ||
| 871 | quicklistCompress(quicklist, new_node); | ||
| 872 | quicklistCompress(quicklist, new_node->prev); | ||
| 873 | if (new_node->next) quicklistCompress(quicklist, new_node->next); | ||
| 874 | } | ||
| 875 | } | ||
| 876 | |||
| 877 | /* In any case, we reset iterator to forbid use of iterator after insert. | ||
| 878 | * Notice: iter->current has been compressed above. */ | ||
| 879 | resetIterator(iter); | ||
| 880 | } | ||
| 881 | |||
| 882 | /* Replace quicklist entry at offset 'index' by 'data' with length 'sz'. | ||
| 883 | * | ||
| 884 | * Returns 1 if replace happened. | ||
| 885 | * Returns 0 if replace failed and no changes happened. */ | ||
| 886 | int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, | ||
| 887 | size_t sz) { | ||
| 888 | quicklistEntry entry; | ||
| 889 | quicklistIter iter; | ||
| 890 | if (likely(quicklistInitIteratorEntryAtIdx(&iter, quicklist, index, &entry))) { | ||
| 891 | quicklistReplaceEntry(&iter, &entry, data, sz); | ||
| 892 | quicklistResetIterator(&iter); | ||
| 893 | return 1; | ||
| 894 | } else { | ||
| 895 | return 0; | ||
| 896 | } | ||
| 897 | } | ||
| 898 | |||
| 899 | /* Given two nodes, try to merge their listpacks. | ||
| 900 | * | ||
| 901 | * This helps us not have a quicklist with 3 element listpacks if | ||
| 902 | * our fill factor can handle much higher levels. | ||
| 903 | * | ||
| 904 | * Note: 'a' must be to the LEFT of 'b'. | ||
| 905 | * | ||
| 906 | * After calling this function, both 'a' and 'b' should be considered | ||
| 907 | * unusable. The return value from this function must be used | ||
| 908 | * instead of re-using any of the quicklistNode input arguments. | ||
| 909 | * | ||
| 910 | * Returns the input node picked to merge against or NULL if | ||
| 911 | * merging was not possible. */ | ||
| 912 | REDIS_STATIC quicklistNode *_quicklistListpackMerge(quicklist *quicklist, | ||
| 913 | quicklistNode *a, | ||
| 914 | quicklistNode *b) { | ||
| 915 | D("Requested merge (a,b) (%u, %u)", a->count, b->count); | ||
| 916 | |||
| 917 | quicklistDecompressNode(quicklist, a); | ||
| 918 | quicklistDecompressNode(quicklist, b); | ||
| 919 | if ((lpMerge(&a->entry, &b->entry))) { | ||
| 920 | /* We merged listpacks! Now remove the unused quicklistNode. */ | ||
| 921 | quicklistNode *keep = NULL, *nokeep = NULL; | ||
| 922 | if (!a->entry) { | ||
| 923 | nokeep = a; | ||
| 924 | keep = b; | ||
| 925 | } else if (!b->entry) { | ||
| 926 | nokeep = b; | ||
| 927 | keep = a; | ||
| 928 | } | ||
| 929 | keep->count = lpLength(keep->entry); | ||
| 930 | size_t oldsize = keep->sz; | ||
| 931 | quicklistNodeUpdateSz(keep); | ||
| 932 | quicklistUpdateAllocSize(quicklist, keep->sz, oldsize); | ||
| 933 | keep->recompress = 0; /* Prevent 'keep' from being recompressed if | ||
| 934 | * it becomes head or tail after merging. */ | ||
| 935 | |||
| 936 | nokeep->count = 0; | ||
| 937 | __quicklistDelNode(quicklist, nokeep); | ||
| 938 | quicklistCompress(quicklist, keep); | ||
| 939 | return keep; | ||
| 940 | } else { | ||
| 941 | /* else, the merge returned NULL and nothing changed. */ | ||
| 942 | return NULL; | ||
| 943 | } | ||
| 944 | } | ||
| 945 | |||
| 946 | /* Attempt to merge listpacks within two nodes on either side of 'center'. | ||
| 947 | * | ||
| 948 | * We attempt to merge: | ||
| 949 | * - (center->prev->prev, center->prev) | ||
| 950 | * - (center->next, center->next->next) | ||
| 951 | * - (center->prev, center) | ||
| 952 | * - (center, center->next) | ||
| 953 | * | ||
| 954 | * Returns the new 'center' after merging. | ||
| 955 | */ | ||
| 956 | REDIS_STATIC quicklistNode *_quicklistMergeNodes(quicklist *quicklist, quicklistNode *center) { | ||
| 957 | int fill = quicklist->fill; | ||
| 958 | quicklistNode *prev, *prev_prev, *next, *next_next, *target; | ||
| 959 | prev = prev_prev = next = next_next = target = NULL; | ||
| 960 | |||
| 961 | if (center->prev) { | ||
| 962 | prev = center->prev; | ||
| 963 | if (center->prev->prev) | ||
| 964 | prev_prev = center->prev->prev; | ||
| 965 | } | ||
| 966 | |||
| 967 | if (center->next) { | ||
| 968 | next = center->next; | ||
| 969 | if (center->next->next) | ||
| 970 | next_next = center->next->next; | ||
| 971 | } | ||
| 972 | |||
| 973 | /* Try to merge prev_prev and prev */ | ||
| 974 | if (_quicklistNodeAllowMerge(prev, prev_prev, fill)) { | ||
| 975 | _quicklistListpackMerge(quicklist, prev_prev, prev); | ||
| 976 | prev_prev = prev = NULL; /* they could have moved, invalidate them. */ | ||
| 977 | } | ||
| 978 | |||
| 979 | /* Try to merge next and next_next */ | ||
| 980 | if (_quicklistNodeAllowMerge(next, next_next, fill)) { | ||
| 981 | _quicklistListpackMerge(quicklist, next, next_next); | ||
| 982 | next = next_next = NULL; /* they could have moved, invalidate them. */ | ||
| 983 | } | ||
| 984 | |||
| 985 | /* Try to merge center node and previous node */ | ||
| 986 | if (_quicklistNodeAllowMerge(center, center->prev, fill)) { | ||
| 987 | target = _quicklistListpackMerge(quicklist, center->prev, center); | ||
| 988 | center = NULL; /* center could have been deleted, invalidate it. */ | ||
| 989 | } else { | ||
| 990 | /* else, we didn't merge here, but target needs to be valid below. */ | ||
| 991 | target = center; | ||
| 992 | } | ||
| 993 | |||
| 994 | /* Use result of center merge (or original) to merge with next node. */ | ||
| 995 | if (_quicklistNodeAllowMerge(target, target->next, fill)) { | ||
| 996 | target = _quicklistListpackMerge(quicklist, target, target->next); | ||
| 997 | } | ||
| 998 | return target; | ||
| 999 | } | ||
| 1000 | |||
| 1001 | /* Split 'node' into two parts, parameterized by 'offset' and 'after'. | ||
| 1002 | * | ||
| 1003 | * The 'after' argument controls which quicklistNode gets returned. | ||
| 1004 | * If 'after'==1, returned node has elements after 'offset'. | ||
| 1005 | * input node keeps elements up to 'offset', including 'offset'. | ||
| 1006 | * If 'after'==0, returned node has elements up to 'offset'. | ||
| 1007 | * input node keeps elements after 'offset', including 'offset'. | ||
| 1008 | * | ||
| 1009 | * Or in other words: | ||
| 1010 | * If 'after'==1, returned node will have elements after 'offset'. | ||
| 1011 | * The returned node will have elements [OFFSET+1, END]. | ||
| 1012 | * The input node keeps elements [0, OFFSET]. | ||
| 1013 | * If 'after'==0, returned node will keep elements up to but not including 'offset'. | ||
| 1014 | * The returned node will have elements [0, OFFSET-1]. | ||
| 1015 | * The input node keeps elements [OFFSET, END]. | ||
| 1016 | * | ||
| 1017 | * The input node keeps all elements not taken by the returned node. | ||
| 1018 | * | ||
| 1019 | * Returns newly created node or NULL if split not possible. */ | ||
| 1020 | REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklist *quicklist, quicklistNode *node, | ||
| 1021 | int offset, int after) { | ||
| 1022 | size_t zl_sz = node->sz; | ||
| 1023 | |||
| 1024 | /* New node is detached on return but all callers add it back to quicklist | ||
| 1025 | * so we account its allocation here and below directly on quicklist. */ | ||
| 1026 | quicklistNode *new_node = quicklistCreateNode(quicklist); | ||
| 1027 | new_node->entry = zmalloc(zl_sz); | ||
| 1028 | |||
| 1029 | /* Copy original listpack so we can split it */ | ||
| 1030 | memcpy(new_node->entry, node->entry, zl_sz); | ||
| 1031 | |||
| 1032 | /* Need positive offset for calculating extent below. */ | ||
| 1033 | if (offset < 0) offset = node->count + offset; | ||
| 1034 | |||
| 1035 | /* Ranges to be trimmed: -1 here means "continue deleting until the list ends" */ | ||
| 1036 | int orig_start = after ? offset + 1 : 0; | ||
| 1037 | int orig_extent = after ? -1 : offset; | ||
| 1038 | int new_start = after ? 0 : offset; | ||
| 1039 | int new_extent = after ? offset + 1 : -1; | ||
| 1040 | |||
| 1041 | D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start, | ||
| 1042 | orig_extent, new_start, new_extent); | ||
| 1043 | |||
| 1044 | size_t oldsize = node->sz; | ||
| 1045 | node->entry = lpDeleteRange(node->entry, orig_start, orig_extent); | ||
| 1046 | node->count = lpLength(node->entry); | ||
| 1047 | quicklistNodeUpdateSz(node); | ||
| 1048 | quicklistUpdateAllocSize(quicklist, node->sz, oldsize); | ||
| 1049 | |||
| 1050 | new_node->entry = lpDeleteRange(new_node->entry, new_start, new_extent); | ||
| 1051 | new_node->count = lpLength(new_node->entry); | ||
| 1052 | quicklistNodeUpdateSz(new_node); | ||
| 1053 | quicklistUpdateAllocSize(quicklist, new_node->sz, 0); | ||
| 1054 | |||
| 1055 | D("After split lengths: orig (%d), new (%d)", node->count, new_node->count); | ||
| 1056 | return new_node; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | /* Insert a new entry before or after existing entry 'entry'. | ||
| 1060 | * | ||
| 1061 | * If after==1, the new value is inserted after 'entry', otherwise | ||
| 1062 | * the new value is inserted before 'entry'. */ | ||
| 1063 | REDIS_STATIC void _quicklistInsert(quicklistIter *iter, quicklistEntry *entry, | ||
| 1064 | void *value, const size_t sz, int after) | ||
| 1065 | { | ||
| 1066 | quicklist *quicklist = iter->quicklist; | ||
| 1067 | int full = 0, at_tail = 0, at_head = 0, avail_next = 0, avail_prev = 0; | ||
| 1068 | int fill = quicklist->fill; | ||
| 1069 | quicklistNode *node = entry->node; | ||
| 1070 | quicklistNode *new_node = NULL; | ||
| 1071 | |||
| 1072 | if (!node) { | ||
| 1073 | /* we have no reference node, so let's create only node in the list */ | ||
| 1074 | D("No node given!"); | ||
| 1075 | if (unlikely(isLargeElement(sz, quicklist->fill))) { | ||
| 1076 | __quicklistInsertPlainNode(quicklist, quicklist->tail, value, sz, after); | ||
| 1077 | return; | ||
| 1078 | } | ||
| 1079 | new_node = quicklistCreateNode(quicklist); | ||
| 1080 | new_node->entry = lpPrepend(lpNew(0), value, sz); | ||
| 1081 | quicklistNodeUpdateSz(new_node); | ||
| 1082 | quicklistUpdateAllocSize(quicklist, new_node->sz, 0); | ||
| 1083 | __quicklistInsertNode(quicklist, NULL, new_node, after); | ||
| 1084 | new_node->count++; | ||
| 1085 | quicklist->count++; | ||
| 1086 | return; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | /* Populate accounting flags for easier boolean checks later */ | ||
| 1090 | if (!_quicklistNodeAllowInsert(node, fill, sz)) { | ||
| 1091 | D("Current node is full with count %d with requested fill %d", | ||
| 1092 | node->count, fill); | ||
| 1093 | full = 1; | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | if (after && (entry->offset == node->count - 1 || entry->offset == -1)) { | ||
| 1097 | D("At Tail of current listpack"); | ||
| 1098 | at_tail = 1; | ||
| 1099 | if (_quicklistNodeAllowInsert(node->next, fill, sz)) { | ||
| 1100 | D("Next node is available."); | ||
| 1101 | avail_next = 1; | ||
| 1102 | } | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | if (!after && (entry->offset == 0 || entry->offset == -(node->count))) { | ||
| 1106 | D("At Head"); | ||
| 1107 | at_head = 1; | ||
| 1108 | if (_quicklistNodeAllowInsert(node->prev, fill, sz)) { | ||
| 1109 | D("Prev node is available."); | ||
| 1110 | avail_prev = 1; | ||
| 1111 | } | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | if (unlikely(isLargeElement(sz, quicklist->fill))) { | ||
| 1115 | if (QL_NODE_IS_PLAIN(node) || (at_tail && after) || (at_head && !after)) { | ||
| 1116 | __quicklistInsertPlainNode(quicklist, node, value, sz, after); | ||
| 1117 | } else { | ||
| 1118 | quicklistDecompressNodeForUse(quicklist, node); | ||
| 1119 | new_node = _quicklistSplitNode(quicklist, node, entry->offset, after); | ||
| 1120 | quicklistNode *entry_node = __quicklistCreateNode(quicklist, QUICKLIST_NODE_CONTAINER_PLAIN, value, sz); | ||
| 1121 | __quicklistInsertNode(quicklist, node, entry_node, after); | ||
| 1122 | __quicklistInsertNode(quicklist, entry_node, new_node, after); | ||
| 1123 | quicklist->count++; | ||
| 1124 | } | ||
| 1125 | return; | ||
| 1126 | } | ||
| 1127 | |||
| 1128 | /* Now determine where and how to insert the new element */ | ||
| 1129 | if (!full && after) { | ||
| 1130 | D("Not full, inserting after current position."); | ||
| 1131 | quicklistDecompressNodeForUse(quicklist, node); | ||
| 1132 | node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_AFTER, NULL); | ||
| 1133 | size_t oldsize = node->sz; | ||
| 1134 | quicklistNodeUpdateSz(node); | ||
| 1135 | quicklistUpdateAllocSize(quicklist, node->sz, oldsize); | ||
| 1136 | node->count++; | ||
| 1137 | quicklistRecompressOnly(quicklist, node); | ||
| 1138 | } else if (!full && !after) { | ||
| 1139 | D("Not full, inserting before current position."); | ||
| 1140 | quicklistDecompressNodeForUse(quicklist, node); | ||
| 1141 | node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_BEFORE, NULL); | ||
| 1142 | size_t oldsize = node->sz; | ||
| 1143 | quicklistNodeUpdateSz(node); | ||
| 1144 | quicklistUpdateAllocSize(quicklist, node->sz, oldsize); | ||
| 1145 | node->count++; | ||
| 1146 | quicklistRecompressOnly(quicklist, node); | ||
| 1147 | } else if (full && at_tail && avail_next && after) { | ||
| 1148 | /* If we are: at tail, next has free space, and inserting after: | ||
| 1149 | * - insert entry at head of next node. */ | ||
| 1150 | D("Full and tail, but next isn't full; inserting next node head"); | ||
| 1151 | new_node = node->next; | ||
| 1152 | quicklistDecompressNodeForUse(quicklist, new_node); | ||
| 1153 | new_node->entry = lpPrepend(new_node->entry, value, sz); | ||
| 1154 | size_t oldsize = new_node->sz; | ||
| 1155 | quicklistNodeUpdateSz(new_node); | ||
| 1156 | quicklistUpdateAllocSize(quicklist, new_node->sz, oldsize); | ||
| 1157 | new_node->count++; | ||
| 1158 | quicklistRecompressOnly(quicklist, new_node); | ||
| 1159 | quicklistRecompressOnly(quicklist, node); | ||
| 1160 | } else if (full && at_head && avail_prev && !after) { | ||
| 1161 | /* If we are: at head, previous has free space, and inserting before: | ||
| 1162 | * - insert entry at tail of previous node. */ | ||
| 1163 | D("Full and head, but prev isn't full, inserting prev node tail"); | ||
| 1164 | new_node = node->prev; | ||
| 1165 | quicklistDecompressNodeForUse(quicklist, new_node); | ||
| 1166 | new_node->entry = lpAppend(new_node->entry, value, sz); | ||
| 1167 | size_t oldsize = new_node->sz; | ||
| 1168 | quicklistNodeUpdateSz(new_node); | ||
| 1169 | quicklistUpdateAllocSize(quicklist, new_node->sz, oldsize); | ||
| 1170 | new_node->count++; | ||
| 1171 | quicklistRecompressOnly(quicklist, new_node); | ||
| 1172 | quicklistRecompressOnly(quicklist, node); | ||
| 1173 | } else if (full && ((at_tail && !avail_next && after) || | ||
| 1174 | (at_head && !avail_prev && !after))) { | ||
| 1175 | /* If we are: full, and our prev/next has no available space, then: | ||
| 1176 | * - create new node and attach to quicklist */ | ||
| 1177 | D("\tprovisioning new node..."); | ||
| 1178 | new_node = quicklistCreateNode(quicklist); | ||
| 1179 | new_node->entry = lpPrepend(lpNew(0), value, sz); | ||
| 1180 | quicklistNodeUpdateSz(new_node); | ||
| 1181 | quicklistUpdateAllocSize(quicklist, new_node->sz, 0); | ||
| 1182 | new_node->count++; | ||
| 1183 | __quicklistInsertNode(quicklist, node, new_node, after); | ||
| 1184 | } else if (full) { | ||
| 1185 | /* else, node is full we need to split it. */ | ||
| 1186 | /* covers both after and !after cases */ | ||
| 1187 | D("\tsplitting node..."); | ||
| 1188 | quicklistDecompressNodeForUse(quicklist, node); | ||
| 1189 | new_node = _quicklistSplitNode(quicklist, node, entry->offset, after); | ||
| 1190 | if (after) | ||
| 1191 | new_node->entry = lpPrepend(new_node->entry, value, sz); | ||
| 1192 | else | ||
| 1193 | new_node->entry = lpAppend(new_node->entry, value, sz); | ||
| 1194 | size_t oldsize = new_node->sz; | ||
| 1195 | quicklistNodeUpdateSz(new_node); | ||
| 1196 | quicklistUpdateAllocSize(quicklist, new_node->sz, oldsize); | ||
| 1197 | new_node->count++; | ||
| 1198 | __quicklistInsertNode(quicklist, node, new_node, after); | ||
| 1199 | _quicklistMergeNodes(quicklist, node); | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | quicklist->count++; | ||
| 1203 | |||
| 1204 | /* In any case, we reset iterator to forbid use of iterator after insert. | ||
| 1205 | * Notice: iter->current has been compressed in _quicklistInsert(). */ | ||
| 1206 | resetIterator(iter); | ||
| 1207 | } | ||
| 1208 | |||
| 1209 | void quicklistInsertBefore(quicklistIter *iter, quicklistEntry *entry, | ||
| 1210 | void *value, const size_t sz) | ||
| 1211 | { | ||
| 1212 | _quicklistInsert(iter, entry, value, sz, 0); | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | void quicklistInsertAfter(quicklistIter *iter, quicklistEntry *entry, | ||
| 1216 | void *value, const size_t sz) | ||
| 1217 | { | ||
| 1218 | _quicklistInsert(iter, entry, value, sz, 1); | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | /* Delete a range of elements from the quicklist. | ||
| 1222 | * | ||
| 1223 | * elements may span across multiple quicklistNodes, so we | ||
| 1224 | * have to be careful about tracking where we start and end. | ||
| 1225 | * | ||
| 1226 | * Returns 1 if entries were deleted, 0 if nothing was deleted. */ | ||
| 1227 | int quicklistDelRange(quicklist *quicklist, const long start, | ||
| 1228 | const long count) { | ||
| 1229 | if (count <= 0) | ||
| 1230 | return 0; | ||
| 1231 | |||
| 1232 | unsigned long extent = count; /* range is inclusive of start position */ | ||
| 1233 | |||
| 1234 | if (start >= 0 && extent > (quicklist->count - start)) { | ||
| 1235 | /* if requesting delete more elements than exist, limit to list size. */ | ||
| 1236 | extent = quicklist->count - start; | ||
| 1237 | } else if (start < 0 && extent > (unsigned long)(-start)) { | ||
| 1238 | /* else, if at negative offset, limit max size to rest of list. */ | ||
| 1239 | extent = -start; /* c.f. LREM -29 29; just delete until end. */ | ||
| 1240 | } | ||
| 1241 | |||
| 1242 | quicklistIter iter; | ||
| 1243 | if (!quicklistInitIteratorAtIdx(&iter, quicklist, AL_START_TAIL, start)) | ||
| 1244 | return 0; | ||
| 1245 | |||
| 1246 | D("Quicklist delete request for start %ld, count %ld, extent: %ld", start, | ||
| 1247 | count, extent); | ||
| 1248 | quicklistNode *node = iter.current; | ||
| 1249 | long offset = iter.offset; | ||
| 1250 | quicklistResetIterator(&iter); | ||
| 1251 | |||
| 1252 | /* iterate over next nodes until everything is deleted. */ | ||
| 1253 | while (extent) { | ||
| 1254 | quicklistNode *next = node->next; | ||
| 1255 | |||
| 1256 | unsigned long del; | ||
| 1257 | int delete_entire_node = 0; | ||
| 1258 | if (offset == 0 && extent >= node->count) { | ||
| 1259 | /* If we are deleting more than the count of this node, we | ||
| 1260 | * can just delete the entire node without listpack math. */ | ||
| 1261 | delete_entire_node = 1; | ||
| 1262 | del = node->count; | ||
| 1263 | } else if (offset >= 0 && extent + offset >= node->count) { | ||
| 1264 | /* If deleting more nodes after this one, calculate delete based | ||
| 1265 | * on size of current node. */ | ||
| 1266 | del = node->count - offset; | ||
| 1267 | } else if (offset < 0) { | ||
| 1268 | /* If offset is negative, we are in the first run of this loop | ||
| 1269 | * and we are deleting the entire range | ||
| 1270 | * from this start offset to end of list. Since the Negative | ||
| 1271 | * offset is the number of elements until the tail of the list, | ||
| 1272 | * just use it directly as the deletion count. */ | ||
| 1273 | del = -offset; | ||
| 1274 | |||
| 1275 | /* If the positive offset is greater than the remaining extent, | ||
| 1276 | * we only delete the remaining extent, not the entire offset. | ||
| 1277 | */ | ||
| 1278 | if (del > extent) | ||
| 1279 | del = extent; | ||
| 1280 | } else { | ||
| 1281 | /* else, we are deleting less than the extent of this node, so | ||
| 1282 | * use extent directly. */ | ||
| 1283 | del = extent; | ||
| 1284 | } | ||
| 1285 | |||
| 1286 | D("[%ld]: asking to del: %ld because offset: %ld; (ENTIRE NODE: %d), " | ||
| 1287 | "node count: %u", | ||
| 1288 | extent, del, offset, delete_entire_node, node->count); | ||
| 1289 | |||
| 1290 | if (delete_entire_node || QL_NODE_IS_PLAIN(node)) { | ||
| 1291 | __quicklistDelNode(quicklist, node); | ||
| 1292 | } else { | ||
| 1293 | quicklistDecompressNodeForUse(quicklist, node); | ||
| 1294 | node->entry = lpDeleteRange(node->entry, offset, del); | ||
| 1295 | size_t oldsize = node->sz; | ||
| 1296 | quicklistNodeUpdateSz(node); | ||
| 1297 | quicklistUpdateAllocSize(quicklist, node->sz, oldsize); | ||
| 1298 | node->count -= del; | ||
| 1299 | quicklist->count -= del; | ||
| 1300 | quicklistDeleteIfEmpty(quicklist, node); | ||
| 1301 | if (node) | ||
| 1302 | quicklistRecompressOnly(quicklist, node); | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | extent -= del; | ||
| 1306 | |||
| 1307 | node = next; | ||
| 1308 | |||
| 1309 | offset = 0; | ||
| 1310 | } | ||
| 1311 | return 1; | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | /* Compare a quicklistEntry with a raw value. | ||
| 1315 | * | ||
| 1316 | * If the entry stores a string (entry->value != NULL), perform a binary-safe | ||
| 1317 | * comparison against p2. | ||
| 1318 | * | ||
| 1319 | * If the entry stores an integer (entry->value == NULL), lazily convert p2 to | ||
| 1320 | * a long long using string2ll() once and cache the result using cached_longval | ||
| 1321 | * and cached_valid. | ||
| 1322 | * | ||
| 1323 | * This optimization avoids repeatedly calling string2ll() in tight loops. | ||
| 1324 | * - If cached_valid == NULL: skip caching | ||
| 1325 | * - If cached_valid == 0: conversion attempted | ||
| 1326 | * - If cached_valid == 1/-1: cached result reused | ||
| 1327 | * | ||
| 1328 | * Returns 1 if equal, 0 otherwise. | ||
| 1329 | */ | ||
| 1330 | int quicklistCompare(quicklistEntry *entry, unsigned char *p2, const size_t p2_len, | ||
| 1331 | long long *cached_longval, int *cached_valid) { | ||
| 1332 | if (entry->value) { | ||
| 1333 | return ((entry->sz == p2_len) && (memcmp(entry->value, p2, p2_len) == 0)); | ||
| 1334 | } else { | ||
| 1335 | /* We use string2ll() to get an integer representation of the | ||
| 1336 | * string 'p2' and compare it to 'entry->longval', it's much | ||
| 1337 | * faster than convert integer to string and comparing. */ | ||
| 1338 | if (cached_valid != NULL) { | ||
| 1339 | /* Use caching */ | ||
| 1340 | if (*cached_valid == 0) { | ||
| 1341 | if (string2ll((const char *)p2, p2_len, cached_longval)) { | ||
| 1342 | *cached_valid = 1; | ||
| 1343 | } else { | ||
| 1344 | *cached_valid = -1; | ||
| 1345 | } | ||
| 1346 | } | ||
| 1347 | return (*cached_valid == 1 && entry->longval == *cached_longval); | ||
| 1348 | } else { | ||
| 1349 | /* No caching - direct conversion */ | ||
| 1350 | long long sval; | ||
| 1351 | if (string2ll((const char *)p2, p2_len, &sval)) | ||
| 1352 | return entry->longval == sval; | ||
| 1353 | } | ||
| 1354 | } | ||
| 1355 | return 0; | ||
| 1356 | } | ||
| 1357 | |||
| 1358 | /* Initialize a quicklist iterator 'iter'. After the initialization every | ||
| 1359 | * call to quicklistNext() will return the next element of the quicklist. */ | ||
| 1360 | void quicklistInitIterator(quicklistIter *iter, quicklist *quicklist, int direction) { | ||
| 1361 | if (direction == AL_START_HEAD) { | ||
| 1362 | iter->current = quicklist->head; | ||
| 1363 | iter->offset = 0; | ||
| 1364 | } else if (direction == AL_START_TAIL) { | ||
| 1365 | iter->current = quicklist->tail; | ||
| 1366 | iter->offset = -1; | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | iter->direction = direction; | ||
| 1370 | iter->quicklist = quicklist; | ||
| 1371 | |||
| 1372 | iter->zi = NULL; | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | /* Initialize an iterator at a specific offset 'idx' and make the iterator | ||
| 1376 | * return nodes in 'direction' direction. Returns 1 on success, 0 if index out of range. */ | ||
| 1377 | int quicklistInitIteratorAtIdx(quicklistIter *iter, quicklist *quicklist, | ||
| 1378 | const int direction, const long long idx) | ||
| 1379 | { | ||
| 1380 | quicklistNode *n; | ||
| 1381 | unsigned long long accum = 0; | ||
| 1382 | unsigned long long index; | ||
| 1383 | int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */ | ||
| 1384 | |||
| 1385 | quicklistInitIterator(iter, quicklist, direction); | ||
| 1386 | |||
| 1387 | index = forward ? idx : (-idx) - 1; | ||
| 1388 | if (index >= quicklist->count) { | ||
| 1389 | iter->current = NULL; | ||
| 1390 | return 0; | ||
| 1391 | } | ||
| 1392 | |||
| 1393 | /* Seek in the other direction if that way is shorter. */ | ||
| 1394 | int seek_forward = forward; | ||
| 1395 | unsigned long long seek_index = index; | ||
| 1396 | if (index > (quicklist->count - 1) / 2) { | ||
| 1397 | seek_forward = !forward; | ||
| 1398 | seek_index = quicklist->count - 1 - index; | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | n = seek_forward ? quicklist->head : quicklist->tail; | ||
| 1402 | while (likely(n)) { | ||
| 1403 | if ((accum + n->count) > seek_index) { | ||
| 1404 | break; | ||
| 1405 | } else { | ||
| 1406 | D("Skipping over (%p) %u at accum %lld", (void *)n, n->count, | ||
| 1407 | accum); | ||
| 1408 | accum += n->count; | ||
| 1409 | n = seek_forward ? n->next : n->prev; | ||
| 1410 | } | ||
| 1411 | } | ||
| 1412 | |||
| 1413 | if (!n) { | ||
| 1414 | iter->current = NULL; | ||
| 1415 | return 0; | ||
| 1416 | } | ||
| 1417 | |||
| 1418 | /* Fix accum so it looks like we seeked in the other direction. */ | ||
| 1419 | if (seek_forward != forward) accum = quicklist->count - n->count - accum; | ||
| 1420 | |||
| 1421 | D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n, | ||
| 1422 | accum, index, index - accum, (-index) - 1 + accum); | ||
| 1423 | |||
| 1424 | iter->current = n; | ||
| 1425 | if (forward) { | ||
| 1426 | /* forward = normal head-to-tail offset. */ | ||
| 1427 | iter->offset = index - accum; | ||
| 1428 | } else { | ||
| 1429 | /* reverse = need negative offset for tail-to-head, so undo | ||
| 1430 | * the result of the original index = (-idx) - 1 above. */ | ||
| 1431 | iter->offset = (-index) - 1 + accum; | ||
| 1432 | } | ||
| 1433 | |||
| 1434 | return 1; | ||
| 1435 | } | ||
| 1436 | |||
| 1437 | /* Reset iterator. | ||
| 1438 | * If we still have a valid current node, then re-encode current node. */ | ||
| 1439 | void quicklistResetIterator(quicklistIter *iter) { | ||
| 1440 | if (iter->current) | ||
| 1441 | quicklistCompress(iter->quicklist, iter->current); | ||
| 1442 | } | ||
| 1443 | |||
| 1444 | /* Get next element in iterator. | ||
| 1445 | * | ||
| 1446 | * Note: You must NOT insert into the list while iterating over it. | ||
| 1447 | * You *may* delete from the list while iterating using the | ||
| 1448 | * quicklistDelEntry() function. | ||
| 1449 | * If you insert into the quicklist while iterating, you should | ||
| 1450 | * re-create the iterator after your addition. | ||
| 1451 | * | ||
| 1452 | * iter = quicklistGetIterator(quicklist,<direction>); | ||
| 1453 | * quicklistEntry entry; | ||
| 1454 | * while (quicklistNext(iter, &entry)) { | ||
| 1455 | * if (entry.value) | ||
| 1456 | * [[ use entry.value with entry.sz ]] | ||
| 1457 | * else | ||
| 1458 | * [[ use entry.longval ]] | ||
| 1459 | * } | ||
| 1460 | * | ||
| 1461 | * Populates 'entry' with values for this iteration. | ||
| 1462 | * Returns 0 when iteration is complete or if iteration not possible. | ||
| 1463 | * If return value is 0, the contents of 'entry' are not valid. | ||
| 1464 | */ | ||
| 1465 | int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { | ||
| 1466 | initEntry(entry); | ||
| 1467 | entry->quicklist = iter->quicklist; | ||
| 1468 | entry->node = iter->current; | ||
| 1469 | |||
| 1470 | if (!iter->current) { | ||
| 1471 | D("Returning because current node is NULL"); | ||
| 1472 | return 0; | ||
| 1473 | } | ||
| 1474 | |||
| 1475 | unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL; | ||
| 1476 | int offset_update = 0; | ||
| 1477 | |||
| 1478 | int plain = QL_NODE_IS_PLAIN(iter->current); | ||
| 1479 | if (!iter->zi) { | ||
| 1480 | /* If !zi, use current index. */ | ||
| 1481 | quicklistDecompressNodeForUse(iter->quicklist, iter->current); | ||
| 1482 | if (unlikely(plain)) | ||
| 1483 | iter->zi = iter->current->entry; | ||
| 1484 | else | ||
| 1485 | iter->zi = lpSeek(iter->current->entry, iter->offset); | ||
| 1486 | } else if (unlikely(plain)) { | ||
| 1487 | iter->zi = NULL; | ||
| 1488 | } else { | ||
| 1489 | /* else, use existing iterator offset and get prev/next as necessary. */ | ||
| 1490 | if (iter->direction == AL_START_HEAD) { | ||
| 1491 | nextFn = lpNext; | ||
| 1492 | offset_update = 1; | ||
| 1493 | } else if (iter->direction == AL_START_TAIL) { | ||
| 1494 | nextFn = lpPrev; | ||
| 1495 | offset_update = -1; | ||
| 1496 | } | ||
| 1497 | iter->zi = nextFn(iter->current->entry, iter->zi); | ||
| 1498 | iter->offset += offset_update; | ||
| 1499 | } | ||
| 1500 | |||
| 1501 | entry->zi = iter->zi; | ||
| 1502 | entry->offset = iter->offset; | ||
| 1503 | |||
| 1504 | if (iter->zi) { | ||
| 1505 | if (unlikely(plain)) { | ||
| 1506 | entry->value = entry->node->entry; | ||
| 1507 | entry->sz = entry->node->sz; | ||
| 1508 | return 1; | ||
| 1509 | } | ||
| 1510 | /* Populate value from existing listpack position */ | ||
| 1511 | unsigned int sz = 0; | ||
| 1512 | entry->value = lpGetValue(entry->zi, &sz, &entry->longval); | ||
| 1513 | entry->sz = sz; | ||
| 1514 | return 1; | ||
| 1515 | } else { | ||
| 1516 | /* We ran out of listpack entries. | ||
| 1517 | * Pick next node, update offset, then re-run retrieval. */ | ||
| 1518 | quicklistCompress(iter->quicklist, iter->current); | ||
| 1519 | if (iter->direction == AL_START_HEAD) { | ||
| 1520 | /* Forward traversal */ | ||
| 1521 | D("Jumping to start of next node"); | ||
| 1522 | iter->current = iter->current->next; | ||
| 1523 | iter->offset = 0; | ||
| 1524 | } else if (iter->direction == AL_START_TAIL) { | ||
| 1525 | /* Reverse traversal */ | ||
| 1526 | D("Jumping to end of previous node"); | ||
| 1527 | iter->current = iter->current->prev; | ||
| 1528 | iter->offset = -1; | ||
| 1529 | } | ||
| 1530 | iter->zi = NULL; | ||
| 1531 | return quicklistNext(iter, entry); | ||
| 1532 | } | ||
| 1533 | } | ||
| 1534 | |||
| 1535 | /* Sets the direction of a quicklist iterator. */ | ||
| 1536 | void quicklistSetDirection(quicklistIter *iter, int direction) { | ||
| 1537 | iter->direction = direction; | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | /* Duplicate the quicklist. | ||
| 1541 | * On success a copy of the original quicklist is returned. | ||
| 1542 | * | ||
| 1543 | * The original quicklist both on success or error is never modified. | ||
| 1544 | * | ||
| 1545 | * Returns newly allocated quicklist. */ | ||
| 1546 | quicklist *quicklistDup(quicklist *orig) { | ||
| 1547 | quicklist *copy; | ||
| 1548 | |||
| 1549 | copy = quicklistNew(orig->fill, orig->compress); | ||
| 1550 | |||
| 1551 | for (quicklistNode *current = orig->head; current; | ||
| 1552 | current = current->next) { | ||
| 1553 | quicklistNode *node = quicklistCreateNode(copy); | ||
| 1554 | |||
| 1555 | if (current->encoding == QUICKLIST_NODE_ENCODING_LZF) { | ||
| 1556 | quicklistLZF *lzf = (quicklistLZF *)current->entry; | ||
| 1557 | size_t lzf_sz = sizeof(*lzf) + lzf->sz; | ||
| 1558 | node->entry = zmalloc(lzf_sz); | ||
| 1559 | memcpy(node->entry, current->entry, lzf_sz); | ||
| 1560 | copy->alloc_size += lzf_sz; | ||
| 1561 | } else if (current->encoding == QUICKLIST_NODE_ENCODING_RAW) { | ||
| 1562 | node->entry = zmalloc(current->sz); | ||
| 1563 | memcpy(node->entry, current->entry, current->sz); | ||
| 1564 | copy->alloc_size += current->sz; | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | node->count = current->count; | ||
| 1568 | node->sz = current->sz; | ||
| 1569 | node->encoding = current->encoding; | ||
| 1570 | node->container = current->container; | ||
| 1571 | copy->count += node->count; | ||
| 1572 | |||
| 1573 | _quicklistInsertNodeAfter(copy, copy->tail, node); | ||
| 1574 | } | ||
| 1575 | |||
| 1576 | /* copy->count must equal orig->count here */ | ||
| 1577 | return copy; | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | /* Populate 'entry' with the element at the specified zero-based index | ||
| 1581 | * where 0 is the head, 1 is the element next to head | ||
| 1582 | * and so on. Negative integers are used in order to count | ||
| 1583 | * from the tail, -1 is the last element, -2 the penultimate | ||
| 1584 | * and so on. If the index is out of range 0 is returned. | ||
| 1585 | * | ||
| 1586 | * Returns 1 if iterator initialized at specific offset 'idx' and element found | ||
| 1587 | * Returns 0 if element not found */ | ||
| 1588 | int quicklistInitIteratorEntryAtIdx(quicklistIter *iter, quicklist *quicklist, | ||
| 1589 | const long long idx, quicklistEntry *entry) | ||
| 1590 | { | ||
| 1591 | if (!quicklistInitIteratorAtIdx(iter, quicklist, AL_START_TAIL, idx)) | ||
| 1592 | return 0; | ||
| 1593 | assert(quicklistNext(iter, entry)); | ||
| 1594 | return 1; | ||
| 1595 | } | ||
| 1596 | |||
| 1597 | static void quicklistRotatePlain(quicklist *quicklist) { | ||
| 1598 | quicklistNode *new_head = quicklist->tail; | ||
| 1599 | quicklistNode *new_tail = quicklist->tail->prev; | ||
| 1600 | quicklist->head->prev = new_head; | ||
| 1601 | new_tail->next = NULL; | ||
| 1602 | new_head->next = quicklist->head; | ||
| 1603 | new_head->prev = NULL; | ||
| 1604 | quicklist->head = new_head; | ||
| 1605 | quicklist->tail = new_tail; | ||
| 1606 | } | ||
| 1607 | |||
| 1608 | /* Rotate quicklist by moving the tail element to the head. */ | ||
| 1609 | void quicklistRotate(quicklist *quicklist) { | ||
| 1610 | if (quicklist->count <= 1) | ||
| 1611 | return; | ||
| 1612 | |||
| 1613 | if (unlikely(QL_NODE_IS_PLAIN(quicklist->tail))) { | ||
| 1614 | quicklistRotatePlain(quicklist); | ||
| 1615 | return; | ||
| 1616 | } | ||
| 1617 | |||
| 1618 | /* First, get the tail entry */ | ||
| 1619 | unsigned char *p = lpSeek(quicklist->tail->entry, -1); | ||
| 1620 | unsigned char *value, *tmp; | ||
| 1621 | long long longval; | ||
| 1622 | unsigned int sz; | ||
| 1623 | char longstr[32] = {0}; | ||
| 1624 | tmp = lpGetValue(p, &sz, &longval); | ||
| 1625 | |||
| 1626 | /* If value found is NULL, then lpGet populated longval instead */ | ||
| 1627 | if (!tmp) { | ||
| 1628 | /* Write the longval as a string so we can re-add it */ | ||
| 1629 | sz = ll2string(longstr, sizeof(longstr), longval); | ||
| 1630 | value = (unsigned char *)longstr; | ||
| 1631 | } else if (quicklist->len == 1) { | ||
| 1632 | /* Copy buffer since there could be a memory overlap when move | ||
| 1633 | * entity from tail to head in the same listpack. */ | ||
| 1634 | value = zmalloc(sz); | ||
| 1635 | memcpy(value, tmp, sz); | ||
| 1636 | } else { | ||
| 1637 | value = tmp; | ||
| 1638 | } | ||
| 1639 | |||
| 1640 | /* Add tail entry to head (must happen before tail is deleted). */ | ||
| 1641 | quicklistPushHead(quicklist, value, sz); | ||
| 1642 | |||
| 1643 | /* If quicklist has only one node, the head listpack is also the | ||
| 1644 | * tail listpack and PushHead() could have reallocated our single listpack, | ||
| 1645 | * which would make our pre-existing 'p' unusable. */ | ||
| 1646 | if (quicklist->len == 1) { | ||
| 1647 | p = lpSeek(quicklist->tail->entry, -1); | ||
| 1648 | } | ||
| 1649 | |||
| 1650 | /* Remove tail entry. */ | ||
| 1651 | quicklistDelIndex(quicklist, quicklist->tail, &p); | ||
| 1652 | if (value != (unsigned char*)longstr && value != tmp) | ||
| 1653 | zfree(value); | ||
| 1654 | } | ||
| 1655 | |||
| 1656 | /* pop from quicklist and return result in 'data' ptr. Value of 'data' | ||
| 1657 | * is the return value of 'saver' function pointer if the data is NOT a number. | ||
| 1658 | * | ||
| 1659 | * If the quicklist element is a long long, then the return value is returned in | ||
| 1660 | * 'sval'. | ||
| 1661 | * | ||
| 1662 | * Return value of 0 means no elements available. | ||
| 1663 | * Return value of 1 means check 'data' and 'sval' for values. | ||
| 1664 | * If 'data' is set, use 'data' and 'sz'. Otherwise, use 'sval'. */ | ||
| 1665 | int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data, | ||
| 1666 | size_t *sz, long long *sval, | ||
| 1667 | void *(*saver)(unsigned char *data, size_t sz)) { | ||
| 1668 | unsigned char *p; | ||
| 1669 | unsigned char *vstr; | ||
| 1670 | unsigned int vlen; | ||
| 1671 | long long vlong; | ||
| 1672 | int pos = (where == QUICKLIST_HEAD) ? 0 : -1; | ||
| 1673 | |||
| 1674 | if (quicklist->count == 0) | ||
| 1675 | return 0; | ||
| 1676 | |||
| 1677 | if (data) | ||
| 1678 | *data = NULL; | ||
| 1679 | if (sz) | ||
| 1680 | *sz = 0; | ||
| 1681 | if (sval) | ||
| 1682 | *sval = -123456789; | ||
| 1683 | |||
| 1684 | quicklistNode *node; | ||
| 1685 | if (where == QUICKLIST_HEAD && quicklist->head) { | ||
| 1686 | node = quicklist->head; | ||
| 1687 | } else if (where == QUICKLIST_TAIL && quicklist->tail) { | ||
| 1688 | node = quicklist->tail; | ||
| 1689 | } else { | ||
| 1690 | return 0; | ||
| 1691 | } | ||
| 1692 | |||
| 1693 | /* The head and tail should never be compressed */ | ||
| 1694 | assert(node->encoding != QUICKLIST_NODE_ENCODING_LZF); | ||
| 1695 | |||
| 1696 | if (unlikely(QL_NODE_IS_PLAIN(node))) { | ||
| 1697 | if (data) | ||
| 1698 | *data = saver(node->entry, node->sz); | ||
| 1699 | if (sz) | ||
| 1700 | *sz = node->sz; | ||
| 1701 | quicklistDelIndex(quicklist, node, NULL); | ||
| 1702 | return 1; | ||
| 1703 | } | ||
| 1704 | |||
| 1705 | p = lpSeek(node->entry, pos); | ||
| 1706 | vstr = lpGetValue(p, &vlen, &vlong); | ||
| 1707 | if (vstr) { | ||
| 1708 | if (data) | ||
| 1709 | *data = saver(vstr, vlen); | ||
| 1710 | if (sz) | ||
| 1711 | *sz = vlen; | ||
| 1712 | } else { | ||
| 1713 | if (data) | ||
| 1714 | *data = NULL; | ||
| 1715 | if (sval) | ||
| 1716 | *sval = vlong; | ||
| 1717 | } | ||
| 1718 | quicklistDelIndex(quicklist, node, &p); | ||
| 1719 | return 1; | ||
| 1720 | } | ||
| 1721 | |||
| 1722 | /* Return a malloc'd copy of data passed in */ | ||
| 1723 | REDIS_STATIC void *_quicklistSaver(unsigned char *data, size_t sz) { | ||
| 1724 | unsigned char *vstr; | ||
| 1725 | if (data) { | ||
| 1726 | vstr = zmalloc(sz); | ||
| 1727 | memcpy(vstr, data, sz); | ||
| 1728 | return vstr; | ||
| 1729 | } | ||
| 1730 | return NULL; | ||
| 1731 | } | ||
| 1732 | |||
| 1733 | /* Default pop function | ||
| 1734 | * | ||
| 1735 | * Returns malloc'd value from quicklist */ | ||
| 1736 | int quicklistPop(quicklist *quicklist, int where, unsigned char **data, | ||
| 1737 | size_t *sz, long long *slong) { | ||
| 1738 | unsigned char *vstr = NULL; | ||
| 1739 | size_t vlen = 0; | ||
| 1740 | long long vlong = 0; | ||
| 1741 | if (quicklist->count == 0) | ||
| 1742 | return 0; | ||
| 1743 | int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong, | ||
| 1744 | _quicklistSaver); | ||
| 1745 | if (data) | ||
| 1746 | *data = vstr; | ||
| 1747 | if (slong) | ||
| 1748 | *slong = vlong; | ||
| 1749 | if (sz) | ||
| 1750 | *sz = vlen; | ||
| 1751 | return ret; | ||
| 1752 | } | ||
| 1753 | |||
| 1754 | /* Wrapper to allow argument-based switching between HEAD/TAIL pop */ | ||
| 1755 | void quicklistPush(quicklist *quicklist, void *value, const size_t sz, | ||
| 1756 | int where) { | ||
| 1757 | /* The head and tail should never be compressed (we don't attempt to decompress them) */ | ||
| 1758 | if (quicklist->head) | ||
| 1759 | assert(quicklist->head->encoding != QUICKLIST_NODE_ENCODING_LZF); | ||
| 1760 | if (quicklist->tail) | ||
| 1761 | assert(quicklist->tail->encoding != QUICKLIST_NODE_ENCODING_LZF); | ||
| 1762 | |||
| 1763 | if (where == QUICKLIST_HEAD) { | ||
| 1764 | quicklistPushHead(quicklist, value, sz); | ||
| 1765 | } else if (where == QUICKLIST_TAIL) { | ||
| 1766 | quicklistPushTail(quicklist, value, sz); | ||
| 1767 | } | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | /* Print info of quicklist which is used in debugCommand. */ | ||
| 1771 | void quicklistRepr(unsigned char *ql, int full) { | ||
| 1772 | int i = 0; | ||
| 1773 | quicklist *quicklist = (struct quicklist*) ql; | ||
| 1774 | printf("{count : %ld}\n", quicklist->count); | ||
| 1775 | printf("{len : %ld}\n", quicklist->len); | ||
| 1776 | printf("{fill : %d}\n", quicklist->fill); | ||
| 1777 | printf("{compress : %d}\n", quicklist->compress); | ||
| 1778 | printf("{bookmark_count : %d}\n", quicklist->bookmark_count); | ||
| 1779 | quicklistNode* node = quicklist->head; | ||
| 1780 | |||
| 1781 | while(node != NULL) { | ||
| 1782 | printf("{quicklist node(%d)\n", i++); | ||
| 1783 | printf("{container : %s, encoding: %s, size: %zu, count: %d, recompress: %d, attempted_compress: %d}\n", | ||
| 1784 | QL_NODE_IS_PLAIN(node) ? "PLAIN": "PACKED", | ||
| 1785 | (node->encoding == QUICKLIST_NODE_ENCODING_RAW) ? "RAW": "LZF", | ||
| 1786 | node->sz, | ||
| 1787 | node->count, | ||
| 1788 | node->recompress, | ||
| 1789 | node->attempted_compress); | ||
| 1790 | |||
| 1791 | if (full) { | ||
| 1792 | quicklistDecompressNode(quicklist, node); | ||
| 1793 | if (node->container == QUICKLIST_NODE_CONTAINER_PACKED) { | ||
| 1794 | printf("{ listpack:\n"); | ||
| 1795 | lpRepr(node->entry); | ||
| 1796 | printf("}\n"); | ||
| 1797 | |||
| 1798 | } else if (QL_NODE_IS_PLAIN(node)) { | ||
| 1799 | printf("{ entry : %s }\n", node->entry); | ||
| 1800 | } | ||
| 1801 | printf("}\n"); | ||
| 1802 | quicklistRecompressOnly(quicklist, node); | ||
| 1803 | } | ||
| 1804 | node = node->next; | ||
| 1805 | } | ||
| 1806 | } | ||
| 1807 | |||
| 1808 | /* Create or update a bookmark in the list which will be updated to the next node | ||
| 1809 | * automatically when the one referenced gets deleted. | ||
| 1810 | * Returns 1 on success (creation of new bookmark or override of an existing one). | ||
| 1811 | * Returns 0 on failure (reached the maximum supported number of bookmarks). | ||
| 1812 | * NOTE: use short simple names, so that string compare on find is quick. | ||
| 1813 | * NOTE: bookmark creation may re-allocate the quicklist, so the input pointer | ||
| 1814 | may change and it's the caller responsibility to update the reference. | ||
| 1815 | */ | ||
| 1816 | int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) { | ||
| 1817 | quicklist *ql = *ql_ref; | ||
| 1818 | if (ql->bookmark_count >= QL_MAX_BM) | ||
| 1819 | return 0; | ||
| 1820 | quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); | ||
| 1821 | if (bm) { | ||
| 1822 | bm->node = node; | ||
| 1823 | return 1; | ||
| 1824 | } | ||
| 1825 | size_t new_size, old_size; | ||
| 1826 | ql = zrealloc_usable(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark), | ||
| 1827 | &new_size, &old_size); | ||
| 1828 | *ql_ref = ql; | ||
| 1829 | ql->bookmarks[ql->bookmark_count].node = node; | ||
| 1830 | size_t name_sz; | ||
| 1831 | ql->bookmarks[ql->bookmark_count].name = zstrdup_usable(name, &name_sz); | ||
| 1832 | ql->bookmark_count++; | ||
| 1833 | quicklistUpdateAllocSize(ql, new_size + name_sz, old_size); | ||
| 1834 | return 1; | ||
| 1835 | } | ||
| 1836 | |||
| 1837 | /* Find the quicklist node referenced by a named bookmark. | ||
| 1838 | * When the bookmarked node is deleted the bookmark is updated to the next node, | ||
| 1839 | * and if that's the last node, the bookmark is deleted (so find returns NULL). */ | ||
| 1840 | quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) { | ||
| 1841 | quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); | ||
| 1842 | if (!bm) return NULL; | ||
| 1843 | return bm->node; | ||
| 1844 | } | ||
| 1845 | |||
| 1846 | /* Delete a named bookmark. | ||
| 1847 | * returns 0 if bookmark was not found, and 1 if deleted. | ||
| 1848 | * Note that the bookmark memory is not freed yet, and is kept for future use. */ | ||
| 1849 | int quicklistBookmarkDelete(quicklist *ql, const char *name) { | ||
| 1850 | quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); | ||
| 1851 | if (!bm) | ||
| 1852 | return 0; | ||
| 1853 | _quicklistBookmarkDelete(ql, bm); | ||
| 1854 | return 1; | ||
| 1855 | } | ||
| 1856 | |||
| 1857 | quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) { | ||
| 1858 | unsigned i; | ||
| 1859 | for (i=0; i<ql->bookmark_count; i++) { | ||
| 1860 | if (!strcmp(ql->bookmarks[i].name, name)) { | ||
| 1861 | return &ql->bookmarks[i]; | ||
| 1862 | } | ||
| 1863 | } | ||
| 1864 | return NULL; | ||
| 1865 | } | ||
| 1866 | |||
| 1867 | quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) { | ||
| 1868 | unsigned i; | ||
| 1869 | for (i=0; i<ql->bookmark_count; i++) { | ||
| 1870 | if (ql->bookmarks[i].node == node) { | ||
| 1871 | return &ql->bookmarks[i]; | ||
| 1872 | } | ||
| 1873 | } | ||
| 1874 | return NULL; | ||
| 1875 | } | ||
| 1876 | |||
| 1877 | void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) { | ||
| 1878 | int index = bm - ql->bookmarks; | ||
| 1879 | size_t name_sz; | ||
| 1880 | zfree_usable(bm->name, &name_sz); | ||
| 1881 | ql->bookmark_count--; | ||
| 1882 | ql->alloc_size -= name_sz; | ||
| 1883 | memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm)); | ||
| 1884 | /* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance, | ||
| 1885 | * it may be re-used later (a call to realloc may NOP). */ | ||
| 1886 | } | ||
| 1887 | |||
| 1888 | void quicklistBookmarksClear(quicklist *ql) { | ||
| 1889 | size_t name_sz; | ||
| 1890 | while (ql->bookmark_count) { | ||
| 1891 | zfree_usable(ql->bookmarks[--ql->bookmark_count].name, &name_sz); | ||
| 1892 | ql->alloc_size -= name_sz; | ||
| 1893 | } | ||
| 1894 | /* NOTE: We do not shrink (realloc) the quick list. main use case for this | ||
| 1895 | * function is just before releasing the allocation. */ | ||
| 1896 | } | ||
| 1897 | |||
| 1898 | /* The rest of this file is test cases and test helpers. */ | ||
| 1899 | #ifdef REDIS_TEST | ||
| 1900 | #include <stdint.h> | ||
| 1901 | #include <sys/time.h> | ||
| 1902 | #include "testhelp.h" | ||
| 1903 | #include <stdlib.h> | ||
| 1904 | |||
| 1905 | #define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__) | ||
| 1906 | |||
| 1907 | #define ERROR \ | ||
| 1908 | do { \ | ||
| 1909 | printf("\tERROR!\n"); \ | ||
| 1910 | err++; \ | ||
| 1911 | } while (0) | ||
| 1912 | |||
| 1913 | #define ERR(x, ...) \ | ||
| 1914 | do { \ | ||
| 1915 | printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \ | ||
| 1916 | printf("ERROR! " x "\n", __VA_ARGS__); \ | ||
| 1917 | err++; \ | ||
| 1918 | } while (0) | ||
| 1919 | |||
| 1920 | #define TEST(name) printf("test — %s\n", name); | ||
| 1921 | #define TEST_DESC(name, ...) printf("test — " name "\n", __VA_ARGS__); | ||
| 1922 | |||
| 1923 | #define QL_TEST_VERBOSE 0 | ||
| 1924 | |||
| 1925 | #define UNUSED(x) (void)(x) | ||
| 1926 | static void ql_info(quicklist *ql) { | ||
| 1927 | #if QL_TEST_VERBOSE | ||
| 1928 | printf("Container length: %lu\n", ql->len); | ||
| 1929 | printf("Container size: %lu\n", ql->count); | ||
| 1930 | if (ql->head) | ||
| 1931 | printf("\t(zsize head: %lu)\n", lpLength(ql->head->entry)); | ||
| 1932 | if (ql->tail) | ||
| 1933 | printf("\t(zsize tail: %lu)\n", lpLength(ql->tail->entry)); | ||
| 1934 | printf("\n"); | ||
| 1935 | #else | ||
| 1936 | UNUSED(ql); | ||
| 1937 | #endif | ||
| 1938 | } | ||
| 1939 | |||
| 1940 | /* Return the UNIX time in microseconds */ | ||
| 1941 | static long long ustime(void) { | ||
| 1942 | struct timeval tv; | ||
| 1943 | long long ust; | ||
| 1944 | |||
| 1945 | gettimeofday(&tv, NULL); | ||
| 1946 | ust = ((long long)tv.tv_sec) * 1000000; | ||
| 1947 | ust += tv.tv_usec; | ||
| 1948 | return ust; | ||
| 1949 | } | ||
| 1950 | |||
| 1951 | /* Return the UNIX time in milliseconds */ | ||
| 1952 | static long long mstime(void) { return ustime() / 1000; } | ||
| 1953 | |||
| 1954 | /* Iterate over an entire quicklist. | ||
| 1955 | * Print the list if 'print' == 1. | ||
| 1956 | * | ||
| 1957 | * Returns physical count of elements found by iterating over the list. */ | ||
| 1958 | static int _itrprintr(quicklist *ql, int print, int forward) { | ||
| 1959 | quicklistIter iter; | ||
| 1960 | quicklistEntry entry; | ||
| 1961 | quicklistInitIterator(&iter, ql, forward ? AL_START_HEAD : AL_START_TAIL); | ||
| 1962 | int i = 0; | ||
| 1963 | int p = 0; | ||
| 1964 | quicklistNode *prev = NULL; | ||
| 1965 | while (quicklistNext(&iter, &entry)) { | ||
| 1966 | if (entry.node != prev) { | ||
| 1967 | /* Count the number of list nodes too */ | ||
| 1968 | p++; | ||
| 1969 | prev = entry.node; | ||
| 1970 | } | ||
| 1971 | if (print) { | ||
| 1972 | int size = (entry.sz > (1<<20)) ? 1<<20 : entry.sz; | ||
| 1973 | printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, size, | ||
| 1974 | (char *)entry.value, entry.longval); | ||
| 1975 | } | ||
| 1976 | i++; | ||
| 1977 | } | ||
| 1978 | quicklistResetIterator(&iter); | ||
| 1979 | return i; | ||
| 1980 | } | ||
| 1981 | static int itrprintr(quicklist *ql, int print) { | ||
| 1982 | return _itrprintr(ql, print, 1); | ||
| 1983 | } | ||
| 1984 | |||
| 1985 | static int itrprintr_rev(quicklist *ql, int print) { | ||
| 1986 | return _itrprintr(ql, print, 0); | ||
| 1987 | } | ||
| 1988 | |||
| 1989 | #define ql_verify(a, b, c, d, e) \ | ||
| 1990 | do { \ | ||
| 1991 | err += _ql_verify((a), (b), (c), (d), (e)); \ | ||
| 1992 | } while (0) | ||
| 1993 | |||
| 1994 | static int _ql_verify_compress(quicklist *ql) { | ||
| 1995 | int errors = 0; | ||
| 1996 | if (quicklistAllowsCompression(ql)) { | ||
| 1997 | quicklistNode *node = ql->head; | ||
| 1998 | unsigned int low_raw = ql->compress; | ||
| 1999 | unsigned int high_raw = ql->len - ql->compress; | ||
| 2000 | |||
| 2001 | for (unsigned int at = 0; at < ql->len; at++, node = node->next) { | ||
| 2002 | if (node && (at < low_raw || at >= high_raw)) { | ||
| 2003 | if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) { | ||
| 2004 | yell("Incorrect compression: node %d is " | ||
| 2005 | "compressed at depth %d ((%u, %u); total " | ||
| 2006 | "nodes: %lu; size: %zu; recompress: %d)", | ||
| 2007 | at, ql->compress, low_raw, high_raw, ql->len, node->sz, | ||
| 2008 | node->recompress); | ||
| 2009 | errors++; | ||
| 2010 | } | ||
| 2011 | } else { | ||
| 2012 | if (node->encoding != QUICKLIST_NODE_ENCODING_LZF && | ||
| 2013 | !node->attempted_compress) { | ||
| 2014 | yell("Incorrect non-compression: node %d is NOT " | ||
| 2015 | "compressed at depth %d ((%u, %u); total " | ||
| 2016 | "nodes: %lu; size: %zu; recompress: %d; attempted: %d)", | ||
| 2017 | at, ql->compress, low_raw, high_raw, ql->len, node->sz, | ||
| 2018 | node->recompress, node->attempted_compress); | ||
| 2019 | errors++; | ||
| 2020 | } | ||
| 2021 | } | ||
| 2022 | } | ||
| 2023 | } | ||
| 2024 | return errors; | ||
| 2025 | } | ||
| 2026 | |||
| 2027 | static int _ql_verify_alloc_size(quicklist *ql) { | ||
| 2028 | int errors = 0; | ||
| 2029 | size_t alloc_size = zmalloc_usable_size(ql); | ||
| 2030 | |||
| 2031 | quicklistNode* node = ql->head; | ||
| 2032 | while (node != NULL) { | ||
| 2033 | alloc_size += zmalloc_usable_size(node); | ||
| 2034 | if (node->encoding == QUICKLIST_NODE_ENCODING_LZF) { | ||
| 2035 | quicklistLZF *lzf = (quicklistLZF *)node->entry; | ||
| 2036 | alloc_size += sizeof(*lzf) + lzf->sz; | ||
| 2037 | } else { | ||
| 2038 | alloc_size += node->sz; | ||
| 2039 | } | ||
| 2040 | node = node->next; | ||
| 2041 | } | ||
| 2042 | |||
| 2043 | for (unsigned i = 0; i < ql->bookmark_count; i++) { | ||
| 2044 | alloc_size += zmalloc_usable_size(ql->bookmarks[i].name); | ||
| 2045 | } | ||
| 2046 | |||
| 2047 | if (ql->alloc_size != alloc_size) { | ||
| 2048 | yell("quicklist alloc_size wrong: expected %zu, got %zu", alloc_size, ql->alloc_size); | ||
| 2049 | errors++; | ||
| 2050 | } | ||
| 2051 | |||
| 2052 | return errors; | ||
| 2053 | } | ||
| 2054 | |||
| 2055 | /* Verify list metadata matches physical list contents. */ | ||
| 2056 | static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count, | ||
| 2057 | uint32_t head_count, uint32_t tail_count) { | ||
| 2058 | int errors = 0; | ||
| 2059 | |||
| 2060 | ql_info(ql); | ||
| 2061 | if (len != ql->len) { | ||
| 2062 | yell("quicklist length wrong: expected %d, got %lu", len, ql->len); | ||
| 2063 | errors++; | ||
| 2064 | } | ||
| 2065 | |||
| 2066 | if (count != ql->count) { | ||
| 2067 | yell("quicklist count wrong: expected %d, got %lu", count, ql->count); | ||
| 2068 | errors++; | ||
| 2069 | } | ||
| 2070 | |||
| 2071 | int loopr = itrprintr(ql, 0); | ||
| 2072 | if (loopr != (int)ql->count) { | ||
| 2073 | yell("quicklist cached count not match actual count: expected %lu, got " | ||
| 2074 | "%d", | ||
| 2075 | ql->count, loopr); | ||
| 2076 | errors++; | ||
| 2077 | } | ||
| 2078 | |||
| 2079 | int rloopr = itrprintr_rev(ql, 0); | ||
| 2080 | if (loopr != rloopr) { | ||
| 2081 | yell("quicklist has different forward count than reverse count! " | ||
| 2082 | "Forward count is %d, reverse count is %d.", | ||
| 2083 | loopr, rloopr); | ||
| 2084 | errors++; | ||
| 2085 | } | ||
| 2086 | |||
| 2087 | if (ql->len == 0 && !errors) { | ||
| 2088 | return errors; | ||
| 2089 | } | ||
| 2090 | |||
| 2091 | if (ql->head && head_count != ql->head->count && | ||
| 2092 | head_count != lpLength(ql->head->entry)) { | ||
| 2093 | yell("quicklist head count wrong: expected %d, " | ||
| 2094 | "got cached %d vs. actual %lu", | ||
| 2095 | head_count, ql->head->count, lpLength(ql->head->entry)); | ||
| 2096 | errors++; | ||
| 2097 | } | ||
| 2098 | |||
| 2099 | if (ql->tail && tail_count != ql->tail->count && | ||
| 2100 | tail_count != lpLength(ql->tail->entry)) { | ||
| 2101 | yell("quicklist tail count wrong: expected %d, " | ||
| 2102 | "got cached %u vs. actual %lu", | ||
| 2103 | tail_count, ql->tail->count, lpLength(ql->tail->entry)); | ||
| 2104 | errors++; | ||
| 2105 | } | ||
| 2106 | |||
| 2107 | errors += _ql_verify_alloc_size(ql); | ||
| 2108 | errors += _ql_verify_compress(ql); | ||
| 2109 | return errors; | ||
| 2110 | } | ||
| 2111 | |||
| 2112 | /* Reset iterator and verify compress correctly. */ | ||
| 2113 | static void ql_reset_iterator(quicklistIter *iter) { | ||
| 2114 | quicklist *ql = NULL; | ||
| 2115 | if (iter) ql = iter->quicklist; | ||
| 2116 | quicklistResetIterator(iter); | ||
| 2117 | if (ql) assert(!_ql_verify_compress(ql)); | ||
| 2118 | } | ||
| 2119 | |||
| 2120 | /* Generate new string concatenating integer i against string 'prefix' */ | ||
| 2121 | static char *genstr(char *prefix, int i) { | ||
| 2122 | static char result[64] = {0}; | ||
| 2123 | snprintf(result, sizeof(result), "%s%d", prefix, i); | ||
| 2124 | return result; | ||
| 2125 | } | ||
| 2126 | |||
| 2127 | static void randstring(unsigned char *target, size_t sz) { | ||
| 2128 | size_t p = 0; | ||
| 2129 | int minval, maxval; | ||
| 2130 | switch(rand() % 3) { | ||
| 2131 | case 0: | ||
| 2132 | minval = 'a'; | ||
| 2133 | maxval = 'z'; | ||
| 2134 | break; | ||
| 2135 | case 1: | ||
| 2136 | minval = '0'; | ||
| 2137 | maxval = '9'; | ||
| 2138 | break; | ||
| 2139 | case 2: | ||
| 2140 | minval = 'A'; | ||
| 2141 | maxval = 'Z'; | ||
| 2142 | break; | ||
| 2143 | default: | ||
| 2144 | assert(NULL); | ||
| 2145 | } | ||
| 2146 | |||
| 2147 | while(p < sz) | ||
| 2148 | target[p++] = minval+rand()%(maxval-minval+1); | ||
| 2149 | } | ||
| 2150 | |||
| 2151 | /* main test, but callable from other files */ | ||
| 2152 | int quicklistTest(int argc, char *argv[], int flags) { | ||
| 2153 | UNUSED(argc); | ||
| 2154 | UNUSED(argv); | ||
| 2155 | |||
| 2156 | int accurate = (flags & REDIS_TEST_ACCURATE); | ||
| 2157 | unsigned int err = 0; | ||
| 2158 | int optimize_start = | ||
| 2159 | -(int)(sizeof(optimization_level) / sizeof(*optimization_level)); | ||
| 2160 | |||
| 2161 | printf("Starting optimization offset at: %d\n", optimize_start); | ||
| 2162 | |||
| 2163 | int options[] = {0, 1, 2, 3, 4, 5, 6, 10}; | ||
| 2164 | int fills[] = {-5, -4, -3, -2, -1, 0, | ||
| 2165 | 1, 2, 32, 66, 128, 999}; | ||
| 2166 | size_t option_count = sizeof(options) / sizeof(*options); | ||
| 2167 | int fill_count = (int)(sizeof(fills) / sizeof(*fills)); | ||
| 2168 | long long runtime[option_count]; | ||
| 2169 | |||
| 2170 | for (int _i = 0; _i < (int)option_count; _i++) { | ||
| 2171 | printf("Testing Compression option %d\n", options[_i]); | ||
| 2172 | long long start = mstime(); | ||
| 2173 | quicklistIter iter; | ||
| 2174 | |||
| 2175 | TEST("create list") { | ||
| 2176 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2177 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2178 | quicklistRelease(ql); | ||
| 2179 | } | ||
| 2180 | |||
| 2181 | TEST("add to tail of empty list") { | ||
| 2182 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2183 | quicklistPushTail(ql, "hello", 6); | ||
| 2184 | /* 1 for head and 1 for tail because 1 node = head = tail */ | ||
| 2185 | ql_verify(ql, 1, 1, 1, 1); | ||
| 2186 | quicklistRelease(ql); | ||
| 2187 | } | ||
| 2188 | |||
| 2189 | TEST("add to head of empty list") { | ||
| 2190 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2191 | quicklistPushHead(ql, "hello", 6); | ||
| 2192 | /* 1 for head and 1 for tail because 1 node = head = tail */ | ||
| 2193 | ql_verify(ql, 1, 1, 1, 1); | ||
| 2194 | quicklistRelease(ql); | ||
| 2195 | } | ||
| 2196 | |||
| 2197 | TEST_DESC("add to tail 5x at compress %d", options[_i]) { | ||
| 2198 | for (int f = 0; f < fill_count; f++) { | ||
| 2199 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2200 | for (int i = 0; i < 5; i++) | ||
| 2201 | quicklistPushTail(ql, genstr("hello", i), 32); | ||
| 2202 | if (ql->count != 5) | ||
| 2203 | ERROR; | ||
| 2204 | if (fills[f] == 32) | ||
| 2205 | ql_verify(ql, 1, 5, 5, 5); | ||
| 2206 | quicklistRelease(ql); | ||
| 2207 | } | ||
| 2208 | } | ||
| 2209 | |||
| 2210 | TEST_DESC("add to head 5x at compress %d", options[_i]) { | ||
| 2211 | for (int f = 0; f < fill_count; f++) { | ||
| 2212 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2213 | for (int i = 0; i < 5; i++) | ||
| 2214 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2215 | if (ql->count != 5) | ||
| 2216 | ERROR; | ||
| 2217 | if (fills[f] == 32) | ||
| 2218 | ql_verify(ql, 1, 5, 5, 5); | ||
| 2219 | quicklistRelease(ql); | ||
| 2220 | } | ||
| 2221 | } | ||
| 2222 | |||
| 2223 | TEST_DESC("add to tail 500x at compress %d", options[_i]) { | ||
| 2224 | for (int f = 0; f < fill_count; f++) { | ||
| 2225 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2226 | for (int i = 0; i < 500; i++) | ||
| 2227 | quicklistPushTail(ql, genstr("hello", i), 64); | ||
| 2228 | if (ql->count != 500) | ||
| 2229 | ERROR; | ||
| 2230 | if (fills[f] == 32) | ||
| 2231 | ql_verify(ql, 16, 500, 32, 20); | ||
| 2232 | quicklistRelease(ql); | ||
| 2233 | } | ||
| 2234 | } | ||
| 2235 | |||
| 2236 | TEST_DESC("add to head 500x at compress %d", options[_i]) { | ||
| 2237 | for (int f = 0; f < fill_count; f++) { | ||
| 2238 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2239 | for (int i = 0; i < 500; i++) | ||
| 2240 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2241 | if (ql->count != 500) | ||
| 2242 | ERROR; | ||
| 2243 | if (fills[f] == 32) | ||
| 2244 | ql_verify(ql, 16, 500, 20, 32); | ||
| 2245 | quicklistRelease(ql); | ||
| 2246 | } | ||
| 2247 | } | ||
| 2248 | |||
| 2249 | TEST("rotate empty") { | ||
| 2250 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2251 | quicklistRotate(ql); | ||
| 2252 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2253 | quicklistRelease(ql); | ||
| 2254 | } | ||
| 2255 | |||
| 2256 | TEST("Compression Plain node") { | ||
| 2257 | for (int f = 0; f < fill_count; f++) { | ||
| 2258 | size_t large_limit = (fills[f] < 0) ? quicklistNodeNegFillLimit(fills[f]) + 1 : SIZE_SAFETY_LIMIT + 1; | ||
| 2259 | |||
| 2260 | char buf[large_limit]; | ||
| 2261 | quicklist *ql = quicklistNew(fills[f], 1); | ||
| 2262 | for (int i = 0; i < 500; i++) { | ||
| 2263 | /* Set to 256 to allow the node to be triggered to compress, | ||
| 2264 | * if it is less than 48(nocompress), the test will be successful. */ | ||
| 2265 | snprintf(buf, sizeof(buf), "hello%d", i); | ||
| 2266 | quicklistPushHead(ql, buf, large_limit); | ||
| 2267 | } | ||
| 2268 | |||
| 2269 | quicklistIter iter; | ||
| 2270 | quicklistEntry entry; | ||
| 2271 | quicklistInitIterator(&iter, ql, AL_START_TAIL); | ||
| 2272 | int i = 0; | ||
| 2273 | while (quicklistNext(&iter, &entry)) { | ||
| 2274 | assert(QL_NODE_IS_PLAIN(entry.node)); | ||
| 2275 | snprintf(buf, sizeof(buf), "hello%d", i); | ||
| 2276 | if (strcmp((char *)entry.value, buf)) | ||
| 2277 | ERR("value [%s] didn't match [%s] at position %d", | ||
| 2278 | entry.value, buf, i); | ||
| 2279 | i++; | ||
| 2280 | } | ||
| 2281 | ql_reset_iterator(&iter); | ||
| 2282 | quicklistRelease(ql); | ||
| 2283 | } | ||
| 2284 | } | ||
| 2285 | |||
| 2286 | TEST("NEXT plain node") { | ||
| 2287 | for (int f = 0; f < fill_count; f++) { | ||
| 2288 | size_t large_limit = (fills[f] < 0) ? quicklistNodeNegFillLimit(fills[f]) + 1 : SIZE_SAFETY_LIMIT + 1; | ||
| 2289 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2290 | |||
| 2291 | char buf[large_limit]; | ||
| 2292 | memcpy(buf, "plain", 5); | ||
| 2293 | quicklistPushHead(ql, buf, large_limit); | ||
| 2294 | quicklistPushHead(ql, buf, large_limit); | ||
| 2295 | quicklistPushHead(ql, "packed3", 7); | ||
| 2296 | quicklistPushHead(ql, "packed4", 7); | ||
| 2297 | quicklistPushHead(ql, buf, large_limit); | ||
| 2298 | |||
| 2299 | quicklistEntry entry; | ||
| 2300 | quicklistIter iter; | ||
| 2301 | quicklistInitIterator(&iter, ql, AL_START_TAIL); | ||
| 2302 | |||
| 2303 | while(quicklistNext(&iter, &entry) != 0) { | ||
| 2304 | if (QL_NODE_IS_PLAIN(entry.node)) | ||
| 2305 | assert(!memcmp(entry.value, "plain", 5)); | ||
| 2306 | else | ||
| 2307 | assert(!memcmp(entry.value, "packed", 6)); | ||
| 2308 | } | ||
| 2309 | ql_reset_iterator(&iter); | ||
| 2310 | quicklistRelease(ql); | ||
| 2311 | } | ||
| 2312 | } | ||
| 2313 | |||
| 2314 | TEST("rotate plain node ") { | ||
| 2315 | for (int f = 0; f < fill_count; f++) { | ||
| 2316 | size_t large_limit = (fills[f] < 0) ? quicklistNodeNegFillLimit(fills[f]) + 1 : SIZE_SAFETY_LIMIT + 1; | ||
| 2317 | |||
| 2318 | unsigned char *data = NULL; | ||
| 2319 | size_t sz; | ||
| 2320 | long long lv; | ||
| 2321 | int i =0; | ||
| 2322 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2323 | char buf[large_limit]; | ||
| 2324 | memcpy(buf, "hello1", 6); | ||
| 2325 | quicklistPushHead(ql, buf, large_limit); | ||
| 2326 | memcpy(buf, "hello4", 6); | ||
| 2327 | quicklistPushHead(ql, buf, large_limit); | ||
| 2328 | memcpy(buf, "hello3", 6); | ||
| 2329 | quicklistPushHead(ql, buf, large_limit); | ||
| 2330 | memcpy(buf, "hello2", 6); | ||
| 2331 | quicklistPushHead(ql, buf, large_limit); | ||
| 2332 | quicklistRotate(ql); | ||
| 2333 | |||
| 2334 | for(i = 1 ; i < 5; i++) { | ||
| 2335 | assert(QL_NODE_IS_PLAIN(ql->tail)); | ||
| 2336 | quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); | ||
| 2337 | int temp_char = data[5]; | ||
| 2338 | zfree(data); | ||
| 2339 | assert(temp_char == ('0' + i)); | ||
| 2340 | } | ||
| 2341 | |||
| 2342 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2343 | quicklistRelease(ql); | ||
| 2344 | } | ||
| 2345 | } | ||
| 2346 | |||
| 2347 | TEST("rotate one val once") { | ||
| 2348 | for (int f = 0; f < fill_count; f++) { | ||
| 2349 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2350 | quicklistPushHead(ql, "hello", 6); | ||
| 2351 | quicklistRotate(ql); | ||
| 2352 | /* Ignore compression verify because listpack is | ||
| 2353 | * too small to compress. */ | ||
| 2354 | ql_verify(ql, 1, 1, 1, 1); | ||
| 2355 | quicklistRelease(ql); | ||
| 2356 | } | ||
| 2357 | } | ||
| 2358 | |||
| 2359 | TEST_DESC("rotate 500 val 5000 times at compress %d", options[_i]) { | ||
| 2360 | for (int f = 0; f < fill_count; f++) { | ||
| 2361 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2362 | quicklistPushHead(ql, "900", 3); | ||
| 2363 | quicklistPushHead(ql, "7000", 4); | ||
| 2364 | quicklistPushHead(ql, "-1200", 5); | ||
| 2365 | quicklistPushHead(ql, "42", 2); | ||
| 2366 | for (int i = 0; i < 500; i++) | ||
| 2367 | quicklistPushHead(ql, genstr("hello", i), 64); | ||
| 2368 | ql_info(ql); | ||
| 2369 | for (int i = 0; i < 5000; i++) { | ||
| 2370 | ql_info(ql); | ||
| 2371 | quicklistRotate(ql); | ||
| 2372 | } | ||
| 2373 | if (fills[f] == 1) | ||
| 2374 | ql_verify(ql, 504, 504, 1, 1); | ||
| 2375 | else if (fills[f] == 2) | ||
| 2376 | ql_verify(ql, 252, 504, 2, 2); | ||
| 2377 | else if (fills[f] == 32) | ||
| 2378 | ql_verify(ql, 16, 504, 32, 24); | ||
| 2379 | quicklistRelease(ql); | ||
| 2380 | } | ||
| 2381 | } | ||
| 2382 | |||
| 2383 | TEST("pop empty") { | ||
| 2384 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2385 | quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL); | ||
| 2386 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2387 | quicklistRelease(ql); | ||
| 2388 | } | ||
| 2389 | |||
| 2390 | TEST("pop 1 string from 1") { | ||
| 2391 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2392 | char *populate = genstr("hello", 331); | ||
| 2393 | quicklistPushHead(ql, populate, 32); | ||
| 2394 | unsigned char *data; | ||
| 2395 | size_t sz; | ||
| 2396 | long long lv; | ||
| 2397 | ql_info(ql); | ||
| 2398 | assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv)); | ||
| 2399 | assert(data != NULL); | ||
| 2400 | assert(sz == 32); | ||
| 2401 | if (strcmp(populate, (char *)data)) { | ||
| 2402 | int size = sz; | ||
| 2403 | ERR("Pop'd value (%.*s) didn't equal original value (%s)", size, | ||
| 2404 | data, populate); | ||
| 2405 | } | ||
| 2406 | zfree(data); | ||
| 2407 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2408 | quicklistRelease(ql); | ||
| 2409 | } | ||
| 2410 | |||
| 2411 | TEST("pop head 1 number from 1") { | ||
| 2412 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2413 | quicklistPushHead(ql, "55513", 5); | ||
| 2414 | unsigned char *data; | ||
| 2415 | size_t sz; | ||
| 2416 | long long lv; | ||
| 2417 | ql_info(ql); | ||
| 2418 | assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv)); | ||
| 2419 | assert(data == NULL); | ||
| 2420 | assert(lv == 55513); | ||
| 2421 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2422 | quicklistRelease(ql); | ||
| 2423 | } | ||
| 2424 | |||
| 2425 | TEST("pop head 500 from 500") { | ||
| 2426 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2427 | for (int i = 0; i < 500; i++) | ||
| 2428 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2429 | ql_info(ql); | ||
| 2430 | for (int i = 0; i < 500; i++) { | ||
| 2431 | unsigned char *data; | ||
| 2432 | size_t sz; | ||
| 2433 | long long lv; | ||
| 2434 | int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); | ||
| 2435 | assert(ret == 1); | ||
| 2436 | assert(data != NULL); | ||
| 2437 | assert(sz == 32); | ||
| 2438 | if (strcmp(genstr("hello", 499 - i), (char *)data)) { | ||
| 2439 | int size = sz; | ||
| 2440 | ERR("Pop'd value (%.*s) didn't equal original value (%s)", | ||
| 2441 | size, data, genstr("hello", 499 - i)); | ||
| 2442 | } | ||
| 2443 | zfree(data); | ||
| 2444 | } | ||
| 2445 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2446 | quicklistRelease(ql); | ||
| 2447 | } | ||
| 2448 | |||
| 2449 | TEST("pop head 5000 from 500") { | ||
| 2450 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2451 | for (int i = 0; i < 500; i++) | ||
| 2452 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2453 | for (int i = 0; i < 5000; i++) { | ||
| 2454 | unsigned char *data; | ||
| 2455 | size_t sz; | ||
| 2456 | long long lv; | ||
| 2457 | int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); | ||
| 2458 | if (i < 500) { | ||
| 2459 | assert(ret == 1); | ||
| 2460 | assert(data != NULL); | ||
| 2461 | assert(sz == 32); | ||
| 2462 | if (strcmp(genstr("hello", 499 - i), (char *)data)) { | ||
| 2463 | int size = sz; | ||
| 2464 | ERR("Pop'd value (%.*s) didn't equal original value " | ||
| 2465 | "(%s)", | ||
| 2466 | size, data, genstr("hello", 499 - i)); | ||
| 2467 | } | ||
| 2468 | zfree(data); | ||
| 2469 | } else { | ||
| 2470 | assert(ret == 0); | ||
| 2471 | } | ||
| 2472 | } | ||
| 2473 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2474 | quicklistRelease(ql); | ||
| 2475 | } | ||
| 2476 | |||
| 2477 | TEST("iterate forward over 500 list") { | ||
| 2478 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2479 | quicklistSetFill(ql, 32); | ||
| 2480 | for (int i = 0; i < 500; i++) | ||
| 2481 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2482 | quicklistIter iter_local; | ||
| 2483 | quicklistEntry entry; | ||
| 2484 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 2485 | int i = 499, count = 0; | ||
| 2486 | while (quicklistNext(&iter_local, &entry)) { | ||
| 2487 | char *h = genstr("hello", i); | ||
| 2488 | if (strcmp((char *)entry.value, h)) | ||
| 2489 | ERR("value [%s] didn't match [%s] at position %d", | ||
| 2490 | entry.value, h, i); | ||
| 2491 | i--; | ||
| 2492 | count++; | ||
| 2493 | } | ||
| 2494 | if (count != 500) | ||
| 2495 | ERR("Didn't iterate over exactly 500 elements (%d)", i); | ||
| 2496 | ql_verify(ql, 16, 500, 20, 32); | ||
| 2497 | ql_reset_iterator(&iter_local); | ||
| 2498 | quicklistRelease(ql); | ||
| 2499 | } | ||
| 2500 | |||
| 2501 | TEST("iterate reverse over 500 list") { | ||
| 2502 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2503 | quicklistSetFill(ql, 32); | ||
| 2504 | for (int i = 0; i < 500; i++) | ||
| 2505 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2506 | quicklistIter iter_local; | ||
| 2507 | quicklistEntry entry; | ||
| 2508 | quicklistInitIterator(&iter_local, ql, AL_START_TAIL); | ||
| 2509 | int i = 0; | ||
| 2510 | while (quicklistNext(&iter_local, &entry)) { | ||
| 2511 | char *h = genstr("hello", i); | ||
| 2512 | if (strcmp((char *)entry.value, h)) | ||
| 2513 | ERR("value [%s] didn't match [%s] at position %d", | ||
| 2514 | entry.value, h, i); | ||
| 2515 | i++; | ||
| 2516 | } | ||
| 2517 | if (i != 500) | ||
| 2518 | ERR("Didn't iterate over exactly 500 elements (%d)", i); | ||
| 2519 | ql_verify(ql, 16, 500, 20, 32); | ||
| 2520 | ql_reset_iterator(&iter_local); | ||
| 2521 | quicklistRelease(ql); | ||
| 2522 | } | ||
| 2523 | |||
| 2524 | TEST("insert after 1 element") { | ||
| 2525 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2526 | quicklistPushHead(ql, "hello", 6); | ||
| 2527 | quicklistEntry entry; | ||
| 2528 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 2529 | quicklistInsertAfter(&iter, &entry, "abc", 4); | ||
| 2530 | ql_reset_iterator(&iter); | ||
| 2531 | ql_verify(ql, 1, 2, 2, 2); | ||
| 2532 | |||
| 2533 | /* verify results */ | ||
| 2534 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 2535 | int sz = entry.sz; | ||
| 2536 | if (strncmp((char *)entry.value, "hello", 5)) { | ||
| 2537 | ERR("Value 0 didn't match, instead got: %.*s", sz, | ||
| 2538 | entry.value); | ||
| 2539 | } | ||
| 2540 | ql_reset_iterator(&iter); | ||
| 2541 | |||
| 2542 | quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry); | ||
| 2543 | sz = entry.sz; | ||
| 2544 | if (strncmp((char *)entry.value, "abc", 3)) { | ||
| 2545 | ERR("Value 1 didn't match, instead got: %.*s", sz, | ||
| 2546 | entry.value); | ||
| 2547 | } | ||
| 2548 | ql_reset_iterator(&iter); | ||
| 2549 | quicklistRelease(ql); | ||
| 2550 | } | ||
| 2551 | |||
| 2552 | TEST("insert before 1 element") { | ||
| 2553 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2554 | quicklistPushHead(ql, "hello", 6); | ||
| 2555 | quicklistEntry entry; | ||
| 2556 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 2557 | quicklistInsertBefore(&iter, &entry, "abc", 4); | ||
| 2558 | ql_reset_iterator(&iter); | ||
| 2559 | ql_verify(ql, 1, 2, 2, 2); | ||
| 2560 | |||
| 2561 | /* verify results */ | ||
| 2562 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 2563 | int sz = entry.sz; | ||
| 2564 | if (strncmp((char *)entry.value, "abc", 3)) { | ||
| 2565 | ERR("Value 0 didn't match, instead got: %.*s", sz, | ||
| 2566 | entry.value); | ||
| 2567 | } | ||
| 2568 | ql_reset_iterator(&iter); | ||
| 2569 | |||
| 2570 | quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry); | ||
| 2571 | sz = entry.sz; | ||
| 2572 | if (strncmp((char *)entry.value, "hello", 5)) { | ||
| 2573 | ERR("Value 1 didn't match, instead got: %.*s", sz, | ||
| 2574 | entry.value); | ||
| 2575 | } | ||
| 2576 | ql_reset_iterator(&iter); | ||
| 2577 | quicklistRelease(ql); | ||
| 2578 | } | ||
| 2579 | |||
| 2580 | TEST("insert head while head node is full") { | ||
| 2581 | quicklist *ql = quicklistNew(4, options[_i]); | ||
| 2582 | for (int i = 0; i < 10; i++) | ||
| 2583 | quicklistPushTail(ql, genstr("hello", i), 6); | ||
| 2584 | quicklistSetFill(ql, -1); | ||
| 2585 | quicklistEntry entry; | ||
| 2586 | quicklistInitIteratorEntryAtIdx(&iter, ql, -10, &entry); | ||
| 2587 | char buf[4096] = {0}; | ||
| 2588 | quicklistInsertBefore(&iter, &entry, buf, 4096); | ||
| 2589 | ql_reset_iterator(&iter); | ||
| 2590 | ql_verify(ql, 4, 11, 1, 2); | ||
| 2591 | quicklistRelease(ql); | ||
| 2592 | } | ||
| 2593 | |||
| 2594 | TEST("insert tail while tail node is full") { | ||
| 2595 | quicklist *ql = quicklistNew(4, options[_i]); | ||
| 2596 | for (int i = 0; i < 10; i++) | ||
| 2597 | quicklistPushHead(ql, genstr("hello", i), 6); | ||
| 2598 | quicklistSetFill(ql, -1); | ||
| 2599 | quicklistEntry entry; | ||
| 2600 | quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry); | ||
| 2601 | char buf[4096] = {0}; | ||
| 2602 | quicklistInsertAfter(&iter, &entry, buf, 4096); | ||
| 2603 | ql_reset_iterator(&iter); | ||
| 2604 | ql_verify(ql, 4, 11, 2, 1); | ||
| 2605 | quicklistRelease(ql); | ||
| 2606 | } | ||
| 2607 | |||
| 2608 | TEST_DESC("insert once in elements while iterating at compress %d", | ||
| 2609 | options[_i]) { | ||
| 2610 | for (int f = 0; f < fill_count; f++) { | ||
| 2611 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2612 | quicklistPushTail(ql, "abc", 3); | ||
| 2613 | quicklistSetFill(ql, 1); | ||
| 2614 | quicklistPushTail(ql, "def", 3); /* force to unique node */ | ||
| 2615 | quicklistSetFill(ql, f); | ||
| 2616 | quicklistPushTail(ql, "bob", 3); /* force to reset for +3 */ | ||
| 2617 | quicklistPushTail(ql, "foo", 3); | ||
| 2618 | quicklistPushTail(ql, "zoo", 3); | ||
| 2619 | |||
| 2620 | itrprintr(ql, 0); | ||
| 2621 | /* insert "bar" before "bob" while iterating over list. */ | ||
| 2622 | quicklistIter iter_local; | ||
| 2623 | quicklistEntry entry; | ||
| 2624 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 2625 | while (quicklistNext(&iter_local, &entry)) { | ||
| 2626 | if (!strncmp((char *)entry.value, "bob", 3)) { | ||
| 2627 | /* Insert as fill = 1 so it spills into new node. */ | ||
| 2628 | quicklistInsertBefore(&iter_local, &entry, "bar", 3); | ||
| 2629 | break; /* didn't we fix insert-while-iterating? */ | ||
| 2630 | } | ||
| 2631 | } | ||
| 2632 | ql_reset_iterator(&iter_local); | ||
| 2633 | itrprintr(ql, 0); | ||
| 2634 | |||
| 2635 | /* verify results */ | ||
| 2636 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 2637 | int sz = entry.sz; | ||
| 2638 | |||
| 2639 | if (strncmp((char *)entry.value, "abc", 3)) | ||
| 2640 | ERR("Value 0 didn't match, instead got: %.*s", sz, | ||
| 2641 | entry.value); | ||
| 2642 | ql_reset_iterator(&iter); | ||
| 2643 | |||
| 2644 | quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry); | ||
| 2645 | if (strncmp((char *)entry.value, "def", 3)) | ||
| 2646 | ERR("Value 1 didn't match, instead got: %.*s", sz, | ||
| 2647 | entry.value); | ||
| 2648 | ql_reset_iterator(&iter); | ||
| 2649 | |||
| 2650 | quicklistInitIteratorEntryAtIdx(&iter, ql, 2, &entry); | ||
| 2651 | if (strncmp((char *)entry.value, "bar", 3)) | ||
| 2652 | ERR("Value 2 didn't match, instead got: %.*s", sz, | ||
| 2653 | entry.value); | ||
| 2654 | ql_reset_iterator(&iter); | ||
| 2655 | |||
| 2656 | quicklistInitIteratorEntryAtIdx(&iter, ql, 3, &entry); | ||
| 2657 | if (strncmp((char *)entry.value, "bob", 3)) | ||
| 2658 | ERR("Value 3 didn't match, instead got: %.*s", sz, | ||
| 2659 | entry.value); | ||
| 2660 | ql_reset_iterator(&iter); | ||
| 2661 | |||
| 2662 | quicklistInitIteratorEntryAtIdx(&iter, ql, 4, &entry); | ||
| 2663 | if (strncmp((char *)entry.value, "foo", 3)) | ||
| 2664 | ERR("Value 4 didn't match, instead got: %.*s", sz, | ||
| 2665 | entry.value); | ||
| 2666 | ql_reset_iterator(&iter); | ||
| 2667 | |||
| 2668 | quicklistInitIteratorEntryAtIdx(&iter, ql, 5, &entry); | ||
| 2669 | if (strncmp((char *)entry.value, "zoo", 3)) | ||
| 2670 | ERR("Value 5 didn't match, instead got: %.*s", sz, | ||
| 2671 | entry.value); | ||
| 2672 | ql_reset_iterator(&iter); | ||
| 2673 | quicklistRelease(ql); | ||
| 2674 | } | ||
| 2675 | } | ||
| 2676 | |||
| 2677 | TEST_DESC("insert [before] 250 new in middle of 500 elements at compress %d", | ||
| 2678 | options[_i]) { | ||
| 2679 | for (int f = 0; f < fill_count; f++) { | ||
| 2680 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2681 | for (int i = 0; i < 500; i++) | ||
| 2682 | quicklistPushTail(ql, genstr("hello", i), 32); | ||
| 2683 | for (int i = 0; i < 250; i++) { | ||
| 2684 | quicklistEntry entry; | ||
| 2685 | quicklistInitIteratorEntryAtIdx(&iter, ql, 250, &entry); | ||
| 2686 | quicklistInsertBefore(&iter, &entry, genstr("abc", i), 32); | ||
| 2687 | ql_reset_iterator(&iter); | ||
| 2688 | } | ||
| 2689 | if (fills[f] == 32) | ||
| 2690 | ql_verify(ql, 25, 750, 32, 20); | ||
| 2691 | quicklistRelease(ql); | ||
| 2692 | } | ||
| 2693 | } | ||
| 2694 | |||
| 2695 | TEST_DESC("insert [after] 250 new in middle of 500 elements at compress %d", | ||
| 2696 | options[_i]) { | ||
| 2697 | for (int f = 0; f < fill_count; f++) { | ||
| 2698 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2699 | for (int i = 0; i < 500; i++) | ||
| 2700 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2701 | for (int i = 0; i < 250; i++) { | ||
| 2702 | quicklistEntry entry; | ||
| 2703 | quicklistInitIteratorEntryAtIdx(&iter, ql, 250, &entry); | ||
| 2704 | quicklistInsertAfter(&iter, &entry, genstr("abc", i), 32); | ||
| 2705 | ql_reset_iterator(&iter); | ||
| 2706 | } | ||
| 2707 | |||
| 2708 | if (ql->count != 750) | ||
| 2709 | ERR("List size not 750, but rather %ld", ql->count); | ||
| 2710 | |||
| 2711 | if (fills[f] == 32) | ||
| 2712 | ql_verify(ql, 26, 750, 20, 32); | ||
| 2713 | quicklistRelease(ql); | ||
| 2714 | } | ||
| 2715 | } | ||
| 2716 | |||
| 2717 | TEST("duplicate empty list") { | ||
| 2718 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2719 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2720 | quicklist *copy = quicklistDup(ql); | ||
| 2721 | ql_verify(copy, 0, 0, 0, 0); | ||
| 2722 | quicklistRelease(ql); | ||
| 2723 | quicklistRelease(copy); | ||
| 2724 | } | ||
| 2725 | |||
| 2726 | TEST("duplicate list of 1 element") { | ||
| 2727 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2728 | quicklistPushHead(ql, genstr("hello", 3), 32); | ||
| 2729 | ql_verify(ql, 1, 1, 1, 1); | ||
| 2730 | quicklist *copy = quicklistDup(ql); | ||
| 2731 | ql_verify(copy, 1, 1, 1, 1); | ||
| 2732 | quicklistRelease(ql); | ||
| 2733 | quicklistRelease(copy); | ||
| 2734 | } | ||
| 2735 | |||
| 2736 | TEST("duplicate list of 500") { | ||
| 2737 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2738 | quicklistSetFill(ql, 32); | ||
| 2739 | for (int i = 0; i < 500; i++) | ||
| 2740 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2741 | ql_verify(ql, 16, 500, 20, 32); | ||
| 2742 | |||
| 2743 | quicklist *copy = quicklistDup(ql); | ||
| 2744 | ql_verify(copy, 16, 500, 20, 32); | ||
| 2745 | quicklistRelease(ql); | ||
| 2746 | quicklistRelease(copy); | ||
| 2747 | } | ||
| 2748 | |||
| 2749 | for (int f = 0; f < fill_count; f++) { | ||
| 2750 | TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f, | ||
| 2751 | options[_i]) { | ||
| 2752 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2753 | for (int i = 0; i < 500; i++) | ||
| 2754 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2755 | quicklistEntry entry; | ||
| 2756 | quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry); | ||
| 2757 | if (strcmp((char *)entry.value, "hello2") != 0) | ||
| 2758 | ERR("Value: %s", entry.value); | ||
| 2759 | ql_reset_iterator(&iter); | ||
| 2760 | |||
| 2761 | quicklistInitIteratorEntryAtIdx(&iter, ql, 200, &entry); | ||
| 2762 | if (strcmp((char *)entry.value, "hello201") != 0) | ||
| 2763 | ERR("Value: %s", entry.value); | ||
| 2764 | ql_reset_iterator(&iter); | ||
| 2765 | quicklistRelease(ql); | ||
| 2766 | } | ||
| 2767 | |||
| 2768 | TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", | ||
| 2769 | fills[f], options[_i]) { | ||
| 2770 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2771 | for (int i = 0; i < 500; i++) | ||
| 2772 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2773 | quicklistEntry entry; | ||
| 2774 | quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry); | ||
| 2775 | if (strcmp((char *)entry.value, "hello500") != 0) | ||
| 2776 | ERR("Value: %s", entry.value); | ||
| 2777 | ql_reset_iterator(&iter); | ||
| 2778 | |||
| 2779 | quicklistInitIteratorEntryAtIdx(&iter, ql, -2, &entry); | ||
| 2780 | if (strcmp((char *)entry.value, "hello499") != 0) | ||
| 2781 | ERR("Value: %s", entry.value); | ||
| 2782 | ql_reset_iterator(&iter); | ||
| 2783 | quicklistRelease(ql); | ||
| 2784 | } | ||
| 2785 | |||
| 2786 | TEST_DESC("index -100 from 500 list at fill %d at compress %d", | ||
| 2787 | fills[f], options[_i]) { | ||
| 2788 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2789 | for (int i = 0; i < 500; i++) | ||
| 2790 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2791 | quicklistEntry entry; | ||
| 2792 | quicklistInitIteratorEntryAtIdx(&iter, ql, -100, &entry); | ||
| 2793 | if (strcmp((char *)entry.value, "hello401") != 0) | ||
| 2794 | ERR("Value: %s", entry.value); | ||
| 2795 | ql_reset_iterator(&iter); | ||
| 2796 | quicklistRelease(ql); | ||
| 2797 | } | ||
| 2798 | |||
| 2799 | TEST_DESC("index too big +1 from 50 list at fill %d at compress %d", | ||
| 2800 | fills[f], options[_i]) { | ||
| 2801 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 2802 | for (int i = 0; i < 50; i++) | ||
| 2803 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2804 | quicklistEntry entry; | ||
| 2805 | int sz = entry.sz; | ||
| 2806 | if (quicklistInitIteratorEntryAtIdx(&iter, ql, 50, &entry)) | ||
| 2807 | ERR("Index found at 50 with 50 list: %.*s", sz, | ||
| 2808 | entry.value); | ||
| 2809 | ql_reset_iterator(&iter); | ||
| 2810 | quicklistRelease(ql); | ||
| 2811 | } | ||
| 2812 | } | ||
| 2813 | |||
| 2814 | TEST("delete range empty list") { | ||
| 2815 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2816 | quicklistDelRange(ql, 5, 20); | ||
| 2817 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2818 | quicklistRelease(ql); | ||
| 2819 | } | ||
| 2820 | |||
| 2821 | TEST("delete range of entire node in list of one node") { | ||
| 2822 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2823 | for (int i = 0; i < 32; i++) | ||
| 2824 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2825 | ql_verify(ql, 1, 32, 32, 32); | ||
| 2826 | quicklistDelRange(ql, 0, 32); | ||
| 2827 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2828 | quicklistRelease(ql); | ||
| 2829 | } | ||
| 2830 | |||
| 2831 | TEST("delete range of entire node with overflow counts") { | ||
| 2832 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2833 | for (int i = 0; i < 32; i++) | ||
| 2834 | quicklistPushHead(ql, genstr("hello", i), 32); | ||
| 2835 | ql_verify(ql, 1, 32, 32, 32); | ||
| 2836 | quicklistDelRange(ql, 0, 128); | ||
| 2837 | ql_verify(ql, 0, 0, 0, 0); | ||
| 2838 | quicklistRelease(ql); | ||
| 2839 | } | ||
| 2840 | |||
| 2841 | TEST("delete middle 100 of 500 list") { | ||
| 2842 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2843 | quicklistSetFill(ql, 32); | ||
| 2844 | for (int i = 0; i < 500; i++) | ||
| 2845 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2846 | ql_verify(ql, 16, 500, 32, 20); | ||
| 2847 | quicklistDelRange(ql, 200, 100); | ||
| 2848 | ql_verify(ql, 14, 400, 32, 20); | ||
| 2849 | quicklistRelease(ql); | ||
| 2850 | } | ||
| 2851 | |||
| 2852 | TEST("delete less than fill but across nodes") { | ||
| 2853 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2854 | quicklistSetFill(ql, 32); | ||
| 2855 | for (int i = 0; i < 500; i++) | ||
| 2856 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2857 | ql_verify(ql, 16, 500, 32, 20); | ||
| 2858 | quicklistDelRange(ql, 60, 10); | ||
| 2859 | ql_verify(ql, 16, 490, 32, 20); | ||
| 2860 | quicklistRelease(ql); | ||
| 2861 | } | ||
| 2862 | |||
| 2863 | TEST("delete negative 1 from 500 list") { | ||
| 2864 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2865 | quicklistSetFill(ql, 32); | ||
| 2866 | for (int i = 0; i < 500; i++) | ||
| 2867 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2868 | ql_verify(ql, 16, 500, 32, 20); | ||
| 2869 | quicklistDelRange(ql, -1, 1); | ||
| 2870 | ql_verify(ql, 16, 499, 32, 19); | ||
| 2871 | quicklistRelease(ql); | ||
| 2872 | } | ||
| 2873 | |||
| 2874 | TEST("delete negative 1 from 500 list with overflow counts") { | ||
| 2875 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2876 | quicklistSetFill(ql, 32); | ||
| 2877 | for (int i = 0; i < 500; i++) | ||
| 2878 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2879 | ql_verify(ql, 16, 500, 32, 20); | ||
| 2880 | quicklistDelRange(ql, -1, 128); | ||
| 2881 | ql_verify(ql, 16, 499, 32, 19); | ||
| 2882 | quicklistRelease(ql); | ||
| 2883 | } | ||
| 2884 | |||
| 2885 | TEST("delete negative 100 from 500 list") { | ||
| 2886 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2887 | quicklistSetFill(ql, 32); | ||
| 2888 | for (int i = 0; i < 500; i++) | ||
| 2889 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2890 | quicklistDelRange(ql, -100, 100); | ||
| 2891 | ql_verify(ql, 13, 400, 32, 16); | ||
| 2892 | quicklistRelease(ql); | ||
| 2893 | } | ||
| 2894 | |||
| 2895 | TEST("delete -10 count 5 from 50 list") { | ||
| 2896 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2897 | quicklistSetFill(ql, 32); | ||
| 2898 | for (int i = 0; i < 50; i++) | ||
| 2899 | quicklistPushTail(ql, genstr("hello", i + 1), 32); | ||
| 2900 | ql_verify(ql, 2, 50, 32, 18); | ||
| 2901 | quicklistDelRange(ql, -10, 5); | ||
| 2902 | ql_verify(ql, 2, 45, 32, 13); | ||
| 2903 | quicklistRelease(ql); | ||
| 2904 | } | ||
| 2905 | |||
| 2906 | TEST("numbers only list read") { | ||
| 2907 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2908 | quicklistPushTail(ql, "1111", 4); | ||
| 2909 | quicklistPushTail(ql, "2222", 4); | ||
| 2910 | quicklistPushTail(ql, "3333", 4); | ||
| 2911 | quicklistPushTail(ql, "4444", 4); | ||
| 2912 | ql_verify(ql, 1, 4, 4, 4); | ||
| 2913 | quicklistEntry entry; | ||
| 2914 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 2915 | if (entry.longval != 1111) | ||
| 2916 | ERR("Not 1111, %lld", entry.longval); | ||
| 2917 | ql_reset_iterator(&iter); | ||
| 2918 | |||
| 2919 | quicklistInitIteratorEntryAtIdx(&iter, ql, 1, &entry); | ||
| 2920 | if (entry.longval != 2222) | ||
| 2921 | ERR("Not 2222, %lld", entry.longval); | ||
| 2922 | ql_reset_iterator(&iter); | ||
| 2923 | |||
| 2924 | quicklistInitIteratorEntryAtIdx(&iter, ql, 2, &entry); | ||
| 2925 | if (entry.longval != 3333) | ||
| 2926 | ERR("Not 3333, %lld", entry.longval); | ||
| 2927 | ql_reset_iterator(&iter); | ||
| 2928 | |||
| 2929 | quicklistInitIteratorEntryAtIdx(&iter, ql, 3, &entry); | ||
| 2930 | if (entry.longval != 4444) | ||
| 2931 | ERR("Not 4444, %lld", entry.longval); | ||
| 2932 | ql_reset_iterator(&iter); | ||
| 2933 | |||
| 2934 | if (quicklistInitIteratorEntryAtIdx(&iter, ql, 4, &entry)) | ||
| 2935 | ERR("Index past elements: %lld", entry.longval); | ||
| 2936 | ql_reset_iterator(&iter); | ||
| 2937 | |||
| 2938 | quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry); | ||
| 2939 | if (entry.longval != 4444) | ||
| 2940 | ERR("Not 4444 (reverse), %lld", entry.longval); | ||
| 2941 | ql_reset_iterator(&iter); | ||
| 2942 | |||
| 2943 | quicklistInitIteratorEntryAtIdx(&iter, ql, -2, &entry); | ||
| 2944 | if (entry.longval != 3333) | ||
| 2945 | ERR("Not 3333 (reverse), %lld", entry.longval); | ||
| 2946 | ql_reset_iterator(&iter); | ||
| 2947 | |||
| 2948 | quicklistInitIteratorEntryAtIdx(&iter, ql, -3, &entry); | ||
| 2949 | if (entry.longval != 2222) | ||
| 2950 | ERR("Not 2222 (reverse), %lld", entry.longval); | ||
| 2951 | ql_reset_iterator(&iter); | ||
| 2952 | |||
| 2953 | quicklistInitIteratorEntryAtIdx(&iter, ql, -4, &entry); | ||
| 2954 | if (entry.longval != 1111) | ||
| 2955 | ERR("Not 1111 (reverse), %lld", entry.longval); | ||
| 2956 | ql_reset_iterator(&iter); | ||
| 2957 | |||
| 2958 | if (quicklistInitIteratorEntryAtIdx(&iter, ql, -5, &entry)) | ||
| 2959 | ERR("Index past elements (reverse), %lld", entry.longval); | ||
| 2960 | ql_reset_iterator(&iter); | ||
| 2961 | quicklistRelease(ql); | ||
| 2962 | } | ||
| 2963 | |||
| 2964 | TEST("numbers larger list read") { | ||
| 2965 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2966 | quicklistSetFill(ql, 32); | ||
| 2967 | char num[32]; | ||
| 2968 | long long nums[5000]; | ||
| 2969 | for (int i = 0; i < 5000; i++) { | ||
| 2970 | nums[i] = -5157318210846258176 + i; | ||
| 2971 | int sz = ll2string(num, sizeof(num), nums[i]); | ||
| 2972 | quicklistPushTail(ql, num, sz); | ||
| 2973 | } | ||
| 2974 | quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20); | ||
| 2975 | quicklistEntry entry; | ||
| 2976 | for (int i = 0; i < 5000; i++) { | ||
| 2977 | quicklistInitIteratorEntryAtIdx(&iter, ql, i, &entry); | ||
| 2978 | if (entry.longval != nums[i]) | ||
| 2979 | ERR("[%d] Not longval %lld but rather %lld", i, nums[i], | ||
| 2980 | entry.longval); | ||
| 2981 | entry.longval = 0xdeadbeef; | ||
| 2982 | ql_reset_iterator(&iter); | ||
| 2983 | } | ||
| 2984 | quicklistInitIteratorEntryAtIdx(&iter, ql, 5000, &entry); | ||
| 2985 | if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20)) | ||
| 2986 | ERR("String val not match: %s", entry.value); | ||
| 2987 | ql_verify(ql, 157, 5001, 32, 9); | ||
| 2988 | ql_reset_iterator(&iter); | ||
| 2989 | quicklistRelease(ql); | ||
| 2990 | } | ||
| 2991 | |||
| 2992 | TEST("numbers larger list read B") { | ||
| 2993 | quicklist *ql = quicklistNew(-2, options[_i]); | ||
| 2994 | quicklistPushTail(ql, "99", 2); | ||
| 2995 | quicklistPushTail(ql, "98", 2); | ||
| 2996 | quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20); | ||
| 2997 | quicklistPushTail(ql, "96", 2); | ||
| 2998 | quicklistPushTail(ql, "95", 2); | ||
| 2999 | quicklistReplaceAtIndex(ql, 1, "foo", 3); | ||
| 3000 | quicklistReplaceAtIndex(ql, -1, "bar", 3); | ||
| 3001 | quicklistRelease(ql); | ||
| 3002 | } | ||
| 3003 | |||
| 3004 | TEST_DESC("lrem test at compress %d", options[_i]) { | ||
| 3005 | for (int f = 0; f < fill_count; f++) { | ||
| 3006 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 3007 | char *words[] = {"abc", "foo", "bar", "foobar", "foobared", | ||
| 3008 | "zap", "bar", "test", "foo"}; | ||
| 3009 | char *result[] = {"abc", "foo", "foobar", "foobared", | ||
| 3010 | "zap", "test", "foo"}; | ||
| 3011 | char *resultB[] = {"abc", "foo", "foobar", | ||
| 3012 | "foobared", "zap", "test"}; | ||
| 3013 | for (int i = 0; i < 9; i++) | ||
| 3014 | quicklistPushTail(ql, words[i], strlen(words[i])); | ||
| 3015 | |||
| 3016 | /* lrem 0 bar */ | ||
| 3017 | quicklistIter iter_local; | ||
| 3018 | quicklistEntry entry; | ||
| 3019 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3020 | int i = 0; | ||
| 3021 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3022 | if (quicklistCompare(&entry, (unsigned char *)"bar", 3, | ||
| 3023 | NULL, NULL)) { | ||
| 3024 | quicklistDelEntry(&iter_local, &entry); | ||
| 3025 | } | ||
| 3026 | i++; | ||
| 3027 | } | ||
| 3028 | ql_reset_iterator(&iter_local); | ||
| 3029 | |||
| 3030 | /* check result of lrem 0 bar */ | ||
| 3031 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3032 | i = 0; | ||
| 3033 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3034 | /* Result must be: abc, foo, foobar, foobared, zap, test, | ||
| 3035 | * foo */ | ||
| 3036 | int sz = entry.sz; | ||
| 3037 | if (strncmp((char *)entry.value, result[i], entry.sz)) { | ||
| 3038 | ERR("No match at position %d, got %.*s instead of %s", | ||
| 3039 | i, sz, entry.value, result[i]); | ||
| 3040 | } | ||
| 3041 | i++; | ||
| 3042 | } | ||
| 3043 | ql_reset_iterator(&iter_local); | ||
| 3044 | |||
| 3045 | quicklistPushTail(ql, "foo", 3); | ||
| 3046 | |||
| 3047 | /* lrem -2 foo */ | ||
| 3048 | quicklistInitIterator(&iter_local, ql, AL_START_TAIL); | ||
| 3049 | i = 0; | ||
| 3050 | int del = 2; | ||
| 3051 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3052 | if (quicklistCompare(&entry, (unsigned char *)"foo", 3, | ||
| 3053 | NULL, NULL)) { | ||
| 3054 | quicklistDelEntry(&iter_local, &entry); | ||
| 3055 | del--; | ||
| 3056 | } | ||
| 3057 | if (!del) | ||
| 3058 | break; | ||
| 3059 | i++; | ||
| 3060 | } | ||
| 3061 | ql_reset_iterator(&iter_local); | ||
| 3062 | |||
| 3063 | /* check result of lrem -2 foo */ | ||
| 3064 | /* (we're ignoring the '2' part and still deleting all foo | ||
| 3065 | * because | ||
| 3066 | * we only have two foo) */ | ||
| 3067 | quicklistInitIterator(&iter_local, ql, AL_START_TAIL); | ||
| 3068 | i = 0; | ||
| 3069 | size_t resB = sizeof(resultB) / sizeof(*resultB); | ||
| 3070 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3071 | /* Result must be: abc, foo, foobar, foobared, zap, test, | ||
| 3072 | * foo */ | ||
| 3073 | int sz = entry.sz; | ||
| 3074 | if (strncmp((char *)entry.value, resultB[resB - 1 - i], | ||
| 3075 | sz)) { | ||
| 3076 | ERR("No match at position %d, got %.*s instead of %s", | ||
| 3077 | i, sz, entry.value, resultB[resB - 1 - i]); | ||
| 3078 | } | ||
| 3079 | i++; | ||
| 3080 | } | ||
| 3081 | |||
| 3082 | ql_reset_iterator(&iter_local); | ||
| 3083 | quicklistRelease(ql); | ||
| 3084 | } | ||
| 3085 | } | ||
| 3086 | |||
| 3087 | TEST_DESC("iterate reverse + delete at compress %d", options[_i]) { | ||
| 3088 | for (int f = 0; f < fill_count; f++) { | ||
| 3089 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 3090 | quicklistPushTail(ql, "abc", 3); | ||
| 3091 | quicklistPushTail(ql, "def", 3); | ||
| 3092 | quicklistPushTail(ql, "hij", 3); | ||
| 3093 | quicklistPushTail(ql, "jkl", 3); | ||
| 3094 | quicklistPushTail(ql, "oop", 3); | ||
| 3095 | |||
| 3096 | quicklistEntry entry; | ||
| 3097 | quicklistIter iter_local; | ||
| 3098 | quicklistInitIterator(&iter_local, ql, AL_START_TAIL); | ||
| 3099 | int i = 0; | ||
| 3100 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3101 | if (quicklistCompare(&entry, (unsigned char *)"hij", 3, | ||
| 3102 | NULL, NULL)) { | ||
| 3103 | quicklistDelEntry(&iter_local, &entry); | ||
| 3104 | } | ||
| 3105 | i++; | ||
| 3106 | } | ||
| 3107 | ql_reset_iterator(&iter_local); | ||
| 3108 | |||
| 3109 | if (i != 5) | ||
| 3110 | ERR("Didn't iterate 5 times, iterated %d times.", i); | ||
| 3111 | |||
| 3112 | /* Check results after deletion of "hij" */ | ||
| 3113 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3114 | i = 0; | ||
| 3115 | char *vals[] = {"abc", "def", "jkl", "oop"}; | ||
| 3116 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3117 | if (!quicklistCompare(&entry, (unsigned char *)vals[i], | ||
| 3118 | 3, NULL, NULL)) { | ||
| 3119 | ERR("Value at %d didn't match %s\n", i, vals[i]); | ||
| 3120 | } | ||
| 3121 | i++; | ||
| 3122 | } | ||
| 3123 | ql_reset_iterator(&iter_local); | ||
| 3124 | quicklistRelease(ql); | ||
| 3125 | } | ||
| 3126 | } | ||
| 3127 | |||
| 3128 | TEST_DESC("iterator at index test at compress %d", options[_i]) { | ||
| 3129 | for (int f = 0; f < fill_count; f++) { | ||
| 3130 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 3131 | char num[32]; | ||
| 3132 | long long nums[5000]; | ||
| 3133 | for (int i = 0; i < 760; i++) { | ||
| 3134 | nums[i] = -5157318210846258176 + i; | ||
| 3135 | int sz = ll2string(num, sizeof(num), nums[i]); | ||
| 3136 | quicklistPushTail(ql, num, sz); | ||
| 3137 | } | ||
| 3138 | |||
| 3139 | quicklistEntry entry; | ||
| 3140 | quicklistIter iter_local; | ||
| 3141 | quicklistInitIteratorAtIdx(&iter_local, ql, AL_START_HEAD, 437); | ||
| 3142 | int i = 437; | ||
| 3143 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3144 | if (entry.longval != nums[i]) | ||
| 3145 | ERR("Expected %lld, but got %lld", entry.longval, | ||
| 3146 | nums[i]); | ||
| 3147 | i++; | ||
| 3148 | } | ||
| 3149 | ql_reset_iterator(&iter_local); | ||
| 3150 | quicklistRelease(ql); | ||
| 3151 | } | ||
| 3152 | } | ||
| 3153 | |||
| 3154 | TEST_DESC("ltrim test A at compress %d", options[_i]) { | ||
| 3155 | for (int f = 0; f < fill_count; f++) { | ||
| 3156 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 3157 | char num[32]; | ||
| 3158 | long long nums[5000]; | ||
| 3159 | for (int i = 0; i < 32; i++) { | ||
| 3160 | nums[i] = -5157318210846258176 + i; | ||
| 3161 | int sz = ll2string(num, sizeof(num), nums[i]); | ||
| 3162 | quicklistPushTail(ql, num, sz); | ||
| 3163 | } | ||
| 3164 | if (fills[f] == 32) | ||
| 3165 | ql_verify(ql, 1, 32, 32, 32); | ||
| 3166 | /* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */ | ||
| 3167 | quicklistDelRange(ql, 0, 25); | ||
| 3168 | quicklistDelRange(ql, 0, 0); | ||
| 3169 | quicklistEntry entry; | ||
| 3170 | for (int i = 0; i < 7; i++) { | ||
| 3171 | quicklistInitIteratorEntryAtIdx(&iter, ql, i, &entry); | ||
| 3172 | if (entry.longval != nums[25 + i]) | ||
| 3173 | ERR("Deleted invalid range! Expected %lld but got " | ||
| 3174 | "%lld", | ||
| 3175 | entry.longval, nums[25 + i]); | ||
| 3176 | ql_reset_iterator(&iter); | ||
| 3177 | } | ||
| 3178 | if (fills[f] == 32) | ||
| 3179 | ql_verify(ql, 1, 7, 7, 7); | ||
| 3180 | quicklistRelease(ql); | ||
| 3181 | } | ||
| 3182 | } | ||
| 3183 | |||
| 3184 | TEST_DESC("ltrim test B at compress %d", options[_i]) { | ||
| 3185 | for (int f = 0; f < fill_count; f++) { | ||
| 3186 | /* Force-disable compression because our 33 sequential | ||
| 3187 | * integers don't compress and the check always fails. */ | ||
| 3188 | quicklist *ql = quicklistNew(fills[f], QUICKLIST_NOCOMPRESS); | ||
| 3189 | char num[32]; | ||
| 3190 | long long nums[5000]; | ||
| 3191 | for (int i = 0; i < 33; i++) { | ||
| 3192 | nums[i] = i; | ||
| 3193 | int sz = ll2string(num, sizeof(num), nums[i]); | ||
| 3194 | quicklistPushTail(ql, num, sz); | ||
| 3195 | } | ||
| 3196 | if (fills[f] == 32) | ||
| 3197 | ql_verify(ql, 2, 33, 32, 1); | ||
| 3198 | /* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */ | ||
| 3199 | quicklistDelRange(ql, 0, 5); | ||
| 3200 | quicklistDelRange(ql, -16, 16); | ||
| 3201 | if (fills[f] == 32) | ||
| 3202 | ql_verify(ql, 1, 12, 12, 12); | ||
| 3203 | quicklistEntry entry; | ||
| 3204 | |||
| 3205 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 3206 | if (entry.longval != 5) | ||
| 3207 | ERR("A: longval not 5, but %lld", entry.longval); | ||
| 3208 | ql_reset_iterator(&iter); | ||
| 3209 | |||
| 3210 | quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry); | ||
| 3211 | if (entry.longval != 16) | ||
| 3212 | ERR("B! got instead: %lld", entry.longval); | ||
| 3213 | quicklistPushTail(ql, "bobobob", 7); | ||
| 3214 | ql_reset_iterator(&iter); | ||
| 3215 | |||
| 3216 | quicklistInitIteratorEntryAtIdx(&iter, ql, -1, &entry); | ||
| 3217 | int sz = entry.sz; | ||
| 3218 | if (strncmp((char *)entry.value, "bobobob", 7)) | ||
| 3219 | ERR("Tail doesn't match bobobob, it's %.*s instead", | ||
| 3220 | sz, entry.value); | ||
| 3221 | ql_reset_iterator(&iter); | ||
| 3222 | |||
| 3223 | for (int i = 0; i < 12; i++) { | ||
| 3224 | quicklistInitIteratorEntryAtIdx(&iter, ql, i, &entry); | ||
| 3225 | if (entry.longval != nums[5 + i]) | ||
| 3226 | ERR("Deleted invalid range! Expected %lld but got " | ||
| 3227 | "%lld", | ||
| 3228 | entry.longval, nums[5 + i]); | ||
| 3229 | ql_reset_iterator(&iter); | ||
| 3230 | } | ||
| 3231 | quicklistRelease(ql); | ||
| 3232 | } | ||
| 3233 | } | ||
| 3234 | |||
| 3235 | TEST_DESC("ltrim test C at compress %d", options[_i]) { | ||
| 3236 | for (int f = 0; f < fill_count; f++) { | ||
| 3237 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 3238 | char num[32]; | ||
| 3239 | long long nums[5000]; | ||
| 3240 | for (int i = 0; i < 33; i++) { | ||
| 3241 | nums[i] = -5157318210846258176 + i; | ||
| 3242 | int sz = ll2string(num, sizeof(num), nums[i]); | ||
| 3243 | quicklistPushTail(ql, num, sz); | ||
| 3244 | } | ||
| 3245 | if (fills[f] == 32) | ||
| 3246 | ql_verify(ql, 2, 33, 32, 1); | ||
| 3247 | /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */ | ||
| 3248 | quicklistDelRange(ql, 0, 3); | ||
| 3249 | quicklistDelRange(ql, -29, | ||
| 3250 | 4000); /* make sure not loop forever */ | ||
| 3251 | if (fills[f] == 32) | ||
| 3252 | ql_verify(ql, 1, 1, 1, 1); | ||
| 3253 | quicklistEntry entry; | ||
| 3254 | quicklistInitIteratorEntryAtIdx(&iter, ql, 0, &entry); | ||
| 3255 | if (entry.longval != -5157318210846258173) | ||
| 3256 | ERROR; | ||
| 3257 | ql_reset_iterator(&iter); | ||
| 3258 | quicklistRelease(ql); | ||
| 3259 | } | ||
| 3260 | } | ||
| 3261 | |||
| 3262 | TEST_DESC("ltrim test D at compress %d", options[_i]) { | ||
| 3263 | for (int f = 0; f < fill_count; f++) { | ||
| 3264 | quicklist *ql = quicklistNew(fills[f], options[_i]); | ||
| 3265 | char num[32]; | ||
| 3266 | long long nums[5000]; | ||
| 3267 | for (int i = 0; i < 33; i++) { | ||
| 3268 | nums[i] = -5157318210846258176 + i; | ||
| 3269 | int sz = ll2string(num, sizeof(num), nums[i]); | ||
| 3270 | quicklistPushTail(ql, num, sz); | ||
| 3271 | } | ||
| 3272 | if (fills[f] == 32) | ||
| 3273 | ql_verify(ql, 2, 33, 32, 1); | ||
| 3274 | quicklistDelRange(ql, -12, 3); | ||
| 3275 | if (ql->count != 30) | ||
| 3276 | ERR("Didn't delete exactly three elements! Count is: %lu", | ||
| 3277 | ql->count); | ||
| 3278 | quicklistRelease(ql); | ||
| 3279 | } | ||
| 3280 | } | ||
| 3281 | |||
| 3282 | long long stop = mstime(); | ||
| 3283 | runtime[_i] = stop - start; | ||
| 3284 | } | ||
| 3285 | |||
| 3286 | /* Run a longer test of compression depth outside of primary test loop. */ | ||
| 3287 | int list_sizes[] = {250, 251, 500, 999, 1000}; | ||
| 3288 | long long start = mstime(); | ||
| 3289 | int list_count = accurate ? (int)(sizeof(list_sizes) / sizeof(*list_sizes)) : 1; | ||
| 3290 | for (int list = 0; list < list_count; list++) { | ||
| 3291 | TEST_DESC("verify specific compression of interior nodes with %d list ", | ||
| 3292 | list_sizes[list]) { | ||
| 3293 | for (int f = 0; f < fill_count; f++) { | ||
| 3294 | for (int depth = 1; depth < 40; depth++) { | ||
| 3295 | /* skip over many redundant test cases */ | ||
| 3296 | quicklist *ql = quicklistNew(fills[f], depth); | ||
| 3297 | for (int i = 0; i < list_sizes[list]; i++) { | ||
| 3298 | quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64); | ||
| 3299 | quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64); | ||
| 3300 | } | ||
| 3301 | |||
| 3302 | for (int step = 0; step < 2; step++) { | ||
| 3303 | /* test remove node */ | ||
| 3304 | if (step == 1) { | ||
| 3305 | for (int i = 0; i < list_sizes[list] / 2; i++) { | ||
| 3306 | unsigned char *data; | ||
| 3307 | assert(quicklistPop(ql, QUICKLIST_HEAD, &data, | ||
| 3308 | NULL, NULL)); | ||
| 3309 | zfree(data); | ||
| 3310 | assert(quicklistPop(ql, QUICKLIST_TAIL, &data, | ||
| 3311 | NULL, NULL)); | ||
| 3312 | zfree(data); | ||
| 3313 | } | ||
| 3314 | } | ||
| 3315 | quicklistNode *node = ql->head; | ||
| 3316 | unsigned int low_raw = ql->compress; | ||
| 3317 | unsigned int high_raw = ql->len - ql->compress; | ||
| 3318 | |||
| 3319 | for (unsigned int at = 0; at < ql->len; | ||
| 3320 | at++, node = node->next) { | ||
| 3321 | if (at < low_raw || at >= high_raw) { | ||
| 3322 | if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) { | ||
| 3323 | ERR("Incorrect compression: node %d is " | ||
| 3324 | "compressed at depth %d ((%u, %u); total " | ||
| 3325 | "nodes: %lu; size: %zu)", | ||
| 3326 | at, depth, low_raw, high_raw, ql->len, | ||
| 3327 | node->sz); | ||
| 3328 | } | ||
| 3329 | } else { | ||
| 3330 | if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) { | ||
| 3331 | ERR("Incorrect non-compression: node %d is NOT " | ||
| 3332 | "compressed at depth %d ((%u, %u); total " | ||
| 3333 | "nodes: %lu; size: %zu; attempted: %d)", | ||
| 3334 | at, depth, low_raw, high_raw, ql->len, | ||
| 3335 | node->sz, node->attempted_compress); | ||
| 3336 | } | ||
| 3337 | } | ||
| 3338 | } | ||
| 3339 | } | ||
| 3340 | |||
| 3341 | quicklistRelease(ql); | ||
| 3342 | } | ||
| 3343 | } | ||
| 3344 | } | ||
| 3345 | } | ||
| 3346 | long long stop = mstime(); | ||
| 3347 | |||
| 3348 | printf("\n"); | ||
| 3349 | for (size_t i = 0; i < option_count; i++) | ||
| 3350 | printf("Test Loop %02d: %0.2f seconds.\n", options[i], | ||
| 3351 | (float)runtime[i] / 1000); | ||
| 3352 | printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000); | ||
| 3353 | printf("\n"); | ||
| 3354 | |||
| 3355 | TEST("bookmark get updated to next item") { | ||
| 3356 | quicklist *ql = quicklistNew(1, 0); | ||
| 3357 | quicklistPushTail(ql, "1", 1); | ||
| 3358 | quicklistPushTail(ql, "2", 1); | ||
| 3359 | quicklistPushTail(ql, "3", 1); | ||
| 3360 | quicklistPushTail(ql, "4", 1); | ||
| 3361 | quicklistPushTail(ql, "5", 1); | ||
| 3362 | assert(ql->len==5); | ||
| 3363 | /* add two bookmarks, one pointing to the node before the last. */ | ||
| 3364 | assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next)); | ||
| 3365 | assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev)); | ||
| 3366 | /* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */ | ||
| 3367 | assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev); | ||
| 3368 | assert(quicklistDelRange(ql, -2, 1)); | ||
| 3369 | assert(quicklistBookmarkFind(ql, "_test") == ql->tail); | ||
| 3370 | /* delete the last node, and see that the bookmark was deleted. */ | ||
| 3371 | assert(quicklistDelRange(ql, -1, 1)); | ||
| 3372 | assert(quicklistBookmarkFind(ql, "_test") == NULL); | ||
| 3373 | /* test that other bookmarks aren't affected */ | ||
| 3374 | assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next); | ||
| 3375 | assert(quicklistBookmarkFind(ql, "_missing") == NULL); | ||
| 3376 | assert(ql->len==3); | ||
| 3377 | quicklistBookmarksClear(ql); /* for coverage */ | ||
| 3378 | assert(quicklistBookmarkFind(ql, "_dummy") == NULL); | ||
| 3379 | quicklistRelease(ql); | ||
| 3380 | } | ||
| 3381 | |||
| 3382 | TEST("bookmark limit") { | ||
| 3383 | int i; | ||
| 3384 | quicklist *ql = quicklistNew(1, 0); | ||
| 3385 | quicklistPushHead(ql, "1", 1); | ||
| 3386 | for (i=0; i<QL_MAX_BM; i++) | ||
| 3387 | assert(quicklistBookmarkCreate(&ql, genstr("",i), ql->head)); | ||
| 3388 | /* when all bookmarks are used, creation fails */ | ||
| 3389 | assert(!quicklistBookmarkCreate(&ql, "_test", ql->head)); | ||
| 3390 | /* delete one and see that we can now create another */ | ||
| 3391 | assert(quicklistBookmarkDelete(ql, "0")); | ||
| 3392 | assert(quicklistBookmarkCreate(&ql, "_test", ql->head)); | ||
| 3393 | /* delete one and see that the rest survive */ | ||
| 3394 | assert(quicklistBookmarkDelete(ql, "_test")); | ||
| 3395 | for (i=1; i<QL_MAX_BM; i++) | ||
| 3396 | assert(quicklistBookmarkFind(ql, genstr("",i)) == ql->head); | ||
| 3397 | /* make sure the deleted ones are indeed gone */ | ||
| 3398 | assert(!quicklistBookmarkFind(ql, "0")); | ||
| 3399 | assert(!quicklistBookmarkFind(ql, "_test")); | ||
| 3400 | quicklistRelease(ql); | ||
| 3401 | } | ||
| 3402 | |||
| 3403 | TEST("quicklistCompare cached string2ll optimization") { | ||
| 3404 | quicklist *ql = quicklistNew(-2, 0); | ||
| 3405 | |||
| 3406 | /* Create a list with mixed integer and string entries */ | ||
| 3407 | quicklistPushTail(ql, "123", 3); /* integer as string */ | ||
| 3408 | quicklistPushTail(ql, "456", 3); /* integer as string */ | ||
| 3409 | quicklistPushTail(ql, "hello", 5); /* non-numeric string */ | ||
| 3410 | quicklistPushTail(ql, "789", 3); /* integer as string */ | ||
| 3411 | quicklistPushTail(ql, "world", 5); /* non-numeric string */ | ||
| 3412 | |||
| 3413 | quicklistEntry entry; | ||
| 3414 | quicklistIter iter_local; | ||
| 3415 | |||
| 3416 | /* Test 1: NULL parameters should work without crashing */ | ||
| 3417 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3418 | assert(quicklistNext(&iter_local, &entry)); | ||
| 3419 | assert(quicklistCompare(&entry, (unsigned char *)"123", 3, NULL, NULL) == 1); | ||
| 3420 | assert(quicklistCompare(&entry, (unsigned char *)"456", 3, NULL, NULL) == 0); | ||
| 3421 | ql_reset_iterator(&iter_local); | ||
| 3422 | |||
| 3423 | /* Test 2: Caching with numeric strings */ | ||
| 3424 | long long cached_val = 0; | ||
| 3425 | int cached_valid = 0; | ||
| 3426 | |||
| 3427 | /* First comparison should cache the value */ | ||
| 3428 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3429 | assert(quicklistNext(&iter_local, &entry)); /* entry = "123" */ | ||
| 3430 | assert(quicklistCompare(&entry, (unsigned char *)"123", 3, &cached_val, &cached_valid) == 1); | ||
| 3431 | assert(cached_valid == 1); /* Should be cached as valid */ | ||
| 3432 | assert(cached_val == 123); /* Should have cached value */ | ||
| 3433 | |||
| 3434 | /* Second comparison with same search string should use cache */ | ||
| 3435 | assert(quicklistNext(&iter_local, &entry)); /* entry = "456" */ | ||
| 3436 | assert(quicklistCompare(&entry, (unsigned char *)"123", 3, &cached_val, &cached_valid) == 0); | ||
| 3437 | assert(cached_valid == 1); /* Cache should still be valid */ | ||
| 3438 | assert(cached_val == 123); /* Cache value should be unchanged */ | ||
| 3439 | |||
| 3440 | /* Third comparison with same search string should use cache */ | ||
| 3441 | assert(quicklistNext(&iter_local, &entry)); /* entry = "hello" (string) */ | ||
| 3442 | assert(quicklistCompare(&entry, (unsigned char *)"123", 3, &cached_val, &cached_valid) == 0); | ||
| 3443 | assert(cached_valid == 1); /* Cache should still be valid */ | ||
| 3444 | ql_reset_iterator(&iter_local); | ||
| 3445 | |||
| 3446 | /* Test 3: Caching with non-numeric strings */ | ||
| 3447 | cached_val = 0; | ||
| 3448 | cached_valid = 0; | ||
| 3449 | |||
| 3450 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3451 | assert(quicklistNext(&iter_local, &entry)); /* entry = "123" */ | ||
| 3452 | assert(quicklistCompare(&entry, (unsigned char *)"abc", 3, &cached_val, &cached_valid) == 0); | ||
| 3453 | assert(cached_valid == -1); /* Should be cached as invalid */ | ||
| 3454 | |||
| 3455 | /* Second comparison with same non-numeric string should use cache */ | ||
| 3456 | assert(quicklistNext(&iter_local, &entry)); /* entry = "456" */ | ||
| 3457 | assert(quicklistCompare(&entry, (unsigned char *)"abc", 3, &cached_val, &cached_valid) == 0); | ||
| 3458 | assert(cached_valid == -1); /* Cache should still be invalid */ | ||
| 3459 | ql_reset_iterator(&iter_local); | ||
| 3460 | |||
| 3461 | /* Test 4: String entries should work correctly with both NULL and caching */ | ||
| 3462 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3463 | quicklistNext(&iter_local, &entry); /* skip "123" */ | ||
| 3464 | quicklistNext(&iter_local, &entry); /* skip "456" */ | ||
| 3465 | assert(quicklistNext(&iter_local, &entry)); /* entry = "hello" */ | ||
| 3466 | |||
| 3467 | /* String comparison with NULL parameters */ | ||
| 3468 | assert(quicklistCompare(&entry, (unsigned char *)"hello", 5, NULL, NULL) == 1); | ||
| 3469 | assert(quicklistCompare(&entry, (unsigned char *)"world", 5, NULL, NULL) == 0); | ||
| 3470 | |||
| 3471 | /* String comparison with caching parameters (cache not used for strings) */ | ||
| 3472 | cached_val = 0; | ||
| 3473 | cached_valid = 0; | ||
| 3474 | assert(quicklistCompare(&entry, (unsigned char *)"hello", 5, &cached_val, &cached_valid) == 1); | ||
| 3475 | assert(cached_valid == 0); /* Cache should not be used for string entries */ | ||
| 3476 | ql_reset_iterator(&iter_local); | ||
| 3477 | |||
| 3478 | /* Test 5: Performance verification - cache should reduce conversions */ | ||
| 3479 | /* This test demonstrates the optimization by showing cache reuse */ | ||
| 3480 | cached_val = 0; | ||
| 3481 | cached_valid = 0; | ||
| 3482 | int comparisons = 0; | ||
| 3483 | |||
| 3484 | /* Search for "456" across all integer entries */ | ||
| 3485 | quicklistInitIterator(&iter_local, ql, AL_START_HEAD); | ||
| 3486 | while (quicklistNext(&iter_local, &entry)) { | ||
| 3487 | if (entry.value == NULL) { /* Only test integer entries */ | ||
| 3488 | quicklistCompare(&entry, (unsigned char *)"456", 3, &cached_val, &cached_valid); | ||
| 3489 | comparisons++; | ||
| 3490 | } | ||
| 3491 | } | ||
| 3492 | ql_reset_iterator(&iter_local); | ||
| 3493 | |||
| 3494 | /* After first comparison, cache should be valid and reused for subsequent ones */ | ||
| 3495 | assert(cached_valid == 1); | ||
| 3496 | assert(cached_val == 456); | ||
| 3497 | assert(comparisons >= 2); /* Should have compared against multiple integer entries */ | ||
| 3498 | |||
| 3499 | quicklistRelease(ql); | ||
| 3500 | } | ||
| 3501 | |||
| 3502 | /* Benchmarks for quicklistCompare caching optimization */ | ||
| 3503 | { | ||
| 3504 | printf("\n=== quicklistCompare Caching Benchmarks ===\n"); | ||
| 3505 | |||
| 3506 | /* Create a quicklist with 10K integer elements */ | ||
| 3507 | quicklist *ql = quicklistNew(-2, 0); | ||
| 3508 | char buf[16]; | ||
| 3509 | for (int i = 1; i <= 10000; i++) { | ||
| 3510 | snprintf(buf, sizeof(buf), "%d", i); | ||
| 3511 | quicklistPushTail(ql, buf, strlen(buf)); | ||
| 3512 | } | ||
| 3513 | printf("Created quicklist with %lu integer elements\n", ql->count); | ||
| 3514 | |||
| 3515 | /* Search string that exists in the middle */ | ||
| 3516 | unsigned char *search_str = (unsigned char *)"5000"; | ||
| 3517 | size_t search_len = 4; | ||
| 3518 | int iterations = accurate ? 50000 : 10000; | ||
| 3519 | |||
| 3520 | /* Benchmark 1: quicklistCompare WITHOUT caching (NULL parameters) */ | ||
| 3521 | TEST("Benchmark quicklistCompare without caching") { | ||
| 3522 | long long start = ustime(); | ||
| 3523 | int matches = 0; | ||
| 3524 | |||
| 3525 | for (int iter = 0; iter < iterations; iter++) { | ||
| 3526 | quicklistIter iter_ptr; | ||
| 3527 | quicklistEntry entry; | ||
| 3528 | quicklistInitIterator(&iter_ptr, ql, AL_START_HEAD); | ||
| 3529 | |||
| 3530 | while (quicklistNext(&iter_ptr, &entry)) { | ||
| 3531 | if (entry.value == NULL) { /* Only test integer entries */ | ||
| 3532 | if (quicklistCompare(&entry, search_str, search_len, NULL, NULL)) { | ||
| 3533 | matches++; | ||
| 3534 | } | ||
| 3535 | } | ||
| 3536 | } | ||
| 3537 | ql_reset_iterator(&iter_ptr); | ||
| 3538 | } | ||
| 3539 | |||
| 3540 | long long elapsed = ustime() - start; | ||
| 3541 | printf("Found %d matches in %d iterations\n", matches, iterations); | ||
| 3542 | printf("Without caching: %lld usec (%.2f usec per iteration)\n", | ||
| 3543 | elapsed, (double)elapsed / iterations); | ||
| 3544 | } | ||
| 3545 | |||
| 3546 | /* Benchmark 2: quicklistCompare WITH caching */ | ||
| 3547 | TEST("Benchmark quicklistCompare with caching") { | ||
| 3548 | long long start = ustime(); | ||
| 3549 | int matches = 0; | ||
| 3550 | |||
| 3551 | for (int iter = 0; iter < iterations; iter++) { | ||
| 3552 | /* Reset cache for each iteration to simulate real usage */ | ||
| 3553 | long long cached_val = 0; | ||
| 3554 | int cached_valid = 0; | ||
| 3555 | |||
| 3556 | quicklistIter iter_ptr; | ||
| 3557 | quicklistEntry entry; | ||
| 3558 | quicklistInitIterator(&iter_ptr, ql, AL_START_HEAD); | ||
| 3559 | |||
| 3560 | while (quicklistNext(&iter_ptr, &entry)) { | ||
| 3561 | if (entry.value == NULL) { /* Only test integer entries */ | ||
| 3562 | if (quicklistCompare(&entry, search_str, search_len, &cached_val, &cached_valid)) { | ||
| 3563 | matches++; | ||
| 3564 | } | ||
| 3565 | } | ||
| 3566 | } | ||
| 3567 | ql_reset_iterator(&iter_ptr); | ||
| 3568 | } | ||
| 3569 | |||
| 3570 | long long elapsed = ustime() - start; | ||
| 3571 | printf("Found %d matches in %d iterations\n", matches, iterations); | ||
| 3572 | printf("With caching: %lld usec (%.2f usec per iteration)\n", | ||
| 3573 | elapsed, (double)elapsed / iterations); | ||
| 3574 | } | ||
| 3575 | |||
| 3576 | quicklistRelease(ql); | ||
| 3577 | printf("=== End quicklistCompare Benchmarks ===\n\n"); | ||
| 3578 | } | ||
| 3579 | |||
| 3580 | if (flags & REDIS_TEST_LARGE_MEMORY) { | ||
| 3581 | TEST("compress and decompress quicklist listpack node") { | ||
| 3582 | quicklist *ql = quicklistNew(1, 0); | ||
| 3583 | quicklistNode *node = quicklistCreateNode(ql); | ||
| 3584 | node->entry = lpNew(0); | ||
| 3585 | |||
| 3586 | /* Just to avoid triggering the assertion in __quicklistCompressNode(), | ||
| 3587 | * it disables the passing of quicklist head or tail node. */ | ||
| 3588 | node->prev = quicklistCreateNode(ql); | ||
| 3589 | node->next = quicklistCreateNode(ql); | ||
| 3590 | |||
| 3591 | /* Create a rand string */ | ||
| 3592 | size_t sz = (1 << 25); /* 32MB per one entry */ | ||
| 3593 | unsigned char *s = zmalloc(sz); | ||
| 3594 | randstring(s, sz); | ||
| 3595 | |||
| 3596 | /* Keep filling the node, until it reaches 1GB */ | ||
| 3597 | for (int i = 0; i < 32; i++) { | ||
| 3598 | node->entry = lpAppend(node->entry, s, sz); | ||
| 3599 | size_t oldsize = node->sz; | ||
| 3600 | quicklistNodeUpdateSz(node); | ||
| 3601 | quicklistUpdateAllocSize(ql, node->sz, oldsize); | ||
| 3602 | |||
| 3603 | long long start = mstime(); | ||
| 3604 | assert(__quicklistCompressNode(ql, node)); | ||
| 3605 | assert(__quicklistDecompressNode(ql, node)); | ||
| 3606 | printf("Compress and decompress: %zu MB in %.2f seconds.\n", | ||
| 3607 | node->sz/1024/1024, (float)(mstime() - start) / 1000); | ||
| 3608 | } | ||
| 3609 | |||
| 3610 | zfree(s); | ||
| 3611 | zfree(node->prev); | ||
| 3612 | zfree(node->next); | ||
| 3613 | zfree(node->entry); | ||
| 3614 | zfree(node); | ||
| 3615 | quicklistRelease(ql); | ||
| 3616 | } | ||
| 3617 | |||
| 3618 | #if ULONG_MAX >= 0xffffffffffffffff | ||
| 3619 | TEST("compress and decompress quicklist plain node larger than UINT32_MAX") { | ||
| 3620 | quicklist *ql = quicklistNew(1, 0); | ||
| 3621 | size_t sz = (1ull << 32); | ||
| 3622 | unsigned char *s = zmalloc(sz); | ||
| 3623 | randstring(s, sz); | ||
| 3624 | memcpy(s, "helloworld", 10); | ||
| 3625 | memcpy(s + sz - 10, "1234567890", 10); | ||
| 3626 | |||
| 3627 | quicklistNode *node = __quicklistCreateNode(ql, QUICKLIST_NODE_CONTAINER_PLAIN, s, sz); | ||
| 3628 | |||
| 3629 | /* Just to avoid triggering the assertion in __quicklistCompressNode(), | ||
| 3630 | * it disables the passing of quicklist head or tail node. */ | ||
| 3631 | node->prev = quicklistCreateNode(ql); | ||
| 3632 | node->next = quicklistCreateNode(ql); | ||
| 3633 | |||
| 3634 | long long start = mstime(); | ||
| 3635 | assert(__quicklistCompressNode(ql, node)); | ||
| 3636 | assert(__quicklistDecompressNode(ql, node)); | ||
| 3637 | printf("Compress and decompress: %zu MB in %.2f seconds.\n", | ||
| 3638 | node->sz/1024/1024, (float)(mstime() - start) / 1000); | ||
| 3639 | |||
| 3640 | assert(memcmp(node->entry, "helloworld", 10) == 0); | ||
| 3641 | assert(memcmp(node->entry + sz - 10, "1234567890", 10) == 0); | ||
| 3642 | zfree(node->prev); | ||
| 3643 | zfree(node->next); | ||
| 3644 | zfree(node->entry); | ||
| 3645 | zfree(node); | ||
| 3646 | quicklistRelease(ql); | ||
| 3647 | } | ||
| 3648 | #endif | ||
| 3649 | } | ||
| 3650 | |||
| 3651 | if (!err) | ||
| 3652 | printf("ALL TESTS PASSED!\n"); | ||
| 3653 | else | ||
| 3654 | ERR("Sorry, not all tests passed! In fact, %d tests failed.", err); | ||
| 3655 | |||
| 3656 | return err; | ||
| 3657 | } | ||
| 3658 | #endif | ||
