Replace ziplist with listpack in quicklist (#9740)

Part three of implementing #8702, following #8887 and #9366 .

## Description of the feature
1. Replace the ziplist container of quicklist with listpack.
2. Convert existing quicklist ziplists on RDB loading time. an O(n) operation.

## Interface changes
1. New `list-max-listpack-size` config is an alias for `list-max-ziplist-size`.
2. Replace `debug ziplist` command with `debug listpack`.

## Internal changes
1. Add `lpMerge` to merge two listpacks . (same as `ziplistMerge`)
2. Add `lpRepr` to print info of listpack which is used in debugCommand and `quicklistRepr`. (same as `ziplistRepr`)
3. Replace `QUICKLIST_NODE_CONTAINER_ZIPLIST` with `QUICKLIST_NODE_CONTAINER_PACKED`(following #9357 ).
    It represent that a quicklistNode is a packed node, as opposed to a plain node.
4. Remove `createZiplistObject` method, which is never used.
5. Calculate listpack entry size using overhead overestimation in `quicklistAllowInsert`.
    We prefer an overestimation, which would at worse lead to a few bytes below the lowest limit of 4k.

## Improvements
1. Calling `lpShrinkToFit` after converting Ziplist to listpack, which was missed at #9366.
2. Optimize `quicklistAppendPlainNode` to avoid memcpy data.

## Bugfix
1. Fix crash in `quicklistRepr` when ziplist is compressed, introduced from #9366.

## Test
1. Add unittest for `lpMerge`.
2. Modify the old quicklist ziplist corrupt dump test.

Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
sundb 2021-11-24 19:34:13 +08:00 committed by GitHub
parent fb4f7be22c
commit 4512905961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 509 additions and 363 deletions

View File

@ -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

View File

@ -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) */

View File

@ -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

View File

@ -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 <key>",
" Show low level info about the ziplist encoding of <key>.",
"LISTPACK <key>",
" Show low level info about the listpack encoding of <key>.",
"QUICKLIST <key> [<0|1>]",
" Show low level info about the quicklist encoding of <key>."
" 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;

View File

@ -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 <sys/time.h>
@ -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);

View File

@ -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);

View File

@ -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:

View File

@ -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);

View File

@ -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 <matt@genges.com>
* 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);
}
quicklistNode *node = quicklistCreateNode();
/* 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};
node->entry = data;
node->count = 1;
node->sz = sz;
node->container = QUICKLIST_NODE_CONTAINER_PLAIN;
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;
}
/* 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();

View File

@ -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,

135
src/rdb.c
View File

@ -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 (ziplistLen(encoded) == 0) {
zfree(encoded);
o->ptr = NULL;
decrRefCount(o);
goto emptykey;
}
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;
}
o->type = OBJ_LIST;
o->encoding = OBJ_ENCODING_ZIPLIST;
listTypeConvert(o,OBJ_ENCODING_QUICKLIST);
break;
if (ql->len == 0) {
zfree(encoded);
o->ptr = NULL;
decrRefCount(o);
quicklistRelease(ql);
goto emptykey;
}
zfree(encoded);
o->type = OBJ_LIST;
o->ptr = ql;
o->encoding = 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:

View File

@ -2378,7 +2378,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 */

View File

@ -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. */
@ -2208,7 +2208,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);
@ -2260,7 +2259,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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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");

View File

@ -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
}
}