diff --git a/redis.conf b/redis.conf index cccc48e0026c..47a393c7a8db 100644 --- a/redis.conf +++ b/redis.conf @@ -1737,7 +1737,7 @@ hash-max-listpack-value 64 # per list node. # The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), # but if your use case is unique, adjust the settings as necessary. -list-max-ziplist-size -2 +list-max-listpack-size -2 # Lists may also be compressed. # Compress depth is the number of quicklist ziplist nodes from *each* side of diff --git a/src/config.c b/src/config.c index 7f30436de0b9..3b97ea5b512d 100644 --- a/src/config.c +++ b/src/config.c @@ -2603,7 +2603,7 @@ standardConfig configs[] = { createIntConfig("io-threads", NULL, DEBUG_CONFIG | IMMUTABLE_CONFIG, 1, 128, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */ createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */ - createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL), + createIntConfig("list-max-listpack-size", "list-max-ziplist-size", MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_listpack_size, -2, INTEGER_CONFIG, NULL, NULL), createIntConfig("tcp-keepalive", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcpkeepalive, 300, INTEGER_CONFIG, NULL, NULL), createIntConfig("cluster-migration-barrier", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_migration_barrier, 1, INTEGER_CONFIG, NULL, NULL), createIntConfig("active-defrag-cycle-min", NULL, MODIFIABLE_CONFIG, 1, 99, server.active_defrag_cycle_min, 1, INTEGER_CONFIG, NULL, NULL), /* Default: 1% CPU min (at lower threshold) */ diff --git a/src/db.c b/src/db.c index 8f1b376a09c3..8d9be361cffa 100644 --- a/src/db.c +++ b/src/db.c @@ -847,7 +847,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { /* Step 2: Iterate the collection. * - * Note that if the object is encoded with a ziplist, intset, or any other + * Note that if the object is encoded with a listpack, intset, or any other * representation that is not a hash table, we are sure that it is also * composed of a small number of elements. So to avoid taking state we * just return everything inside the object in a single call, setting the diff --git a/src/debug.c b/src/debug.c index 9ffc4e8dd4f5..e86462cd1bd7 100644 --- a/src/debug.c +++ b/src/debug.c @@ -473,8 +473,8 @@ void debugCommand(client *c) { " Run a fuzz tester against the stringmatchlen() function.", "STRUCTSIZE", " Return the size of different Redis core C structures.", -"ZIPLIST ", -" Show low level info about the ziplist encoding of .", +"LISTPACK ", +" Show low level info about the listpack encoding of .", "QUICKLIST [<0|1>]", " Show low level info about the quicklist encoding of ." " The optional argument (0 by default) sets the level of detail", @@ -602,8 +602,8 @@ NULL used = snprintf(nextra, remaining, " ql_avg_node:%.2f", avg); nextra += used; remaining -= used; - /* Add quicklist fill level / max ziplist size */ - used = snprintf(nextra, remaining, " ql_ziplist_max:%d", ql->fill); + /* Add quicklist fill level / max listpack size */ + used = snprintf(nextra, remaining, " ql_listpack_max:%d", ql->fill); nextra += used; remaining -= used; /* Add isCompressed? */ @@ -653,17 +653,17 @@ NULL (long long) sdsavail(val->ptr), (long long) getStringObjectSdsUsedMemory(val)); } - } else if (!strcasecmp(c->argv[1]->ptr,"ziplist") && c->argc == 3) { + } else if (!strcasecmp(c->argv[1]->ptr,"listpack") && c->argc == 3) { robj *o; if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr)) == NULL) return; - if (o->encoding != OBJ_ENCODING_ZIPLIST) { - addReplyError(c,"Not a ziplist encoded object."); + if (o->encoding != OBJ_ENCODING_LISTPACK) { + addReplyError(c,"Not a listpack encoded object."); } else { - ziplistRepr(o->ptr); - addReplyStatus(c,"Ziplist structure printed on stdout"); + lpRepr(o->ptr); + addReplyStatus(c,"Listpack structure printed on stdout"); } } else if (!strcasecmp(c->argv[1]->ptr,"quicklist") && (c->argc == 3 || c->argc == 4)) { robj *o; diff --git a/src/listpack.c b/src/listpack.c index 0d91cb55bd24..9d412d07ca82 100644 --- a/src/listpack.c +++ b/src/listpack.c @@ -1021,7 +1021,7 @@ unsigned char *lpDeleteRangeWithEntry(unsigned char *lp, unsigned char **p, unsi * address again after a reallocation. */ unsigned long poff = first-lp; - /* Move tail to the front of the ziplist */ + /* Move tail to the front of the listpack */ memmove(first, tail, eofptr - tail + 1); lpSetTotalBytes(lp, bytes - (tail - first)); uint32_t numele = lpGetNumElements(lp); @@ -1064,6 +1064,103 @@ unsigned char *lpDeleteRange(unsigned char *lp, long index, unsigned long num) { return lp; } +/* Merge listpacks 'first' and 'second' by appending 'second' to 'first'. + * + * NOTE: The larger listpack is reallocated to contain the new merged listpack. + * Either 'first' or 'second' can be used for the result. The parameter not + * used will be free'd and set to NULL. + * + * After calling this function, the input parameters are no longer valid since + * they are changed and free'd in-place. + * + * The result listpack is the contents of 'first' followed by 'second'. + * + * On failure: returns NULL if the merge is impossible. + * On success: returns the merged listpack (which is expanded version of either + * 'first' or 'second', also frees the other unused input listpack, and sets the + * input listpack argument equal to newly reallocated listpack return value. */ +unsigned char *lpMerge(unsigned char **first, unsigned char **second) { + /* If any params are null, we can't merge, so NULL. */ + if (first == NULL || *first == NULL || second == NULL || *second == NULL) + return NULL; + + /* Can't merge same list into itself. */ + if (*first == *second) + return NULL; + + size_t first_bytes = lpBytes(*first); + unsigned long first_len = lpLength(*first); + + size_t second_bytes = lpBytes(*second); + unsigned long second_len = lpLength(*second); + + int append; + unsigned char *source, *target; + size_t target_bytes, source_bytes; + /* Pick the largest listpack so we can resize easily in-place. + * We must also track if we are now appending or prepending to + * the target listpack. */ + if (first_bytes >= second_bytes) { + /* retain first, append second to first. */ + target = *first; + target_bytes = first_bytes; + source = *second; + source_bytes = second_bytes; + append = 1; + } else { + /* else, retain second, prepend first to second. */ + target = *second; + target_bytes = second_bytes; + source = *first; + source_bytes = first_bytes; + append = 0; + } + + /* Calculate final bytes (subtract one pair of metadata) */ + unsigned long long lpbytes = (unsigned long long)first_bytes + second_bytes - LP_HDR_SIZE - 1; + assert(lpbytes < UINT32_MAX); /* larger values can't be stored */ + unsigned long lplength = first_len + second_len; + + /* Combined lp length should be limited within UINT16_MAX */ + lplength = lplength < UINT16_MAX ? lplength : UINT16_MAX; + + /* Extend target to new lpbytes then append or prepend source. */ + target = zrealloc(target, lpbytes); + if (append) { + /* append == appending to target */ + /* Copy source after target (copying over original [END]): + * [TARGET - END, SOURCE - HEADER] */ + memcpy(target + target_bytes - 1, + source + LP_HDR_SIZE, + source_bytes - LP_HDR_SIZE); + } else { + /* !append == prepending to target */ + /* Move target *contents* exactly size of (source - [END]), + * then copy source into vacated space (source - [END]): + * [SOURCE - END, TARGET - HEADER] */ + memmove(target + source_bytes - 1, + target + LP_HDR_SIZE, + target_bytes - LP_HDR_SIZE); + memcpy(target, source, source_bytes - 1); + } + + lpSetNumElements(target, lplength); + lpSetTotalBytes(target, lpbytes); + + /* Now free and NULL out what we didn't realloc */ + if (append) { + zfree(*second); + *second = NULL; + *first = target; + } else { + zfree(*first); + *first = NULL; + *second = target; + } + + return target; +} + /* Return the total number of bytes the listpack is composed of. */ size_t lpBytes(unsigned char *lp) { return lpGetTotalBytes(lp); @@ -1377,6 +1474,57 @@ unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpack return picked; } +/* Print info of listpack which is used in debugCommand */ +void lpRepr(unsigned char *lp) { + unsigned char *p, *vstr; + int64_t vlen; + unsigned char intbuf[LP_INTBUF_SIZE]; + int index = 0; + + printf("{total bytes %zu} {num entries %lu}\n", lpBytes(lp), lpLength(lp)); + + p = lpFirst(lp); + while(p) { + uint32_t encoded_size_bytes = lpCurrentEncodedSizeBytes(p); + uint32_t encoded_size = lpCurrentEncodedSizeUnsafe(p); + unsigned long back_len = lpEncodeBacklen(NULL, encoded_size); + printf( + "{\n" + "\taddr: 0x%08lx,\n" + "\tindex: %2d,\n" + "\toffset: %1lu,\n" + "\thdr+entrylen+backlen: %2lu,\n" + "\thdrlen: %3u,\n" + "\tbacklen: %2lu,\n" + "\tpayload: %1u\n", + (long unsigned)p, + index, + (unsigned long) (p-lp), + encoded_size + back_len, + encoded_size_bytes, + back_len, + encoded_size - encoded_size_bytes); + printf("\tbytes: "); + for (unsigned int i = 0; i < (encoded_size + back_len); i++) { + printf("%02x|",p[i]); + } + printf("\n"); + + vstr = lpGet(p, &vlen, intbuf); + printf("\t[str]"); + if (vlen > 40) { + if (fwrite(vstr, 40, 1, stdout) == 0) perror("fwrite"); + printf("..."); + } else { + if (fwrite(vstr, vlen, 1, stdout) == 0) perror("fwrite"); + } + printf("\n}\n"); + index++; + p = lpNext(lp, p); + } + printf("{end}\n\n"); +} + #ifdef REDIS_TEST #include @@ -1845,6 +1993,58 @@ int listpackTest(int argc, char *argv[], int flags) { lpFree(lp); } + TEST("lpMerge two empty listpacks") { + unsigned char *lp1 = lpNew(0); + unsigned char *lp2 = lpNew(0); + + /* Merge two empty listpacks, get empty result back. */ + lp1 = lpMerge(&lp1, &lp2); + assert(lpLength(lp1) == 0); + zfree(lp1); + } + + TEST("lpMerge two listpacks - first larger than second") { + unsigned char *lp1 = createIntList(); + unsigned char *lp2 = createList(); + + size_t lp1_bytes = lpBytes(lp1); + size_t lp2_bytes = lpBytes(lp2); + unsigned long lp1_len = lpLength(lp1); + unsigned long lp2_len = lpLength(lp2); + + unsigned char *lp3 = lpMerge(&lp1, &lp2); + assert(lp3 == lp1); + assert(lp2 == NULL); + assert(lpLength(lp3) == (lp1_len + lp2_len)); + assert(lpBytes(lp3) == (lp1_bytes + lp2_bytes - LP_HDR_SIZE - 1)); + verifyEntry(lpSeek(lp3, 0), (unsigned char*)"4294967296", 10); + verifyEntry(lpSeek(lp3, 5), (unsigned char*)"much much longer non integer", 28); + verifyEntry(lpSeek(lp3, 6), (unsigned char*)"hello", 5); + verifyEntry(lpSeek(lp3, -1), (unsigned char*)"1024", 4); + zfree(lp3); + } + + TEST("lpMerge two listpacks - second larger than first") { + unsigned char *lp1 = createList(); + unsigned char *lp2 = createIntList(); + + size_t lp1_bytes = lpBytes(lp1); + size_t lp2_bytes = lpBytes(lp2); + unsigned long lp1_len = lpLength(lp1); + unsigned long lp2_len = lpLength(lp2); + + unsigned char *lp3 = lpMerge(&lp1, &lp2); + assert(lp3 == lp2); + assert(lp1 == NULL); + assert(lpLength(lp3) == (lp1_len + lp2_len)); + assert(lpBytes(lp3) == (lp1_bytes + lp2_bytes - LP_HDR_SIZE - 1)); + verifyEntry(lpSeek(lp3, 0), (unsigned char*)"hello", 5); + verifyEntry(lpSeek(lp3, 3), (unsigned char*)"1024", 4); + verifyEntry(lpSeek(lp3, 4), (unsigned char*)"4294967296", 10); + verifyEntry(lpSeek(lp3, -1), (unsigned char*)"much much longer non integer", 28); + zfree(lp3); + } + TEST("Random pair with one element") { listpackEntry key, val; unsigned char *lp = lpNew(0); diff --git a/src/listpack.h b/src/listpack.h index c380940778c1..6c4d6bdd6f7b 100644 --- a/src/listpack.h +++ b/src/listpack.h @@ -68,6 +68,7 @@ unsigned char *lpReplaceInteger(unsigned char *lp, unsigned char **p, long long unsigned char *lpDelete(unsigned char *lp, unsigned char *p, unsigned char **newp); unsigned char *lpDeleteRangeWithEntry(unsigned char *lp, unsigned char **p, unsigned long num); unsigned char *lpDeleteRange(unsigned char *lp, long index, unsigned long num); +unsigned char *lpMerge(unsigned char **first, unsigned char **second); unsigned long lpLength(unsigned char *lp); unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf); unsigned char *lpGetValue(unsigned char *p, unsigned int *slen, long long *lval); @@ -88,6 +89,7 @@ void lpRandomPair(unsigned char *lp, unsigned long total_count, listpackEntry *k void lpRandomPairs(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals); unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpackEntry *keys, listpackEntry *vals); int lpSafeToAdd(unsigned char* lp, size_t add); +void lpRepr(unsigned char *lp); #ifdef REDIS_TEST int listpackTest(int argc, char *argv[], int flags); diff --git a/src/module.c b/src/module.c index 498588ff14bd..6faf1e9ec3ef 100644 --- a/src/module.c +++ b/src/module.c @@ -526,7 +526,7 @@ int moduleCreateEmptyKey(RedisModuleKey *key, int type) { switch(type) { case REDISMODULE_KEYTYPE_LIST: obj = createQuicklistObject(); - quicklistSetOptions(obj->ptr, server.list_max_ziplist_size, + quicklistSetOptions(obj->ptr, server.list_max_listpack_size, server.list_compress_depth); break; case REDISMODULE_KEYTYPE_ZSET: diff --git a/src/object.c b/src/object.c index 0ef41f065ecb..5831f196d8d2 100644 --- a/src/object.c +++ b/src/object.c @@ -233,13 +233,6 @@ robj *createQuicklistObject(void) { return o; } -robj *createZiplistObject(void) { - unsigned char *zl = ziplistNew(); - robj *o = createObject(OBJ_LIST,zl); - o->encoding = OBJ_ENCODING_ZIPLIST; - return o; -} - robj *createSetObject(void) { dict *d = dictCreate(&setDictType); robj *o = createObject(OBJ_SET,d); diff --git a/src/quicklist.c b/src/quicklist.c index 98ede7d90c72..346f44b73b83 100644 --- a/src/quicklist.c +++ b/src/quicklist.c @@ -1,4 +1,4 @@ -/* quicklist.c - A doubly linked list of ziplists +/* quicklist.c - A doubly linked list of listpacks * * Copyright (c) 2014, Matt Stancliff * All rights reserved. @@ -33,7 +33,7 @@ #include "quicklist.h" #include "zmalloc.h" #include "config.h" -#include "ziplist.h" +#include "listpack.h" #include "util.h" /* for ll2string */ #include "lzf.h" #include "redisassert.h" @@ -62,14 +62,20 @@ int quicklistisSetPackedThreshold(size_t sz) { return 1; } -/* Maximum size in bytes of any multi-element ziplist. - * Larger values will live in their own isolated ziplists. +/* Maximum size in bytes of any multi-element listpack. + * Larger values will live in their own isolated listpacks. * This is used only if we're limited by record count. when we're limited by * size, the maximum limit is bigger, but still safe. * 8k is a recommended / default size limit */ #define SIZE_SAFETY_LIMIT 8192 -/* Minimum ziplist size in bytes for attempting compression. */ +/* Maximum estimate of the listpack entry overhead. + * Although in the worst case(sz < 64), we will waste 6 bytes in one + * quicklistNode, but can avoid memory waste due to internal fragmentation + * when the listpack exceeds the size limit by a few bytes (e.g. being 16388). */ +#define SIZE_ESTIMATE_OVERHEAD 8 + +/* Minimum listpack size in bytes for attempting compression. */ #define MIN_COMPRESS_BYTES 48 /* Minimum size reduction in bytes to store compressed quicklistNode data. @@ -161,7 +167,7 @@ REDIS_STATIC quicklistNode *quicklistCreateNode(void) { node->sz = 0; node->next = node->prev = NULL; node->encoding = QUICKLIST_NODE_ENCODING_RAW; - node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST; + node->container = QUICKLIST_NODE_CONTAINER_PACKED; node->recompress = 0; return node; } @@ -191,9 +197,9 @@ void quicklistRelease(quicklist *quicklist) { zfree(quicklist); } -/* Compress the ziplist in 'node' and update encoding details. - * Returns 1 if ziplist compressed successfully. - * Returns 0 if compression failed or if ziplist too small to compress. */ +/* Compress the listpack in 'node' and update encoding details. + * Returns 1 if listpack compressed successfully. + * Returns 0 if compression failed or if listpack too small to compress. */ REDIS_STATIC int __quicklistCompressNode(quicklistNode *node) { #ifdef REDIS_TEST node->attempted_compress = 1; @@ -233,7 +239,7 @@ REDIS_STATIC int __quicklistCompressNode(quicklistNode *node) { } \ } while (0) -/* Uncompress the ziplist in 'node' and update encoding details. +/* Uncompress the listpack in 'node' and update encoding details. * Returns 1 on successful decode, 0 on failure to decode. */ REDIS_STATIC int __quicklistDecompressNode(quicklistNode *node) { #ifdef REDIS_TEST @@ -280,36 +286,6 @@ size_t quicklistGetLzf(const quicklistNode *node, void **data) { return lzf->sz; } -void quicklistRepr(unsigned char *ql, int full) { - int i = 0; - quicklist *quicklist = (struct quicklist*) ql; - printf("{count : %ld}\n", quicklist->count); - printf("{len : %ld}\n", quicklist->len); - printf("{fill : %d}\n", quicklist->fill); - printf("{compress : %d}\n", quicklist->compress); - printf("{bookmark_count : %d}\n", quicklist->bookmark_count); - quicklistNode* node = quicklist->head; - - while(node != NULL) { - printf("{quicklist node(%d)\n", i++); - printf("{container : %s, encoding: %s, size: %zu}\n", QL_NODE_IS_PLAIN(node) ? "PLAIN": "PACKED", - (node->encoding == QUICKLIST_NODE_ENCODING_RAW) ? "RAW": "LZF", node->sz); - - if (full) { - if (node->container == QUICKLIST_NODE_CONTAINER_ZIPLIST) { - printf("{ ziplist:\n"); - ziplistRepr(node->entry); - printf("}\n"); - - } else if (QL_NODE_IS_PLAIN(node)) { - printf("{ entry : %s }\n", node->entry); - } - printf("}\n"); - } - node = node->next; - } -} - #define quicklistAllowsCompression(_ql) ((_ql)->compress != 0) /* Force 'quicklist' to meet compression guidelines set by compress depth. @@ -398,7 +374,7 @@ REDIS_STATIC void __quicklistCompress(const quicklist *quicklist, } while (0) /* If we previously used quicklistDecompressNodeForUse(), just recompress. */ -#define quicklistRecompressOnly(_ql, _node) \ +#define quicklistRecompressOnly(_node) \ do { \ if ((_node)->recompress) \ quicklistCompressNode((_node)); \ @@ -485,23 +461,12 @@ REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node, if (unlikely(QL_NODE_IS_PLAIN(node) || isLargeElement(sz))) return 0; - int ziplist_overhead; - /* size of previous offset */ - if (sz < 254) - ziplist_overhead = 1; - else - ziplist_overhead = 5; - - /* size of forward offset */ - if (sz < 64) - ziplist_overhead += 1; - else if (likely(sz < 16384)) - ziplist_overhead += 2; - else - ziplist_overhead += 5; - - /* new_sz overestimates if 'sz' encodes to an integer type */ - unsigned int new_sz = node->sz + sz + ziplist_overhead; + /* Estimate how many bytes will be added to the listpack by this one entry. + * We prefer an overestimation, which would at worse lead to a few bytes + * below the lowest limit of 4k (see optimization_level). + * Note: No need to check for overflow below since both `node->sz` and + * `sz` are to be less than 1GB after the plain/large element check above. */ + size_t new_sz = node->sz + sz + SIZE_ESTIMATE_OVERHEAD; if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill))) return 1; /* when we return 1 above we know that the limit is a size limit (which is @@ -523,7 +488,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a, if (unlikely(QL_NODE_IS_PLAIN(a) || QL_NODE_IS_PLAIN(b))) return 0; - /* approximate merged ziplist size (- 11 to remove one ziplist + /* approximate merged listpack size (- 11 to remove one listpack * header/trailer) */ unsigned int merge_sz = a->sz + b->sz - 11; if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill))) @@ -540,7 +505,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a, #define quicklistNodeUpdateSz(node) \ do { \ - (node)->sz = ziplistBlobLen((node)->entry); \ + (node)->sz = lpBytes((node)->entry); \ } while (0) static quicklistNode* __quicklistCreatePlainNode(void *value, size_t sz) { @@ -573,12 +538,11 @@ int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) { if (likely( _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) { - quicklist->head->entry = - ziplistPush(quicklist->head->entry, value, sz, ZIPLIST_HEAD); + quicklist->head->entry = lpPrepend(quicklist->head->entry, value, sz); quicklistNodeUpdateSz(quicklist->head); } else { quicklistNode *node = quicklistCreateNode(); - node->entry = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); + node->entry = lpPrepend(lpNew(0), value, sz); quicklistNodeUpdateSz(node); _quicklistInsertNodeBefore(quicklist, quicklist->head, node); @@ -601,12 +565,11 @@ int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) { if (likely( _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) { - quicklist->tail->entry = - ziplistPush(quicklist->tail->entry, value, sz, ZIPLIST_TAIL); + quicklist->tail->entry = lpAppend(quicklist->tail->entry, value, sz); quicklistNodeUpdateSz(quicklist->tail); } else { quicklistNode *node = quicklistCreateNode(); - node->entry = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL); + node->entry = lpAppend(lpNew(0), value, sz); quicklistNodeUpdateSz(node); _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); @@ -616,15 +579,15 @@ int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) { return (orig_tail != quicklist->tail); } -/* Create new node consisting of a pre-formed ziplist. - * Used for loading RDBs where entire ziplists have been stored +/* Create new node consisting of a pre-formed listpack. + * Used for loading RDBs where entire listpacks have been stored * to be retrieved later. */ -void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) { +void quicklistAppendListpack(quicklist *quicklist, unsigned char *zl) { quicklistNode *node = quicklistCreateNode(); node->entry = zl; - node->count = ziplistLen(node->entry); - node->sz = ziplistBlobLen(zl); + node->count = lpLength(node->entry); + node->sz = lpBytes(zl); _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); quicklist->count += node->count; @@ -635,42 +598,15 @@ void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl) { * to be retrieved later. * data - the data to add (pointer becomes the responsibility of quicklist) */ void quicklistAppendPlainNode(quicklist *quicklist, unsigned char *data, size_t sz) { - __quicklistInsertPlainNode(quicklist, quicklist->tail, data, sz, 1); -} - -/* Append all values of ziplist 'zl' individually into 'quicklist'. - * - * This allows us to restore old RDB ziplists into new quicklists - * with smaller ziplist sizes than the saved RDB ziplist. - * - * Returns 'quicklist' argument. Frees passed-in ziplist 'zl' */ -quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist, - unsigned char *zl) { - unsigned char *value; - unsigned int sz; - long long longval; - char longstr[32] = {0}; + quicklistNode *node = quicklistCreateNode(); - unsigned char *p = ziplistIndex(zl, 0); - while (ziplistGet(p, &value, &sz, &longval)) { - if (!value) { - /* Write the longval as a string so we can re-add it */ - sz = ll2string(longstr, sizeof(longstr), longval); - value = (unsigned char *)longstr; - } - quicklistPushTail(quicklist, value, sz); - p = ziplistNext(zl, p); - } - zfree(zl); - return quicklist; -} + node->entry = data; + node->count = 1; + node->sz = sz; + node->container = QUICKLIST_NODE_CONTAINER_PLAIN; -/* Create new (potentially multi-node) quicklist from a single existing ziplist. - * - * Returns new quicklist. Frees passed-in ziplist 'zl'. */ -quicklist *quicklistCreateFromZiplist(int fill, int compress, - unsigned char *zl) { - return quicklistAppendValuesFromZiplist(quicklistNew(fill, compress), zl); + _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); + quicklist->count += node->count; } #define quicklistDeleteIfEmpty(ql, n) \ @@ -724,7 +660,7 @@ REDIS_STATIC void __quicklistDelNode(quicklist *quicklist, * already had to get *p from an uncompressed node somewhere. * * Returns 1 if the entire node was deleted, 0 if node still exists. - * Also updates in/out param 'p' with the next offset in the ziplist. */ + * Also updates in/out param 'p' with the next offset in the listpack. */ REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node, unsigned char **p) { int gone = 0; @@ -733,7 +669,7 @@ REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node, __quicklistDelNode(quicklist, node); return 1; } - node->entry = ziplistDelete(node->entry, p); + node->entry = lpDelete(node->entry, *p, p); node->count--; if (node->count == 0) { gone = 1; @@ -749,7 +685,7 @@ REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node, /* Delete one element represented by 'entry' * * 'entry' stores enough metadata to delete the proper position in - * the correct ziplist in the correct quicklist node. */ + * the correct listpack in the correct quicklist node. */ void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) { quicklistNode *prev = entry->node->prev; quicklistNode *next = entry->node->next; @@ -775,7 +711,7 @@ void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) { * - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1 * - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0 * if we deleted the last element at offset N and now - * length of this ziplist is N-1, the next call into + * length of this listpack is N-1, the next call into * quicklistNext() will jump to the next node. */ } @@ -783,7 +719,7 @@ void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) { void quicklistReplaceEntry(quicklist *quicklist, quicklistEntry *entry, void *data, size_t sz) { if (likely(!QL_NODE_IS_PLAIN(entry->node) && !isLargeElement(sz))) { - entry->node->entry = ziplistReplace(entry->node->entry, entry->zi, data, sz); + entry->node->entry = lpReplace(entry->node->entry, &entry->zi, data, sz); quicklistNodeUpdateSz(entry->node); /* quicklistNext() and quicklistIndex() provide an uncompressed node */ quicklistCompress(quicklist, entry->node); @@ -803,7 +739,7 @@ void quicklistReplaceEntry(quicklist *quicklist, quicklistEntry *entry, if (entry->node->count == 1) __quicklistDelNode(quicklist, entry->node); else { - unsigned char *p = ziplistIndex(entry->node->entry, -1); + unsigned char *p = lpSeek(entry->node->entry, -1); quicklistDelIndex(quicklist, entry->node, &p); quicklistCompress(quicklist, entry->node->next); } @@ -825,9 +761,9 @@ int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, } } -/* Given two nodes, try to merge their ziplists. +/* Given two nodes, try to merge their listpacks. * - * This helps us not have a quicklist with 3 element ziplists if + * This helps us not have a quicklist with 3 element listpacks if * our fill factor can handle much higher levels. * * Note: 'a' must be to the LEFT of 'b'. @@ -838,15 +774,15 @@ int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, * * Returns the input node picked to merge against or NULL if * merging was not possible. */ -REDIS_STATIC quicklistNode *_quicklistZiplistMerge(quicklist *quicklist, - quicklistNode *a, - quicklistNode *b) { +REDIS_STATIC quicklistNode *_quicklistListpackMerge(quicklist *quicklist, + quicklistNode *a, + quicklistNode *b) { D("Requested merge (a,b) (%u, %u)", a->count, b->count); quicklistDecompressNode(a); quicklistDecompressNode(b); - if ((ziplistMerge(&a->entry, &b->entry))) { - /* We merged ziplists! Now remove the unused quicklistNode. */ + if ((lpMerge(&a->entry, &b->entry))) { + /* We merged listpacks! Now remove the unused quicklistNode. */ quicklistNode *keep = NULL, *nokeep = NULL; if (!a->entry) { nokeep = a; @@ -855,7 +791,7 @@ REDIS_STATIC quicklistNode *_quicklistZiplistMerge(quicklist *quicklist, nokeep = b; keep = a; } - keep->count = ziplistLen(keep->entry); + keep->count = lpLength(keep->entry); quicklistNodeUpdateSz(keep); nokeep->count = 0; @@ -868,7 +804,7 @@ REDIS_STATIC quicklistNode *_quicklistZiplistMerge(quicklist *quicklist, } } -/* Attempt to merge ziplists within two nodes on either side of 'center'. +/* Attempt to merge listpacks within two nodes on either side of 'center'. * * We attempt to merge: * - (center->prev->prev, center->prev) @@ -896,19 +832,19 @@ REDIS_STATIC void _quicklistMergeNodes(quicklist *quicklist, /* Try to merge prev_prev and prev */ if (_quicklistNodeAllowMerge(prev, prev_prev, fill)) { - _quicklistZiplistMerge(quicklist, prev_prev, prev); + _quicklistListpackMerge(quicklist, prev_prev, prev); prev_prev = prev = NULL; /* they could have moved, invalidate them. */ } /* Try to merge next and next_next */ if (_quicklistNodeAllowMerge(next, next_next, fill)) { - _quicklistZiplistMerge(quicklist, next, next_next); + _quicklistListpackMerge(quicklist, next, next_next); next = next_next = NULL; /* they could have moved, invalidate them. */ } /* Try to merge center node and previous node */ if (_quicklistNodeAllowMerge(center, center->prev, fill)) { - target = _quicklistZiplistMerge(quicklist, center->prev, center); + target = _quicklistListpackMerge(quicklist, center->prev, center); center = NULL; /* center could have been deleted, invalidate it. */ } else { /* else, we didn't merge here, but target needs to be valid below. */ @@ -917,7 +853,7 @@ REDIS_STATIC void _quicklistMergeNodes(quicklist *quicklist, /* Use result of center merge (or original) to merge with next node. */ if (_quicklistNodeAllowMerge(target, target->next, fill)) { - _quicklistZiplistMerge(quicklist, target, target->next); + _quicklistListpackMerge(quicklist, target, target->next); } } @@ -947,7 +883,7 @@ REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset, quicklistNode *new_node = quicklistCreateNode(); new_node->entry = zmalloc(zl_sz); - /* Copy original ziplist so we can split it */ + /* Copy original listpack so we can split it */ memcpy(new_node->entry, node->entry, zl_sz); /* Ranges to be trimmed: -1 here means "continue deleting until the list ends" */ @@ -959,12 +895,12 @@ REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset, D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start, orig_extent, new_start, new_extent); - node->entry = ziplistDeleteRange(node->entry, orig_start, orig_extent); - node->count = ziplistLen(node->entry); + node->entry = lpDeleteRange(node->entry, orig_start, orig_extent); + node->count = lpLength(node->entry); quicklistNodeUpdateSz(node); - new_node->entry = ziplistDeleteRange(new_node->entry, new_start, new_extent); - new_node->count = ziplistLen(new_node->entry); + new_node->entry = lpDeleteRange(new_node->entry, new_start, new_extent); + new_node->count = lpLength(new_node->entry); quicklistNodeUpdateSz(new_node); D("After split lengths: orig (%d), new (%d)", node->count, new_node->count); @@ -990,7 +926,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry, return; } new_node = quicklistCreateNode(); - new_node->entry = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); + new_node->entry = lpPrepend(lpNew(0), value, sz); __quicklistInsertNode(quicklist, NULL, new_node, after); new_node->count++; quicklist->count++; @@ -1005,7 +941,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry, } if (after && (entry->offset == node->count - 1 || entry->offset == -1)) { - D("At Tail of current ziplist"); + D("At Tail of current listpack"); at_tail = 1; if (_quicklistNodeAllowInsert(node->next, fill, sz)) { D("Next node is available."); @@ -1040,49 +976,44 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry, if (!full && after) { D("Not full, inserting after current position."); quicklistDecompressNodeForUse(node); - unsigned char *next = ziplistNext(node->entry, entry->zi); - if (next == NULL) { - node->entry = ziplistPush(node->entry, value, sz, ZIPLIST_TAIL); - } else { - node->entry = ziplistInsert(node->entry, next, value, sz); - } + node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_AFTER, NULL); node->count++; quicklistNodeUpdateSz(node); - quicklistRecompressOnly(quicklist, node); + quicklistRecompressOnly(node); } else if (!full && !after) { D("Not full, inserting before current position."); quicklistDecompressNodeForUse(node); - node->entry = ziplistInsert(node->entry, entry->zi, value, sz); + node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_BEFORE, NULL); node->count++; quicklistNodeUpdateSz(node); - quicklistRecompressOnly(quicklist, node); + quicklistRecompressOnly(node); } else if (full && at_tail && avail_next && after) { /* If we are: at tail, next has free space, and inserting after: * - insert entry at head of next node. */ D("Full and tail, but next isn't full; inserting next node head"); new_node = node->next; quicklistDecompressNodeForUse(new_node); - new_node->entry = ziplistPush(new_node->entry, value, sz, ZIPLIST_HEAD); + new_node->entry = lpPrepend(new_node->entry, value, sz); new_node->count++; quicklistNodeUpdateSz(new_node); - quicklistRecompressOnly(quicklist, new_node); + quicklistRecompressOnly(new_node); } else if (full && at_head && avail_prev && !after) { /* If we are: at head, previous has free space, and inserting before: * - insert entry at tail of previous node. */ D("Full and head, but prev isn't full, inserting prev node tail"); new_node = node->prev; quicklistDecompressNodeForUse(new_node); - new_node->entry = ziplistPush(new_node->entry, value, sz, ZIPLIST_TAIL); + new_node->entry = lpAppend(new_node->entry, value, sz); new_node->count++; quicklistNodeUpdateSz(new_node); - quicklistRecompressOnly(quicklist, new_node); + quicklistRecompressOnly(new_node); } else if (full && ((at_tail && !avail_next && after) || (at_head && !avail_prev && !after))) { /* If we are: full, and our prev/next has no available space, then: * - create new node and attach to quicklist */ D("\tprovisioning new node..."); new_node = quicklistCreateNode(); - new_node->entry = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); + new_node->entry = lpPrepend(lpNew(0), value, sz); new_node->count++; quicklistNodeUpdateSz(new_node); __quicklistInsertNode(quicklist, node, new_node, after); @@ -1092,8 +1023,10 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry, D("\tsplitting node..."); quicklistDecompressNodeForUse(node); new_node = _quicklistSplitNode(node, entry->offset, after); - new_node->entry = ziplistPush(new_node->entry, value, sz, - after ? ZIPLIST_HEAD : ZIPLIST_TAIL); + if (after) + new_node->entry = lpPrepend(new_node->entry, value, sz); + else + new_node->entry = lpAppend(new_node->entry, value, sz); new_node->count++; quicklistNodeUpdateSz(new_node); __quicklistInsertNode(quicklist, node, new_node, after); @@ -1150,7 +1083,7 @@ int quicklistDelRange(quicklist *quicklist, const long start, int delete_entire_node = 0; if (entry.offset == 0 && extent >= node->count) { /* If we are deleting more than the count of this node, we - * can just delete the entire node without ziplist math. */ + * can just delete the entire node without listpack math. */ delete_entire_node = 1; del = node->count; } else if (entry.offset >= 0 && extent + entry.offset >= node->count) { @@ -1184,13 +1117,13 @@ int quicklistDelRange(quicklist *quicklist, const long start, __quicklistDelNode(quicklist, node); } else { quicklistDecompressNodeForUse(node); - node->entry = ziplistDeleteRange(node->entry, entry.offset, del); + node->entry = lpDeleteRange(node->entry, entry.offset, del); quicklistNodeUpdateSz(node); node->count -= del; quicklist->count -= del; quicklistDeleteIfEmpty(quicklist, node); if (node) - quicklistRecompressOnly(quicklist, node); + quicklistRecompressOnly(node); } extent -= del; @@ -1207,7 +1140,7 @@ int quicklistCompare(quicklistEntry* entry, unsigned char *p2, const size_t p2_l if (unlikely(QL_NODE_IS_PLAIN(entry->node))) { return ((entry->sz == p2_len) && (memcmp(entry->value, p2, p2_len) == 0)); } - return ziplistCompare(entry->zi, p2, p2_len); + return lpCompare(entry->zi, p2, p2_len); } /* Returns a quicklist iterator 'iter'. After the initialization every @@ -1307,16 +1240,16 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { if (unlikely(plain)) iter->zi = iter->current->entry; else - iter->zi = ziplistIndex(iter->current->entry, iter->offset); + iter->zi = lpSeek(iter->current->entry, iter->offset); } else if (unlikely(plain)) { iter->zi = NULL; } else { /* else, use existing iterator offset and get prev/next as necessary. */ if (iter->direction == AL_START_HEAD) { - nextFn = ziplistNext; + nextFn = lpNext; offset_update = 1; } else if (iter->direction == AL_START_TAIL) { - nextFn = ziplistPrev; + nextFn = lpPrev; offset_update = -1; } iter->zi = nextFn(iter->current->entry, iter->zi); @@ -1332,13 +1265,13 @@ int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { entry->sz = entry->node->sz; return 1; } - /* Populate value from existing ziplist position */ + /* Populate value from existing listpack position */ unsigned int sz = 0; - ziplistGet(entry->zi, &entry->value, &sz, &entry->longval); + entry->value = lpGetValue(entry->zi, &sz, &entry->longval); entry->sz = sz; return 1; } else { - /* We ran out of ziplist entries. + /* We ran out of listpack entries. * Pick next node, update offset, then re-run retrieval. */ quicklistCompress(iter->quicklist, iter->current); if (iter->direction == AL_START_HEAD) { @@ -1469,10 +1402,9 @@ int quicklistIndex(const quicklist *quicklist, const long long idx, return 1; } - entry->zi = ziplistIndex(entry->node->entry, entry->offset); + entry->zi = lpSeek(entry->node->entry, entry->offset); unsigned int sz = 0; - if (!ziplistGet(entry->zi, &entry->value, &sz, &entry->longval)) - assert(0); /* This can happen on corrupt ziplist with fake entry count. */ + entry->value = lpGetValue(entry->zi, &sz, &entry->longval); /* The caller will use our result, so we don't re-compress here. * The caller can recompress or delete the node as needed. */ entry->sz = sz; @@ -1501,21 +1433,21 @@ void quicklistRotate(quicklist *quicklist) { } /* First, get the tail entry */ - unsigned char *p = ziplistIndex(quicklist->tail->entry, -1); + unsigned char *p = lpSeek(quicklist->tail->entry, -1); unsigned char *value, *tmp; long long longval; unsigned int sz; char longstr[32] = {0}; - ziplistGet(p, &tmp, &sz, &longval); + tmp = lpGetValue(p, &sz, &longval); - /* If value found is NULL, then ziplistGet populated longval instead */ + /* If value found is NULL, then lpGet populated longval instead */ if (!tmp) { /* Write the longval as a string so we can re-add it */ sz = ll2string(longstr, sizeof(longstr), longval); value = (unsigned char *)longstr; } else if (quicklist->len == 1) { /* Copy buffer since there could be a memory overlap when move - * entity from tail to head in the same ziplist. */ + * entity from tail to head in the same listpack. */ value = zmalloc(sz); memcpy(value, tmp, sz); } else { @@ -1525,11 +1457,11 @@ void quicklistRotate(quicklist *quicklist) { /* Add tail entry to head (must happen before tail is deleted). */ quicklistPushHead(quicklist, value, sz); - /* If quicklist has only one node, the head ziplist is also the - * tail ziplist and PushHead() could have reallocated our single ziplist, + /* If quicklist has only one node, the head listpack is also the + * tail listpack and PushHead() could have reallocated our single listpack, * which would make our pre-existing 'p' unusable. */ if (quicklist->len == 1) { - p = ziplistIndex(quicklist->tail->entry, -1); + p = lpSeek(quicklist->tail->entry, -1); } /* Remove tail entry. */ @@ -1587,23 +1519,21 @@ int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data, return 1; } - p = ziplistIndex(node->entry, pos); - if (ziplistGet(p, &vstr, &vlen, &vlong)) { - if (vstr) { - if (data) - *data = saver(vstr, vlen); - if (sz) - *sz = vlen; - } else { - if (data) - *data = NULL; - if (sval) - *sval = vlong; - } - quicklistDelIndex(quicklist, node, &p); - return 1; + p = lpSeek(node->entry, pos); + vstr = lpGetValue(p, &vlen, &vlong); + if (vstr) { + if (data) + *data = saver(vstr, vlen); + if (sz) + *sz = vlen; + } else { + if (data) + *data = NULL; + if (sval) + *sval = vlong; } - return 0; + quicklistDelIndex(quicklist, node, &p); + return 1; } /* Return a malloc'd copy of data passed in */ @@ -1654,6 +1584,39 @@ void quicklistPush(quicklist *quicklist, void *value, const size_t sz, } } +/* Print info of quicklist which is used in debugCommand. */ +void quicklistRepr(unsigned char *ql, int full) { + int i = 0; + quicklist *quicklist = (struct quicklist*) ql; + printf("{count : %ld}\n", quicklist->count); + printf("{len : %ld}\n", quicklist->len); + printf("{fill : %d}\n", quicklist->fill); + printf("{compress : %d}\n", quicklist->compress); + printf("{bookmark_count : %d}\n", quicklist->bookmark_count); + quicklistNode* node = quicklist->head; + + while(node != NULL) { + printf("{quicklist node(%d)\n", i++); + printf("{container : %s, encoding: %s, size: %zu}\n", QL_NODE_IS_PLAIN(node) ? "PLAIN": "PACKED", + (node->encoding == QUICKLIST_NODE_ENCODING_RAW) ? "RAW": "LZF", node->sz); + + if (full) { + quicklistDecompressNode(node); + if (node->container == QUICKLIST_NODE_CONTAINER_PACKED) { + printf("{ listpack:\n"); + lpRepr(node->entry); + printf("}\n"); + + } else if (QL_NODE_IS_PLAIN(node)) { + printf("{ entry : %s }\n", node->entry); + } + printf("}\n"); + quicklistRecompressOnly(node); + } + node = node->next; + } +} + /* Create or update a bookmark in the list which will be updated to the next node * automatically when the one referenced gets deleted. * Returns 1 on success (creation of new bookmark or override of an existing one). @@ -1768,9 +1731,9 @@ static void ql_info(quicklist *ql) { printf("Container length: %lu\n", ql->len); printf("Container size: %lu\n", ql->count); if (ql->head) - printf("\t(zsize head: %d)\n", ziplistLen(ql->head->zl)); + printf("\t(zsize head: %lu)\n", lpLength(ql->head->entry)); if (ql->tail) - printf("\t(zsize tail: %d)\n", ziplistLen(ql->tail->zl)); + printf("\t(zsize tail: %lu)\n", lpLength(ql->tail->entry)); printf("\n"); #else UNUSED(ql); @@ -1868,18 +1831,18 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count, } if (ql->head && head_count != ql->head->count && - head_count != ziplistLen(ql->head->entry)) { + head_count != lpLength(ql->head->entry)) { yell("quicklist head count wrong: expected %d, " - "got cached %d vs. actual %d", - head_count, ql->head->count, ziplistLen(ql->head->entry)); + "got cached %d vs. actual %lu", + head_count, ql->head->count, lpLength(ql->head->entry)); errors++; } if (ql->tail && tail_count != ql->tail->count && - tail_count != ziplistLen(ql->tail->entry)) { + tail_count != lpLength(ql->tail->entry)) { yell("quicklist tail count wrong: expected %d, " - "got cached %u vs. actual %d", - tail_count, ql->tail->count, ziplistLen(ql->tail->entry)); + "got cached %u vs. actual %lu", + tail_count, ql->tail->count, lpLength(ql->tail->entry)); errors++; } @@ -2126,7 +2089,7 @@ int quicklistTest(int argc, char *argv[], int flags) { quicklist *ql = quicklistNew(fills[f], options[_i]); quicklistPushHead(ql, "hello", 6); quicklistRotate(ql); - /* Ignore compression verify because ziplist is + /* Ignore compression verify because listpack is * too small to compress. */ ql_verify(ql, 1, 1, 1, 1); quicklistRelease(ql); @@ -3022,32 +2985,6 @@ int quicklistTest(int argc, char *argv[], int flags) { } } - TEST_DESC("create quicklist from ziplist at compress %d", options[_i]) { - for (int f = 0; f < fill_count; f++) { - unsigned char *zl = ziplistNew(); - long long nums[64]; - char num[64]; - for (int i = 0; i < 33; i++) { - nums[i] = -5157318210846258176 + i; - int sz = ll2string(num, sizeof(num), nums[i]); - zl = - ziplistPush(zl, (unsigned char *)num, sz, ZIPLIST_TAIL); - } - for (int i = 0; i < 33; i++) { - zl = ziplistPush(zl, (unsigned char *)genstr("hello", i), - 32, ZIPLIST_TAIL); - } - quicklist *ql = quicklistCreateFromZiplist(fills[f], options[_i], zl); - if (fills[f] == 1) - ql_verify(ql, 66, 66, 1, 1); - else if (fills[f] == 32) - ql_verify(ql, 3, 66, 32, 2); - else if (fills[f] == 66) - ql_verify(ql, 1, 66, 66, 66); - quicklistRelease(ql); - } - } - long long stop = mstime(); runtime[_i] = stop - start; } @@ -3170,9 +3107,9 @@ int quicklistTest(int argc, char *argv[], int flags) { } if (flags & REDIS_TEST_LARGE_MEMORY) { - TEST("compress and decompress quicklist ziplist node") { + TEST("compress and decompress quicklist listpack node") { quicklistNode *node = quicklistCreateNode(); - node->entry = ziplistNew(); + node->entry = lpNew(0); /* Create a rand string */ size_t sz = (1 << 25); /* 32MB per one entry */ @@ -3181,7 +3118,7 @@ int quicklistTest(int argc, char *argv[], int flags) { /* Keep filling the node, until it reaches 1GB */ for (int i = 0; i < 32; i++) { - node->entry = ziplistPush(node->entry, s, sz, ZIPLIST_TAIL); + node->entry = lpAppend(node->entry, s, sz); quicklistNodeUpdateSz(node); long long start = mstime(); diff --git a/src/quicklist.h b/src/quicklist.h index 79c1c346e031..dc0d67ef3034 100644 --- a/src/quicklist.h +++ b/src/quicklist.h @@ -35,11 +35,11 @@ /* Node, quicklist, and Iterator are the only data structures used currently. */ -/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist. +/* quicklistNode is a 32 byte struct describing a listpack for a quicklist. * We use bit fields keep the quicklistNode at 32 bytes. - * count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k). + * count: 16 bits, max 65536 (max lp bytes is 65k, so max count actually < 32k). * encoding: 2 bits, RAW=1, LZF=2. - * container: 2 bits, NONE=1, ZIPLIST=2. + * container: 2 bits, PLAIN=1, PACKED=2. * recompress: 1 bit, bool, true if node is temporary decompressed for usage. * attempted_compress: 1 bit, boolean, used for verifying during testing. * extra: 10 bits, free for future use; pads out the remainder of 32 bits */ @@ -48,9 +48,9 @@ typedef struct quicklistNode { struct quicklistNode *next; unsigned char *entry; size_t sz; /* entry size in bytes */ - unsigned int count : 16; /* count of items in ziplist */ + unsigned int count : 16; /* count of items in listpack */ unsigned int encoding : 2; /* RAW==1 or LZF==2 */ - unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */ + unsigned int container : 2; /* PLAIN==1 or PACKED==2 */ unsigned int recompress : 1; /* was this node previous compressed? */ unsigned int attempted_compress : 1; /* node can't compress; too small */ unsigned int extra : 10; /* more bits to steal for future usage */ @@ -105,7 +105,7 @@ typedef struct quicklistBookmark { typedef struct quicklist { quicklistNode *head; quicklistNode *tail; - unsigned long count; /* total count of all entries in all ziplists */ + unsigned long count; /* total count of all entries in all listpacks */ unsigned long len; /* number of quicklistNodes */ int fill : QL_FILL_BITS; /* fill factor for individual nodes */ unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */ @@ -117,7 +117,7 @@ typedef struct quicklistIter { const quicklist *quicklist; quicklistNode *current; unsigned char *zi; - long offset; /* offset in current ziplist */ + long offset; /* offset in current listpack */ int direction; } quicklistIter; @@ -143,7 +143,7 @@ typedef struct quicklistEntry { /* quicklist container formats */ #define QUICKLIST_NODE_CONTAINER_PLAIN 1 -#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2 +#define QUICKLIST_NODE_CONTAINER_PACKED 2 #define QL_NODE_IS_PLAIN(node) ((node)->container == QUICKLIST_NODE_CONTAINER_PLAIN) @@ -161,12 +161,8 @@ int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz); int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz); void quicklistPush(quicklist *quicklist, void *value, const size_t sz, int where); -void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl); +void quicklistAppendListpack(quicklist *quicklist, unsigned char *zl); void quicklistAppendPlainNode(quicklist *quicklist, unsigned char *data, size_t sz); -quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist, - unsigned char *zl); -quicklist *quicklistCreateFromZiplist(int fill, int compress, - unsigned char *zl); void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *entry, void *value, const size_t sz); void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry, diff --git a/src/rdb.c b/src/rdb.c index 7b7ff59332de..f3ea053179f2 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -1593,6 +1593,45 @@ int ziplistPairsConvertAndValidateIntegrity(unsigned char *zl, size_t size, unsi return ret; } +/* callback for ziplistValidateIntegrity. + * The ziplist element pointed by 'p' will be converted and stored into listpack. */ +static int _ziplistEntryConvertAndValidate(unsigned char *p, unsigned int head_count, void *userdata) { + UNUSED(head_count); + unsigned char *str; + unsigned int slen; + long long vll; + unsigned char **lp = (unsigned char**)userdata; + + if (!ziplistGet(p, &str, &slen, &vll)) return 0; + + if (str) + *lp = lpAppend(*lp, (unsigned char*)str, slen); + else + *lp = lpAppendInteger(*lp, vll); + + return 1; +} + +/* callback for ziplistValidateIntegrity. + * The ziplist element pointed by 'p' will be converted and stored into quicklist. */ +static int _listZiplistEntryConvertAndValidate(unsigned char *p, unsigned int head_count, void *userdata) { + UNUSED(head_count); + unsigned char *str; + unsigned int slen; + long long vll; + char longstr[32] = {0}; + quicklist *ql = (quicklist*)userdata; + + if (!ziplistGet(p, &str, &slen, &vll)) return 0; + if (!str) { + /* Write the longval as a string so we can re-add it */ + slen = ll2string(longstr, sizeof(longstr), vll); + str = (unsigned char *)longstr; + } + quicklistPushTail(ql, str, slen); + return 1; +} + /* callback for to check the listpack doesn't have duplicate records */ static int _lpPairsEntryValidation(unsigned char *p, unsigned int head_count, void *userdata) { struct { @@ -1680,7 +1719,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { if (len == 0) goto emptykey; o = createQuicklistObject(); - quicklistSetOptions(o->ptr, server.list_max_ziplist_size, + quicklistSetOptions(o->ptr, server.list_max_listpack_size, server.list_compress_depth); /* Load every single element of the list */ @@ -1950,10 +1989,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { if (len == 0) goto emptykey; o = createQuicklistObject(); - quicklistSetOptions(o->ptr, server.list_max_ziplist_size, + quicklistSetOptions(o->ptr, server.list_max_listpack_size, server.list_compress_depth); - uint64_t container = QUICKLIST_NODE_CONTAINER_ZIPLIST; + uint64_t container = QUICKLIST_NODE_CONTAINER_PACKED; while (len--) { + unsigned char *lp; size_t encoded_len; if (rdbtype == RDB_TYPE_LIST_QUICKLIST_2) { @@ -1962,7 +2002,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { return NULL; } - if (container != QUICKLIST_NODE_CONTAINER_ZIPLIST && container != QUICKLIST_NODE_CONTAINER_PLAIN) { + if (container != QUICKLIST_NODE_CONTAINER_PACKED && container != QUICKLIST_NODE_CONTAINER_PLAIN) { rdbReportCorruptRDB("Quicklist integrity check failed."); decrRefCount(o); return NULL; @@ -1979,24 +2019,39 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { if (container == QUICKLIST_NODE_CONTAINER_PLAIN) { quicklistAppendPlainNode(o->ptr, data, encoded_len); - zfree(data); continue; } - if (deep_integrity_validation) server.stat_dump_payload_sanitizations++; - if (!ziplistValidateIntegrity(data, encoded_len, deep_integrity_validation, NULL, NULL)) { - rdbReportCorruptRDB("Ziplist integrity check failed."); - decrRefCount(o); + if (rdbtype == RDB_TYPE_LIST_QUICKLIST_2) { + lp = data; + if (deep_integrity_validation) server.stat_dump_payload_sanitizations++; + if (!lpValidateIntegrity(lp, encoded_len, deep_integrity_validation, NULL, NULL)) { + rdbReportCorruptRDB("Listpack integrity check failed."); + decrRefCount(o); + zfree(lp); + return NULL; + } + } else { + lp = lpNew(encoded_len); + if (!ziplistValidateIntegrity(data, encoded_len, 1, + _ziplistEntryConvertAndValidate, &lp)) + { + rdbReportCorruptRDB("Ziplist integrity check failed."); + decrRefCount(o); + zfree(data); + zfree(lp); + return NULL; + } zfree(data); - return NULL; + lp = lpShrinkToFit(lp); } /* Silently skip empty ziplists, if we'll end up with empty quicklist we'll fail later. */ - if (ziplistLen(data) == 0) { - zfree(data); + if (lpLength(lp) == 0) { + zfree(lp); continue; } else { - quicklistAppendZiplist(o->ptr, data); + quicklistAppendListpack(o->ptr, lp); } } @@ -2080,27 +2135,36 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { } } break; - case RDB_TYPE_LIST_ZIPLIST: - if (deep_integrity_validation) server.stat_dump_payload_sanitizations++; - if (!ziplistValidateIntegrity(encoded, encoded_len, deep_integrity_validation, NULL, NULL)) { - rdbReportCorruptRDB("List ziplist integrity check failed."); - zfree(encoded); - o->ptr = NULL; - decrRefCount(o); - return NULL; - } + case RDB_TYPE_LIST_ZIPLIST: + { + quicklist *ql = quicklistNew(server.list_max_listpack_size, + server.list_compress_depth); + + if (!ziplistValidateIntegrity(encoded, encoded_len, 1, + _listZiplistEntryConvertAndValidate, ql)) + { + rdbReportCorruptRDB("List ziplist integrity check failed."); + zfree(encoded); + o->ptr = NULL; + decrRefCount(o); + quicklistRelease(ql); + return NULL; + } + + if (ql->len == 0) { + zfree(encoded); + o->ptr = NULL; + decrRefCount(o); + quicklistRelease(ql); + goto emptykey; + } - if (ziplistLen(encoded) == 0) { zfree(encoded); - o->ptr = NULL; - decrRefCount(o); - goto emptykey; + o->type = OBJ_LIST; + o->ptr = ql; + o->encoding = OBJ_ENCODING_QUICKLIST; + break; } - - o->type = OBJ_LIST; - o->encoding = OBJ_ENCODING_ZIPLIST; - listTypeConvert(o,OBJ_ENCODING_QUICKLIST); - break; case RDB_TYPE_SET_INTSET: if (deep_integrity_validation) server.stat_dump_payload_sanitizations++; if (!intsetValidateIntegrity(encoded, encoded_len, deep_integrity_validation)) { @@ -2138,6 +2202,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { if (zsetLength(o) > server.zset_max_listpack_entries) zsetConvert(o,OBJ_ENCODING_SKIPLIST); + else + o->ptr = lpShrinkToFit(o->ptr); break; } case RDB_TYPE_ZSET_LISTPACK: @@ -2180,9 +2246,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { goto emptykey; } - if (hashTypeLength(o) > server.hash_max_listpack_entries) { + if (hashTypeLength(o) > server.hash_max_listpack_entries) hashTypeConvert(o, OBJ_ENCODING_HT); - } + else + o->ptr = lpShrinkToFit(o->ptr); break; } case RDB_TYPE_HASH_LISTPACK: diff --git a/src/server.c b/src/server.c index 1e293dedffd2..a69d54e7d726 100644 --- a/src/server.c +++ b/src/server.c @@ -2392,7 +2392,7 @@ dictType commandTableDictType = { NULL /* allow to expand */ }; -/* Hash type hash table (note that small hashes are represented with ziplists) */ +/* Hash type hash table (note that small hashes are represented with listpacks) */ dictType hashDictType = { dictSdsHash, /* hash function */ NULL, /* key dup */ diff --git a/src/server.h b/src/server.h index 21bfc501fe44..65bd62fa1cbd 100644 --- a/src/server.h +++ b/src/server.h @@ -730,7 +730,7 @@ typedef struct RedisModuleDigest { #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ -#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ +#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */ #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */ #define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */ @@ -1679,7 +1679,7 @@ struct redisServer { size_t stream_node_max_bytes; long long stream_node_max_entries; /* List parameters */ - int list_max_ziplist_size; + int list_max_listpack_size; int list_compress_depth; /* time cache */ redisAtomic time_t unixtime; /* Unix time sampled every cron cycle. */ @@ -2209,7 +2209,6 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where); void listTypeReplace(listTypeEntry *entry, robj *value); int listTypeEqual(listTypeEntry *entry, robj *o); void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry); -void listTypeConvert(robj *subject, int enc); robj *listTypeDup(robj *o); int listTypeDelRange(robj *o, long start, long stop); void unblockClientWaitingData(client *c); @@ -2261,7 +2260,6 @@ robj *createStringObjectFromLongLong(long long value); robj *createStringObjectFromLongLongForValue(long long value); robj *createStringObjectFromLongDouble(long double value, int humanfriendly); robj *createQuicklistObject(void); -robj *createZiplistObject(void); robj *createSetObject(void); robj *createIntsetObject(void); robj *createHashObject(void); diff --git a/src/t_list.c b/src/t_list.c index 6024c60cfbe2..7a1733bfcbd6 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -199,21 +199,6 @@ void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) { } } -/* Create a quicklist from a single ziplist */ -void listTypeConvert(robj *subject, int enc) { - serverAssertWithInfo(NULL,subject,subject->type==OBJ_LIST); - serverAssertWithInfo(NULL,subject,subject->encoding==OBJ_ENCODING_ZIPLIST); - - if (enc == OBJ_ENCODING_QUICKLIST) { - size_t zlen = server.list_max_ziplist_size; - int depth = server.list_compress_depth; - subject->ptr = quicklistCreateFromZiplist(zlen, depth, subject->ptr); - subject->encoding = enc; - } else { - serverPanic("Unsupported list conversion"); - } -} - /* This is a helper function for the COPY command. * Duplicate a list object, with the guarantee that the returned object * has the same encoding as the original one. @@ -263,7 +248,7 @@ void pushGenericCommand(client *c, int where, int xx) { } lobj = createQuicklistObject(); - quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size, + quicklistSetOptions(lobj->ptr, server.list_max_listpack_size, server.list_compress_depth); dbAdd(c->db,c->argv[1],lobj); } @@ -823,7 +808,7 @@ void lmoveHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value, /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createQuicklistObject(); - quicklistSetOptions(dstobj->ptr, server.list_max_ziplist_size, + quicklistSetOptions(dstobj->ptr, server.list_max_listpack_size, server.list_compress_depth); dbAdd(c->db,dstkey,dstobj); } diff --git a/src/t_zset.c b/src/t_zset.c index 4516c19c0f9f..483951535348 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -1377,7 +1377,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, dou } } - /* Note that the above block handling ziplist would have either returned or + /* Note that the above block handling listpack would have either returned or * converted the key to skiplist. */ if (zobj->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = zobj->ptr; diff --git a/src/util.c b/src/util.c index 8477998874bc..9cdc30632167 100644 --- a/src/util.c +++ b/src/util.c @@ -552,7 +552,7 @@ int string2d(const char *s, size_t slen, double *dp) { * required. The representation should always be parsable by strtod(3). * This function does not support human-friendly formatting like ld2string * does. It is intended mainly to be used inside t_zset.c when writing scores - * into a ziplist representing a sorted set. */ + * into a listpack representing a sorted set. */ int d2string(char *buf, size_t len, double value) { if (isnan(value)) { len = snprintf(buf,len,"nan"); diff --git a/tests/integration/corrupt-dump.tcl b/tests/integration/corrupt-dump.tcl index d647fcad4811..cd08589b0793 100644 --- a/tests/integration/corrupt-dump.tcl +++ b/tests/integration/corrupt-dump.tcl @@ -29,26 +29,6 @@ test {corrupt payload: #7445 - with sanitize} { } } -test {corrupt payload: #7445 - without sanitize - 1} { - start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { - r config set sanitize-dump-payload no - r restore key 0 $corrupt_payload_7445 - catch {r lindex key 2} - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 - } -} - -test {corrupt payload: #7445 - without sanitize - 2} { - start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { - r config set sanitize-dump-payload no - r restore key 0 $corrupt_payload_7445 - catch {r lset key 2 "BEEF"} - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 - } -} - test {corrupt payload: hash with valid zip list header, invalid entry len} { start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { catch { @@ -82,10 +62,9 @@ test {corrupt payload: valid zipped hash header, dup records} { test {corrupt payload: quicklist big ziplist prev len} { start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { r config set sanitize-dump-payload no - r restore key 0 "\x0e\x01\x1b\x1b\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x02\x61\x00\x04\x02\x62\x00\x04\x02\x63\x00\x19\x02\x64\x00\xff\x09\x00\xec\x42\xe9\xf5\xd6\x19\x9e\xbd" - catch {r lindex key -2} - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 + catch {r restore key 0 "\x0E\x01\x13\x13\x00\x00\x00\x0E\x00\x00\x00\x02\x00\x00\x02\x61\x00\x0E\x02\x62\x00\xFF\x09\x00\x49\x97\x30\xB2\x0D\xA1\xED\xAA"} err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 } } @@ -103,13 +82,9 @@ test {corrupt payload: quicklist small ziplist prev len} { test {corrupt payload: quicklist ziplist wrong count} { start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { r config set sanitize-dump-payload no - r restore key 0 "\x0E\x01\x13\x13\x00\x00\x00\x0E\x00\x00\x00\x03\x00\x00\x02\x61\x00\x04\x02\x62\x00\xFF\x09\x00\x4D\xE2\x0A\x2F\x08\x25\xDF\x91" - # we'll be able to push, but iterating on the list will assert - r lpush key header - r rpush key footer - catch { [r lrange key 0 -1] } - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 + catch {r restore key 0 "\x0E\x01\x13\x13\x00\x00\x00\x0E\x00\x00\x00\x03\x00\x00\x02\x61\x00\x04\x02\x62\x00\xFF\x09\x00\x4D\xE2\x0A\x2F\x08\x25\xDF\x91"} err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 } } @@ -335,10 +310,9 @@ test {corrupt payload: fuzzer findings - NPD in quicklistIndex} { r debug set-skip-checksum-validation 1 catch { r RESTORE key 0 "\x0E\x01\x13\x13\x00\x00\x00\x10\x00\x00\x00\x03\x12\x00\xF3\x02\x02\x5F\x31\x04\xF1\xFF\x09\x00\xC9\x4B\x31\xFE\x61\xC0\x96\xFE" - r LSET key 290 290 - } - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 + } err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 } } @@ -429,12 +403,9 @@ test {corrupt payload: fuzzer findings - valgrind ziplist prevlen reaches outsid start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { r config set sanitize-dump-payload no r debug set-skip-checksum-validation 1 - r RESTORE _listbig 0 "\x0E\x02\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\x02\x5F\x39\x04\xF9\x02\x02\x5F\x37\x04\xF7\x02\x02\x5F\x35\xFF\x19\x19\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\xF5\x02\x02\x5F\x33\x04\xF3\x95\x02\x5F\x31\x04\xF1\xFF\x09\x00\x0C\xFC\x99\x2C\x23\x45\x15\x60" - catch { r RPOP _listbig } - catch { r RPOP _listbig } - catch { r RPUSH _listbig 949682325 } - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 + catch {r RESTORE _listbig 0 "\x0E\x02\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\x02\x5F\x39\x04\xF9\x02\x02\x5F\x37\x04\xF7\x02\x02\x5F\x35\xFF\x19\x19\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\xF5\x02\x02\x5F\x33\x04\xF3\x95\x02\x5F\x31\x04\xF1\xFF\x09\x00\x0C\xFC\x99\x2C\x23\x45\x15\x60"} err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 } } @@ -451,11 +422,9 @@ test {corrupt payload: fuzzer findings - valgrind ziplist prev too big} { start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { r config set sanitize-dump-payload no r debug set-skip-checksum-validation 1 - r RESTORE _list 0 "\x0E\x01\x13\x13\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\xF3\x02\x02\x5F\x31\xC1\xF1\xFF\x09\x00\xC9\x4B\x31\xFE\x61\xC0\x96\xFE" - catch { r RPUSHX _list -45 } - catch { r LREM _list -748 -840} - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 + catch {r RESTORE _list 0 "\x0E\x01\x13\x13\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\xF3\x02\x02\x5F\x31\xC1\xF1\xFF\x09\x00\xC9\x4B\x31\xFE\x61\xC0\x96\xFE"} err + assert_match "*Bad data format*" $err + verify_log_message 0 "*integrity check failed*" 0 } } @@ -778,10 +747,9 @@ test {corrupt payload: fuzzer findings - invalid access in ziplist tail prevlen start_server [list overrides [list loglevel verbose use-exit-on-panic yes crash-memcheck-enabled no] ] { r debug set-skip-checksum-validation 1 r config set sanitize-dump-payload no - r restore _listbig 0 "\x12\x02\x02\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\x02\x5F\x39\x04\xF9\x02\x02\x5F\x37\x04\xF7\x02\x02\x5F\x35\xFF\x02\x19\x19\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\xF5\x02\x02\x5F\x33\x04\xF3\x02\x02\x5F\x31\xFE\xF1\xFF\x0A\x00\x64\x0C\xEB\x03\xDF\x36\x61\xCE" - catch { r RPOPLPUSH _listbig _listbig } - assert_equal [count_log_message 0 "crashed by signal"] 0 - assert_equal [count_log_message 0 "ASSERTION FAILED"] 1 + catch {r restore _listbig 0 "\x0e\x02\x1B\x1B\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\x02\x5F\x39\x04\xF9\x02\x02\x5F\x37\x04\xF7\x02\x02\x5F\x35\xFF\x19\x19\x00\x00\x00\x16\x00\x00\x00\x05\x00\x00\xF5\x02\x02\x5F\x33\x04\xF3\x02\x02\x5F\x31\xFE\xF1\xFF\x0A\x00\x6B\x43\x32\x2F\xBB\x29\x0a\xBE"} err + assert_match "*Bad data format*" $err + r ping } }