From fa2a707e9196aea8bd69ae733a14cc6403e53162 Mon Sep 17 00:00:00 2001 From: imchuncai Date: Tue, 12 Dec 2023 16:26:17 +0800 Subject: [PATCH] Rewrite quicklist Why: I tried to solve the issue I found earlier, but found myself stuck in a quagmire because the issues kept coming up while I fix the old one, so I finally decided to rewrite it. Issues with the old one: - A node which should be compressed stays raw This is due to by poor design of quicklist->recompress, the design forgot the situation that a node could stay uncompressed if it can not compress small enough. And if we changed the node, wo should perform compress on it again. See issue #12563. - Iterator don't behave like iterator Iterator will be reset and not avaliable for further use after replace or insert, see marcro resetIterator(). The only operation that athe iterator does not get reset is quicklistDelEntry(), and it has a commment about it, but the comment is wrong, the iterator may not behave like the comment says. See issue #12614. - Packed node violate size limit Certen call to function quicklistReplaceEntry(), quicklistInsertBefore() and quicklistInsertAfter() will cause a packed node violate size limit. See issue #12548. - Merge operation only performed in insert There is no merging in delete nor replace, which can make the quicklist contain adjacent small nodes. See issue #12856. - Algorithms that maintain compress depth are not efficient the algorithm to maintain compress depth after add or delete is to check nodes on both sides of the list, time complexity is O(n), where n is the uncompressed depth on both sides of the list. All the changes: - Partition the node Divide the node into three partitions: head, middle and tail. The head and tail partitions hold uncompressed nodes, and the middle partition holds compressed nodes. Therefore,the time complexity of maintaining compress depth after adding or deleting a node will drop to O(1), moving at most one node from one partition to another. - Removed annoying members recompress, attempted_compress and dont_compress from quicklist node structure - Merge structure quicklistIter and quicklistEntry - The historical parameter packed_threshold has been removed This is mentioned by @sundb and @oranagra in pull request #12568. - Merge strategy is added That is that any adjacent node in quicklist can not be merged. --- src/aof.c | 5 +- src/debug.c | 41 +- src/defrag.c | 51 +- src/lazyfree.c | 4 +- src/module.c | 29 +- src/object.c | 37 +- src/quicklist.c | 5607 ++++++++++++++++++++++---------------- src/quicklist.h | 369 +-- src/rdb.c | 45 +- src/server.h | 25 +- src/sort.c | 10 +- src/t_list.c | 242 +- tests/unit/type/list.tcl | 4723 ++++++++++++++++---------------- 13 files changed, 6069 insertions(+), 5119 deletions(-) mode change 100644 => 100755 tests/unit/type/list.tcl diff --git a/src/aof.c b/src/aof.c index cb9d899bbce3d..4a727d82845a1 100644 --- a/src/aof.c +++ b/src/aof.c @@ -1802,8 +1802,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) { long long count = 0, items = listTypeLength(o); listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL); - listTypeEntry entry; - while (listTypeNext(li,&entry)) { + while (listTypeNext(li)) { if (count == 0) { int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? AOF_REWRITE_ITEMS_PER_CMD : items; @@ -1819,7 +1818,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) { unsigned char *vstr; size_t vlen; long long lval; - vstr = listTypeGetValue(&entry,&vlen,&lval); + vstr = listTypeGetValue(li,&vlen,&lval); if (vstr) { if (!rioWriteBulkString(r,(char*)vstr,vlen)) { listTypeReleaseIterator(li); diff --git a/src/debug.c b/src/debug.c index b924d9ed3f0e6..3f3a93e717f86 100644 --- a/src/debug.c +++ b/src/debug.c @@ -154,9 +154,8 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) mixStringObjectDigest(digest,o); } else if (o->type == OBJ_LIST) { listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL); - listTypeEntry entry; - while(listTypeNext(li,&entry)) { - robj *eleobj = listTypeGet(&entry); + while(listTypeNext(li)) { + robj *eleobj = listTypeGet(li); mixStringObjectDigest(digest,eleobj); decrRefCount(eleobj); } @@ -470,9 +469,6 @@ void debugCommand(client *c) { " Setting it to 0 disables expiring keys in background when they are not", " accessed (otherwise the Redis behavior). Setting it to 1 reenables back the", " default.", -"QUICKLIST-PACKED-THRESHOLD ", -" Sets the threshold for elements to be inserted as plain vs packed nodes", -" Default value is 1GB, allows values up to 4GB. Setting to 0 restores to default.", "SET-SKIP-CHECKSUM-VALIDATION <0|1>", " Enables or disables checksum checks for RDB files and RESTORE's payload.", "SLEEP ", @@ -617,29 +613,36 @@ NULL if (val->encoding == OBJ_ENCODING_QUICKLIST) { char *nextra = extra; int remaining = sizeof(extra); - quicklist *ql = val->ptr; + struct quicklist *ql = val->ptr; /* Add number of quicklist nodes */ - int used = snprintf(nextra, remaining, " ql_nodes:%lu", ql->len); + int used = snprintf(nextra, remaining, " ql_nodes:%lu", quicklist_node_count(ql)); nextra += used; remaining -= used; /* Add average quicklist fill factor */ - double avg = (double)ql->count/ql->len; + double avg = (double)ql->count/quicklist_node_count(ql); used = snprintf(nextra, remaining, " ql_avg_node:%.2f", avg); nextra += used; remaining -= used; /* Add quicklist fill level / max listpack size */ - used = snprintf(nextra, remaining, " ql_listpack_max:%d", ql->fill); + used = snprintf(nextra, remaining, " ql_pack_max_count:%d", ql->fill->pack_max_count); + nextra += used; + remaining -= used; + used = snprintf(nextra, remaining, " ql_pack_max_size:%d", ql->fill->pack_max_size); nextra += used; remaining -= used; /* Add isCompressed? */ - int compressed = ql->compress != 0; + int compressed = ql->head->next->capacity != 0; used = snprintf(nextra, remaining, " ql_compressed:%d", compressed); nextra += used; remaining -= used; /* Add total uncompressed size */ unsigned long sz = 0; - for (quicklistNode *node = ql->head; node; node = node->next) { - sz += node->sz; + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(ql, &p, &node); + while (node) { + sz += node->raw_sz; + quicklist_next(p, node, &p, &node); } used = snprintf(nextra, remaining, " ql_uncompressed_size:%lu", sz); nextra += used; @@ -702,7 +705,7 @@ NULL if (o->encoding != OBJ_ENCODING_QUICKLIST) { addReplyError(c,"Not a quicklist encoded object."); } else { - quicklistRepr(o->ptr, full); + quicklist_debug_print((struct quicklist *)o->ptr, full); addReplyStatus(c,"Quicklist structure printed on stdout"); } } else if (!strcasecmp(c->argv[1]->ptr,"populate") && @@ -850,16 +853,6 @@ NULL { server.active_expire_enabled = atoi(c->argv[2]->ptr); addReply(c,shared.ok); - } else if (!strcasecmp(c->argv[1]->ptr,"quicklist-packed-threshold") && - c->argc == 3) - { - int memerr; - unsigned long long sz = memtoull((const char *)c->argv[2]->ptr, &memerr); - if (memerr || !quicklistisSetPackedThreshold(sz)) { - addReplyError(c, "argument must be a memory value bigger than 1 and smaller than 4gb"); - } else { - addReply(c,shared.ok); - } } else if (!strcasecmp(c->argv[1]->ptr,"set-skip-checksum-validation") && c->argc == 3) { diff --git a/src/defrag.c b/src/defrag.c index 13091333902dc..df9ffee555155 100644 --- a/src/defrag.c +++ b/src/defrag.c @@ -296,29 +296,25 @@ void activeDefragList(list *l, int val_type) { } } -void activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) { - quicklistNode *newnode, *node = *node_ref; +void activeDefragQuickListNode(struct quicklist_node **node_ref) { + struct quicklist_node *newnode, *node = *node_ref; unsigned char *newzl; if ((newnode = activeDefragAlloc(node))) { - if (newnode->prev) - newnode->prev->next = newnode; - else - ql->head = newnode; - if (newnode->next) - newnode->next->prev = newnode; - else - ql->tail = newnode; + newnode->prev->next = newnode; + newnode->next->prev = newnode; *node_ref = node = newnode; } - if ((newzl = activeDefragAlloc(node->entry))) - node->entry = newzl; + if ((newzl = activeDefragAlloc(node->carry))) + node->carry = newzl; } -void activeDefragQuickListNodes(quicklist *ql) { - quicklistNode *node = ql->head; +void activeDefragQuickListNodes(struct quicklist *ql) { + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(ql, &p, &node); while (node) { - activeDefragQuickListNode(ql, &node); - node = node->next; + activeDefragQuickListNode(&node); + quicklist_next(p, node, &p, &node); } } @@ -332,8 +328,9 @@ void defragLater(redisDb *db, dictEntry *kde) { /* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */ long scanLaterList(robj *ob, unsigned long *cursor, long long endtime) { - quicklist *ql = ob->ptr; - quicklistNode *node; + struct quicklist *ql = ob->ptr; + struct quicklist_partition *p; + struct quicklist_node *node; long iterations = 0; int bookmark_failed = 0; if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST) @@ -341,24 +338,24 @@ long scanLaterList(robj *ob, unsigned long *cursor, long long endtime) { if (*cursor == 0) { /* if cursor is 0, we start new iteration */ - node = ql->head; + quicklist_first_node(ql, &p, &node); } else { - node = quicklistBookmarkFind(ql, "_AD"); + node = quicklist_bm_find(ql, "_AD"); if (!node) { /* if the bookmark was deleted, it means we reached the end. */ *cursor = 0; return 0; } - node = node->next; + node = quicklist_next_for_bookmark(ql, node); } (*cursor)++; while (node) { - activeDefragQuickListNode(ql, &node); + activeDefragQuickListNode(&node); server.stat_active_defrag_scanned++; if (++iterations > 128 && !bookmark_failed) { if (ustime() > endtime) { - if (!quicklistBookmarkCreate(&ql, "_AD", node)) { + if (!quicklist_bm_create(&ql, "_AD", node)) { bookmark_failed = 1; } else { ob->ptr = ql; /* bookmark creation may have re-allocated the quicklist */ @@ -367,9 +364,9 @@ long scanLaterList(robj *ob, unsigned long *cursor, long long endtime) { } iterations = 0; } - node = node->next; + node = quicklist_next_for_bookmark(ql, node); } - quicklistBookmarkDelete(ql, "_AD"); + quicklist_bm_delete(ql, "_AD"); *cursor = 0; return bookmark_failed? 1: 0; } @@ -427,11 +424,11 @@ void scanLaterHash(robj *ob, unsigned long *cursor) { void defragQuicklist(redisDb *db, dictEntry *kde) { robj *ob = dictGetVal(kde); - quicklist *ql = ob->ptr, *newql; + struct quicklist *ql = ob->ptr, *newql; serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST); if ((newql = activeDefragAlloc(ql))) ob->ptr = ql = newql; - if (ql->len > server.active_defrag_max_scan_fields) + if ((unsigned long)quicklist_node_count(ql) > server.active_defrag_max_scan_fields) defragLater(db, kde); else activeDefragQuickListNodes(ql); diff --git a/src/lazyfree.c b/src/lazyfree.c index 2a6d1b7e16d5b..9a4253ebfcd8e 100644 --- a/src/lazyfree.c +++ b/src/lazyfree.c @@ -108,8 +108,8 @@ void lazyfreeResetStats(void) { * representing the list. */ size_t lazyfreeGetFreeEffort(robj *key, robj *obj, int dbid) { if (obj->type == OBJ_LIST && obj->encoding == OBJ_ENCODING_QUICKLIST) { - quicklist *ql = obj->ptr; - return ql->len; + struct quicklist *ql = obj->ptr; + return quicklist_node_count(ql); } else if (obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) { dict *ht = obj->ptr; return dictSize(ht); diff --git a/src/module.c b/src/module.c index 96bc61e0f298e..217f774a1e11b 100644 --- a/src/module.c +++ b/src/module.c @@ -197,7 +197,6 @@ struct RedisModuleKey { union { struct { /* List, use only if value->type == OBJ_LIST */ - listTypeEntry entry; /* Current entry in iteration. */ long index; /* Current 0-based index in iteration. */ } list; struct { @@ -4438,7 +4437,7 @@ int moduleListIteratorSeek(RedisModuleKey *key, long index, int mode) { /* No existing iterator. Create one. */ key->iter = listTypeInitIterator(key->value, index, LIST_TAIL); serverAssert(key->iter != NULL); - serverAssert(listTypeNext(key->iter, &key->u.list.entry)); + serverAssert(listTypeNext(key->iter)); key->u.list.index = index; return 1; } @@ -4452,9 +4451,17 @@ int moduleListIteratorSeek(RedisModuleKey *key, long index, int mode) { /* Seek the iterator to the requested index. */ unsigned char dir = key->u.list.index < index ? LIST_TAIL : LIST_HEAD; - listTypeSetIteratorDirection(key->iter, &key->u.list.entry, dir); + listTypeIterator *li = key->iter; + if (dir != li->direction) { + listTypeReleaseIterator(li); + key->iter = listTypeInitIterator(key->value, index, dir); + serverAssert(key->iter != NULL); + serverAssert(listTypeNext(key->iter)); + key->u.list.index = index; + return 1; + } while (key->u.list.index != index) { - serverAssert(listTypeNext(key->iter, &key->u.list.entry)); + serverAssert(listTypeNext(key->iter)); key->u.list.index += dir == LIST_HEAD ? -1 : 1; } return 1; @@ -4546,7 +4553,7 @@ RedisModuleString *RM_ListPop(RedisModuleKey *key, int where) { */ RedisModuleString *RM_ListGet(RedisModuleKey *key, long index) { if (moduleListIteratorSeek(key, index, REDISMODULE_READ)) { - robj *elem = listTypeGet(&key->u.list.entry); + robj *elem = listTypeGet(key->iter); robj *decoded = getDecodedObject(elem); decrRefCount(elem); autoMemoryAdd(key->ctx, REDISMODULE_AM_STRING, decoded); @@ -4582,7 +4589,7 @@ int RM_ListSet(RedisModuleKey *key, long index, RedisModuleString *value) { } listTypeTryConversionAppend(key->value, &value, 0, 0, moduleFreeListIterator, key); if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) { - listTypeReplace(&key->u.list.entry, value); + listTypeReplace(key->iter, value); /* A note in quicklist.c forbids use of iterator after insert, so * probably also after replace. */ moduleFreeKeyIterator(key); @@ -4629,7 +4636,7 @@ int RM_ListInsert(RedisModuleKey *key, long index, RedisModuleString *value) { listTypeTryConversionAppend(key->value, &value, 0, 0, moduleFreeListIterator, key); if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) { int where = index < 0 ? LIST_TAIL : LIST_HEAD; - listTypeInsert(&key->u.list.entry, value, where); + listTypeInsert(key->iter, value, where); /* A note in quicklist.c forbids use of iterator after insert. */ moduleFreeKeyIterator(key); return REDISMODULE_OK; @@ -4651,19 +4658,19 @@ int RM_ListInsert(RedisModuleKey *key, long index, RedisModuleString *value) { */ int RM_ListDelete(RedisModuleKey *key, long index) { if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) { - listTypeDelete(key->iter, &key->u.list.entry); + listTypeDelete(key->iter); if (moduleDelKeyIfEmpty(key)) return REDISMODULE_OK; listTypeTryConversion(key->value, LIST_CONV_SHRINKING, moduleFreeListIterator, key); if (!key->iter) return REDISMODULE_OK; /* Return ASAP if iterator has been freed */ - if (listTypeNext(key->iter, &key->u.list.entry)) { + listTypeIterator *iter = key->iter; + if (listTypeNext(iter)) { /* After delete entry at position 'index', we need to update * 'key->u.list.index' according to the following cases: * 1) [1, 2, 3] => dir: forward, index: 0 => [2, 3] => index: still 0 * 2) [1, 2, 3] => dir: forward, index: -3 => [2, 3] => index: -2 * 3) [1, 2, 3] => dir: reverse, index: 2 => [1, 2] => index: 1 * 4) [1, 2, 3] => dir: reverse, index: -1 => [1, 2] => index: still -1 */ - listTypeIterator *li = key->iter; - int reverse = li->direction == LIST_HEAD; + int reverse = iter->direction == LIST_HEAD; if (key->u.list.index < 0) key->u.list.index += reverse ? 0 : 1; else diff --git a/src/object.c b/src/object.c index bf85c7cc1335d..fd090f2efdc35 100644 --- a/src/object.c +++ b/src/object.c @@ -233,7 +233,7 @@ robj *dupStringObject(const robj *o) { } robj *createQuicklistObject(void) { - quicklist *l = quicklistCreate(); + struct quicklist *l = quicklist_new(server.list_max_listpack_size, server.list_compress_depth); robj *o = createObject(OBJ_LIST,l); o->encoding = OBJ_ENCODING_QUICKLIST; return o; @@ -314,7 +314,7 @@ void freeStringObject(robj *o) { void freeListObject(robj *o) { if (o->encoding == OBJ_ENCODING_QUICKLIST) { - quicklistRelease(o->ptr); + quicklist_free(o->ptr); } else if (o->encoding == OBJ_ENCODING_LISTPACK) { lpFree(o->ptr); } else { @@ -423,19 +423,21 @@ void dismissStringObject(robj *o) { /* See dismissObject() */ void dismissListObject(robj *o, size_t size_hint) { if (o->encoding == OBJ_ENCODING_QUICKLIST) { - quicklist *ql = o->ptr; - serverAssert(ql->len != 0); + struct quicklist *ql = o->ptr; + serverAssert(quicklist_node_count(ql) != 0); /* We iterate all nodes only when average node size is bigger than a * page size, and there's a high chance we'll actually dismiss something. */ - if (size_hint / ql->len >= server.page_size) { - quicklistNode *node = ql->head; + if (size_hint / quicklist_node_count(ql) >= server.page_size) { + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(ql, &p, &node); while (node) { - if (quicklistNodeIsCompressed(node)) { - dismissMemory(node->entry, ((quicklistLZF*)node->entry)->sz); + if (!node->raw) { + dismissMemory(node->carry, ((struct quicklist_lzf *)node->carry)->sz); } else { - dismissMemory(node->entry, node->sz); + dismissMemory(node->carry, node->raw_sz); } - node = node->next; + quicklist_next(p, node, &p, &node); } } } else if (o->encoding == OBJ_ENCODING_LISTPACK) { @@ -1018,14 +1020,17 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { } } else if (o->type == OBJ_LIST) { if (o->encoding == OBJ_ENCODING_QUICKLIST) { - quicklist *ql = o->ptr; - quicklistNode *node = ql->head; - asize = sizeof(*o)+sizeof(quicklist); + struct quicklist *ql = o->ptr; + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(ql, &p, &node); + asize = sizeof(*o)+sizeof(struct quicklist); do { - elesize += sizeof(quicklistNode)+zmalloc_size(node->entry); + elesize += sizeof(struct quicklist_node)+zmalloc_size(node->carry); samples++; - } while ((node = node->next) && samples < sample_size); - asize += (double)elesize/samples*ql->len; + quicklist_next(p, node, &p, &node); + } while (node && samples < sample_size); + asize += (double)elesize/samples*quicklist_node_count(ql); } else if (o->encoding == OBJ_ENCODING_LISTPACK) { asize = sizeof(*o)+zmalloc_size(o->ptr); } else { diff --git a/src/quicklist.c b/src/quicklist.c index 301a2166ee771..e7ffd9f95b54d 100644 --- a/src/quicklist.c +++ b/src/quicklist.c @@ -28,6 +28,15 @@ * POSSIBILITY OF SUCH DAMAGE. */ +/** + * Note: every function start with quicklist_p_ has view on one partition. + * + * Note: every function start with quicklist_n_ has view on one node. + * + * Note: every function start with quicklist_p_ should keep merge strategy and + * partition compress strategy. + */ + #include #include /* for memcpy */ #include @@ -37,1698 +46,2551 @@ #include "listpack.h" #include "util.h" /* for ll2string */ #include "lzf.h" -#include "redisassert.h" -#ifndef REDIS_STATIC -#define REDIS_STATIC static -#endif +/** + * Optimized levels for size-based fill. + * Note that the largest possible limit is (64k-1), + * so even if each record takes just one byte, + * it still won't overflow the 16 bit count field. + */ +static size_t QUICKLIST_OPTIMIZED_LEVEL[5] = {4096, 8192, 16384, 32768, 65535}; +static_assert(65535 < 1<= length) + return -1; + return i; +} -/* Optimization levels for size-based filling. - * Note that the largest possible limit is 64k, so even if each record takes - * just one byte, it still won't overflow the 16 bit count field. */ -static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536}; +/** + * __quicklist_fill_new - Create a new limitation for packed node. + * @pack_max_count: maximum number of elements within a packed node + * @pack_max_size: maximum size of a packed node + */ +static struct quicklist_fill *__quicklist_fill_new(int pack_max_count, + int pack_max_size) +{ + struct quicklist_fill *f = zmalloc(sizeof(*f)); + f->pack_max_count = pack_max_count; + f->pack_max_size = pack_max_size; + return f; +} -/* packed_threshold is initialized to 1gb*/ -static size_t packed_threshold = (1 << 30); +#define FILL_MAX ((1 << (QUICKLIST_FILL_BITS-1))-1) -/* set threshold for PLAIN nodes, the real limit is 4gb */ -#define isLargeElement(size) ((size) >= packed_threshold) +/** + * Maximum size in bytes of element within a listpack that is limited by count. + */ +#define QUICKLIST_SIZE_SAFETY_LIMIT 8192 + +/** + * quicklist_fill_new - Create a new limitation for packed node base on @fill. + * @fill: packed node is limited by size if fill < 0, Otherwise, + * node is limited by count. + * + * Note: see @QUICKLIST_OPTIMIZED_LEVEL and @QUICKLIST_SIZE_SAFETY_LIMIT. + * Note: if (fill==0), every node is a plain node. + * Note: free function is zfree(). + */ +struct quicklist_fill *quicklist_fill_new(int fill) +{ + if (fill >= 0) { + if (fill > FILL_MAX) + fill = FILL_MAX; + return __quicklist_fill_new(fill, QUICKLIST_SIZE_SAFETY_LIMIT); + } + + size_t i = (-fill) - 1; + size_t max_level = sizeof(QUICKLIST_OPTIMIZED_LEVEL) / + sizeof(*QUICKLIST_OPTIMIZED_LEVEL); + if (i >= max_level) + i = max_level - 1; + return __quicklist_fill_new(INT_MAX, QUICKLIST_OPTIMIZED_LEVEL[i]); +} -int quicklistisSetPackedThreshold(size_t sz) { - /* Don't allow threshold to be set above or even slightly below 4GB */ - if (sz > (1ull<<32) - (1<<20)) { - return 0; - } else if (sz == 0) { /* 0 means restore threshold */ - sz = (1 << 30); - } - packed_threshold = sz; - return 1; -} - -/* Maximum size in bytes of any multi-element listpack. - * Larger values will live in their own isolated listpacks. - * This is used only if we're limited by record count. when we're limited by - * size, the maximum limit is bigger, but still safe. - * 8k is a recommended / default size limit */ -#define SIZE_SAFETY_LIMIT 8192 - -/* Maximum estimate of the listpack entry overhead. - * Although in the worst case(sz < 64), we will waste 6 bytes in one - * quicklistNode, but can avoid memory waste due to internal fragmentation - * when the listpack exceeds the size limit by a few bytes (e.g. being 16388). */ -#define SIZE_ESTIMATE_OVERHEAD 8 - -/* Minimum listpack size in bytes for attempting compression. */ -#define MIN_COMPRESS_BYTES 48 - -/* Minimum size reduction in bytes to store compressed quicklistNode data. - * This also prevents us from storing compression if the compression - * resulted in a larger size than the original data. */ -#define MIN_COMPRESS_IMPROVE 8 - -/* If not verbose testing, remove all debug printing. */ -#ifndef REDIS_TEST_VERBOSE -#define D(...) -#else -#define D(...) \ - do { \ - printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \ - printf(__VA_ARGS__); \ - printf("\n"); \ - } while (0) -#endif +/** + * quicklist_fill_dup - Duplicate @fill. + */ +static struct quicklist_fill *quicklist_fill_dup(struct quicklist_fill *fill) +{ + return __quicklist_fill_new(fill->pack_max_count, fill->pack_max_size); +} -/* Bookmarks forward declarations */ -#define QL_MAX_BM ((1 << QL_BM_BITS)-1) -quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name); -quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node); -void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm); +/** encoding + data bytes + total bytes */ +#define QUICKLIST_PACK_ENTRY_SIZE_OVERHEAD (1+4+5) -/* Simple way to give quicklistEntry structs default values with one call. */ -#define initEntry(e) \ - do { \ - (e)->zi = (e)->value = NULL; \ - (e)->longval = -123456789; \ - (e)->quicklist = NULL; \ - (e)->node = NULL; \ - (e)->offset = 123456789; \ - (e)->sz = 0; \ - } while (0) +/** total bytes + element length + end byte */ +#define QUICKLIST_PACK_MERGE_SIZE_REDUCE (4+2+1) -/* Reset the quicklistIter to prevent it from being used again after - * insert, replace, or other against quicklist operation. */ -#define resetIterator(iter) \ - do { \ - (iter)->current = NULL; \ - (iter)->zi = NULL; \ - } while (0) +/** + * quicklist_is_large_element - Test element with size @sz is a large element + * or not based on limitation @fill. + * + * Return: 1 if element is large, 0 if not. + */ +static int quicklist_is_large_element(struct quicklist_fill *fill, size_t sz) +{ + if (fill->pack_max_count == 0) + return 1; -/* Create a new quicklist. - * Free with quicklistRelease(). */ -quicklist *quicklistCreate(void) { - struct quicklist *quicklist; - - quicklist = zmalloc(sizeof(*quicklist)); - quicklist->head = quicklist->tail = NULL; - quicklist->len = 0; - quicklist->count = 0; - quicklist->compress = 0; - quicklist->fill = -2; - quicklist->bookmark_count = 0; - return quicklist; -} - -#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1) -void quicklistSetCompressDepth(quicklist *quicklist, int compress) { - if (compress > COMPRESS_MAX) { - compress = COMPRESS_MAX; - } else if (compress < 0) { - compress = 0; - } - quicklist->compress = compress; + return sz + QUICKLIST_PACK_MERGE_SIZE_REDUCE + + QUICKLIST_PACK_ENTRY_SIZE_OVERHEAD > fill->pack_max_size; } -#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1) -void quicklistSetFill(quicklist *quicklist, int fill) { - if (fill > FILL_MAX) { - fill = FILL_MAX; - } else if (fill < -5) { - fill = -5; - } - quicklist->fill = fill; +/** + * quicklist_n_new_raw - Create a new raw node which contains one element + * with value @value and size @sz based on limitation @fill. + * + * Note: the returned node's prev and next is undefined. + * Note: free function is quicklist_n_free(). + */ +static struct quicklist_node *quicklist_n_new_raw(struct quicklist_fill *fill, + void *value, size_t sz) +{ + struct quicklist_node *node = zmalloc(sizeof(*node)); + if (quicklist_is_large_element(fill, sz)) { + node->carry = zmalloc(sz); + memcpy(node->carry, value, sz); + node->raw_sz = sz; + node->container = QUICKLIST_NODE_CONTAINER_PLAIN; + } else { + node->carry = lpPrepend(lpNew(0), value, sz); + node->raw_sz = lpBytes(node->carry); + node->container = QUICKLIST_NODE_CONTAINER_PACKED; + } + node->count = 1; + node->raw = 1; + return node; } -void quicklistSetOptions(quicklist *quicklist, int fill, int depth) { - quicklistSetFill(quicklist, fill); - quicklistSetCompressDepth(quicklist, depth); +/** + * quicklist_n_free - Deallocates the space related to @node. + */ +static void quicklist_n_free(struct quicklist_node *node) +{ + zfree(node->carry); + zfree(node); } -/* Create a new quicklist with some default parameters. */ -quicklist *quicklistNew(int fill, int compress) { - quicklist *quicklist = quicklistCreate(); - quicklistSetOptions(quicklist, fill, compress); - return quicklist; +/** + * quicklist_n_dup - Duplicate @node. + * + * Note: the returned node's prev and next is undefined. + */ +static struct quicklist_node *quicklist_n_dup(const struct quicklist_node *node) +{ + struct quicklist_node *new = zmalloc(sizeof(*new)); + if (node->raw) { + new->carry = zmalloc(node->raw_sz); + memcpy(new->carry, node->carry, node->raw_sz); + } else { + struct quicklist_lzf *lzf = (struct quicklist_lzf *)node->carry; + size_t carry_sz = sizeof(*lzf) + lzf->sz; + new->carry = zmalloc(carry_sz); + memcpy(new->carry, node->carry, carry_sz); + } + new->raw_sz = node->raw_sz; + new->container = node->container; + new->count = node->count; + new->raw = node->raw; + return new; } -REDIS_STATIC quicklistNode *quicklistCreateNode(void) { - quicklistNode *node; - node = zmalloc(sizeof(*node)); - node->entry = NULL; - node->count = 0; - node->sz = 0; - node->next = node->prev = NULL; - node->encoding = QUICKLIST_NODE_ENCODING_RAW; - node->container = QUICKLIST_NODE_CONTAINER_PACKED; - node->recompress = 0; - node->dont_compress = 0; - return node; +/** + * It's used for compress, we will not compress a small node. + */ +#define QUICKLIST_MIN_COMPRESS_BYTES 48 + +/** + * Minimum bytes of reduction should compress perform. + */ +#define QUICKLIST_MIN_COMPRESS_IMPROVE 8 + +/** + * quicklist_n_compress_raw - Perform compress on @node. + * + * Note: caller should make sure @node is a raw node. + * Note: node will keep uncompressed if it can't compress small enough. + */ +static void quicklist_n_compress_raw(struct quicklist_node *node) +{ + if (node->raw_sz < QUICKLIST_MIN_COMPRESS_BYTES) + return; + + assert(QUICKLIST_MIN_COMPRESS_BYTES > QUICKLIST_MIN_COMPRESS_IMPROVE); + size_t max_compressed_sz = node->raw_sz - QUICKLIST_MIN_COMPRESS_IMPROVE; + struct quicklist_lzf *lzf = zmalloc(sizeof(*lzf) + max_compressed_sz); + lzf->sz = lzf_compress(node->carry, node->raw_sz, + lzf->compressed, max_compressed_sz); + if (lzf->sz == 0) { + zfree(lzf); + return; + } + + lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz); + zfree(node->carry); + node->carry = lzf; + node->raw = 0; } -/* Return cached quicklist count */ -unsigned long quicklistCount(const quicklist *ql) { return ql->count; } +/** + * quicklist_n_decompress - Decompress @node if it is compressed. + * + * Return: 1 if decompress performed, 0 if not. + */ +static int quicklist_n_decompress(struct quicklist_node *node) +{ + if (node->raw) + return 0; + + void *raw = zmalloc(node->raw_sz); + struct quicklist_lzf *lzf = (struct quicklist_lzf *)node->carry; + assert(lzf_decompress(lzf->compressed, lzf->sz, raw, node->raw_sz)); + zfree(lzf); + node->carry = raw; + node->raw = 1; + return 1; +} -/* Free entire quicklist. */ -void quicklistRelease(quicklist *quicklist) { - unsigned long len; - quicklistNode *current, *next; +/** + * quicklist_n_debug_print - Print @node's information for debug. + * @element: 1 for print element information, 0 for not + */ +static void quicklist_n_debug_print(struct quicklist_node *node, int element) +{ + printf("{raw-sz: %zu}\n", node->raw_sz); + printf("{container: %d}\n", node->container); + printf("{count: %d}\n", node->count); + printf("{raw: %d}\n", node->raw); + if (!node->raw) { + struct quicklist_lzf *lzf = node->carry; + printf("{lzf sz: %zu}\n", lzf->sz); + } + if (!element) + return; + + printf("{carry}"); + int recompress = quicklist_n_decompress(node); + + if (node->container == QUICKLIST_NODE_CONTAINER_PLAIN) + printf("{%s}\n", (char *)node->carry); + else + lpRepr(node->carry); + + if (recompress) + quicklist_n_compress_raw(node); +} - current = quicklist->head; - len = quicklist->len; - while (len--) { - next = current->next; +/** + * quicklist_n_add - Add node @new between adjacent nodes @prev and @next. + */ +static void quicklist_n_add(struct quicklist_node *new, + struct quicklist_node *prev, + struct quicklist_node *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} - zfree(current->entry); - quicklist->count -= current->count; +/** + * quicklist_n_add_after - Add node @new after @node. + */ +static void quicklist_n_add_after(struct quicklist_node *new, + struct quicklist_node *node) +{ + quicklist_n_add(new, node, node->next); +} - zfree(current); +/** + * quicklist_n_add_before - Add node @new before @node. + */ +static void quicklist_n_add_before(struct quicklist_node *new, + struct quicklist_node *node) +{ + quicklist_n_add(new, node->prev, node); +} - quicklist->len--; - current = next; - } - quicklistBookmarksClear(quicklist); - zfree(quicklist); +/** + * quicklist_n_remove - Remove a node from the partition. + * @prev: node->prev + * @next: node->next + */ +static void quicklist_n_remove(struct quicklist_node *prev, + struct quicklist_node *next) +{ + next->prev = prev; + prev->next = next; } -/* 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; -#endif - if (node->dont_compress) return 0; - - /* validate that the node is neither - * tail nor head (it has prev and next)*/ - assert(node->prev && node->next); - - node->recompress = 0; - /* Don't bother compressing small values */ - if (node->sz < MIN_COMPRESS_BYTES) - return 0; - - quicklistLZF *lzf = zmalloc(sizeof(*lzf) + node->sz); - - /* Cancel if compression fails or doesn't compress small enough */ - if (((lzf->sz = lzf_compress(node->entry, node->sz, lzf->compressed, - node->sz)) == 0) || - lzf->sz + MIN_COMPRESS_IMPROVE >= node->sz) { - /* lzf_compress aborts/rejects compression if value not compressible. */ - zfree(lzf); - return 0; - } - lzf = zrealloc(lzf, sizeof(*lzf) + lzf->sz); - zfree(node->entry); - node->entry = (unsigned char *)lzf; - node->encoding = QUICKLIST_NODE_ENCODING_LZF; - return 1; +/** + * quicklist_n_remove_entry - Remove @node from the partition. + */ +static void quicklist_n_remove_entry(struct quicklist_node *node) +{ + quicklist_n_remove(node->prev, node->next); } -/* Compress only uncompressed nodes. */ -#define quicklistCompressNode(_node) \ - do { \ - if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_RAW) { \ - __quicklistCompressNode((_node)); \ - } \ - } while (0) +/** + * quicklist_n_move_after - Remove @from from the partition and add it after @to. + */ +static void quicklist_n_move_after(struct quicklist_node *from, + struct quicklist_node *to) +{ + quicklist_n_remove_entry(from); + quicklist_n_add_after(from, to); +} -/* 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 - node->attempted_compress = 0; -#endif - node->recompress = 0; - - void *decompressed = zmalloc(node->sz); - quicklistLZF *lzf = (quicklistLZF *)node->entry; - if (lzf_decompress(lzf->compressed, lzf->sz, decompressed, node->sz) == 0) { - /* Someone requested decompress, but we can't decompress. Not good. */ - zfree(decompressed); - return 0; - } - zfree(lzf); - node->entry = decompressed; - node->encoding = QUICKLIST_NODE_ENCODING_RAW; - return 1; +/** + * quicklist_n_move_before - Remove @from from the partition and add it before @to. + */ +static void quicklist_n_move_before(struct quicklist_node *from, + struct quicklist_node *to) +{ + quicklist_n_remove_entry(from); + quicklist_n_add_before(from, to); } -/* Decompress only compressed nodes. */ -#define quicklistDecompressNode(_node) \ - do { \ - if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \ - __quicklistDecompressNode((_node)); \ - } \ - } while (0) +/** + * quicklist_n_allow_add_carry - Test if @node is allowed to add a new + * element with size of @sz. + * @fill: limitation of packed node + * + * Return: 1 if addition is allowed, 0 if not. + */ +static int quicklist_n_allow_add_carry(struct quicklist_fill *fill, + struct quicklist_node *node, size_t sz) +{ + if (node->container == QUICKLIST_NODE_CONTAINER_PLAIN) + return 0; -/* Force node to not be immediately re-compressible */ -#define quicklistDecompressNodeForUse(_node) \ - do { \ - if ((_node) && (_node)->encoding == QUICKLIST_NODE_ENCODING_LZF) { \ - __quicklistDecompressNode((_node)); \ - (_node)->recompress = 1; \ - } \ - } while (0) + if (node->count >= fill->pack_max_count) + return 0; -/* Extract the raw LZF data from this quicklistNode. - * Pointer to LZF data is assigned to '*data'. - * Return value is the length of compressed LZF data. */ -size_t quicklistGetLzf(const quicklistNode *node, void **data) { - quicklistLZF *lzf = (quicklistLZF *)node->entry; - *data = lzf->compressed; - return lzf->sz; -} - -#define quicklistAllowsCompression(_ql) ((_ql)->compress != 0) - -/* Force 'quicklist' to meet compression guidelines set by compress depth. - * The only way to guarantee interior nodes get compressed is to iterate - * to our "interior" compress depth then compress the next node we find. - * If compress depth is larger than the entire list, we return immediately. */ -REDIS_STATIC void __quicklistCompress(const quicklist *quicklist, - quicklistNode *node) { - if (quicklist->len == 0) return; - - /* The head and tail should never be compressed (we should not attempt to recompress them) */ - assert(quicklist->head->recompress == 0 && quicklist->tail->recompress == 0); - - /* If length is less than our compress depth (from both sides), - * we can't compress anything. */ - if (!quicklistAllowsCompression(quicklist) || - quicklist->len < (unsigned int)(quicklist->compress * 2)) - return; - -#if 0 - /* Optimized cases for small depth counts */ - if (quicklist->compress == 1) { - quicklistNode *h = quicklist->head, *t = quicklist->tail; - quicklistDecompressNode(h); - quicklistDecompressNode(t); - if (h != node && t != node) - quicklistCompressNode(node); - return; - } else if (quicklist->compress == 2) { - quicklistNode *h = quicklist->head, *hn = h->next, *hnn = hn->next; - quicklistNode *t = quicklist->tail, *tp = t->prev, *tpp = tp->prev; - quicklistDecompressNode(h); - quicklistDecompressNode(hn); - quicklistDecompressNode(t); - quicklistDecompressNode(tp); - if (h != node && hn != node && t != node && tp != node) { - quicklistCompressNode(node); - } - if (hnn != t) { - quicklistCompressNode(hnn); - } - if (tpp != h) { - quicklistCompressNode(tpp); - } - return; - } -#endif + size_t new_sz = node->raw_sz + sz + QUICKLIST_PACK_ENTRY_SIZE_OVERHEAD; + return new_sz <= fill->pack_max_size; +} - /* Iterate until we reach compress depth for both sides of the list.a - * Note: because we do length checks at the *top* of this function, - * we can skip explicit null checks below. Everything exists. */ - quicklistNode *forward = quicklist->head; - quicklistNode *reverse = quicklist->tail; - int depth = 0; - int in_depth = 0; - while (depth++ < quicklist->compress) { - quicklistDecompressNode(forward); - quicklistDecompressNode(reverse); - - if (forward == node || reverse == node) - in_depth = 1; - - /* We passed into compress depth of opposite side of the quicklist - * so there's no need to compress anything and we can exit. */ - if (forward == reverse || forward->next == reverse) - return; - - forward = forward->next; - reverse = reverse->prev; - } +/** + * quicklist_n_allow_merge - Test if node @a and @b is allowed to merge. + * @fill: limitation of packed node + */ +static int quicklist_n_allow_merge(struct quicklist_fill *fill, + struct quicklist_node *a, + struct quicklist_node *b) { + if (a->container == QUICKLIST_NODE_CONTAINER_PLAIN) + return 0; + + if (b->container == QUICKLIST_NODE_CONTAINER_PLAIN) + return 0; + + assert(QUICKLIST_FILL_BITS * 2 <= 32); + unsigned int new_count = a->count + b->count; + size_t new_size = a->raw_sz + b->raw_sz - QUICKLIST_PACK_MERGE_SIZE_REDUCE; + return new_size <= fill->pack_max_size && new_count <= fill->pack_max_count; +} + +/** + * quicklist_n_try_add_carry - Try to add a new element with value @value and + * size @sz as @node's head or tail. + * @tail: 0 for add as head, 1 for add as tail + * @fill: limitation of packed node + * @compress: 1 for compress @node after operation, 0 for not + * + * Return: 1 if element is added, 0 if not. + */ +static int quicklist_n_try_add_carry(int tail, struct quicklist_fill *fill, + struct quicklist_node *node, int compress, + void *value, size_t sz) +{ + if(!quicklist_n_allow_add_carry(fill, node, sz)) + return 0; + + quicklist_n_decompress(node); - if (!in_depth) - quicklistCompressNode(node); + if (tail) + node->carry = lpAppend(node->carry, value, sz); + else + node->carry = lpPrepend(node->carry, value, sz); - /* At this point, forward and reverse are one node beyond depth */ - quicklistCompressNode(forward); - quicklistCompressNode(reverse); + node->raw_sz = lpBytes(node->carry); + node->count++; + if (compress) + quicklist_n_compress_raw(node); + return 1; } -#define quicklistCompress(_ql, _node) \ - do { \ - if ((_node)->recompress) \ - quicklistCompressNode((_node)); \ - else \ - __quicklistCompress((_ql), (_node)); \ - } while (0) +/** + * quicklist_n_try_add_carry_head - Try to add a new element with + * value @value and size @sz as @node's head. + * @fill: limitation of packed node + * @compress: 1 for compress @node after operation, 0 for not + * + * Return: 1 if element is added, 0 if not. + */ +static int quicklist_n_try_add_carry_head(struct quicklist_fill *fill, + struct quicklist_node *node, int compress, + void *value, size_t sz) +{ + return quicklist_n_try_add_carry(0, fill, node, compress, value, sz); +} -/* If we previously used quicklistDecompressNodeForUse(), just recompress. */ -#define quicklistRecompressOnly(_node) \ - do { \ - if ((_node)->recompress) \ - quicklistCompressNode((_node)); \ - } while (0) +/** + * quicklist_n_try_add_carry_tail - Try to add a new element with + * value @value and size @sz as @node's tail. + * @fill: limitation of packed node + * @compress: 1 for compress @node after operation, 0 for not + * + * Return: 1 if element is added, 0 if not. + */ +static int quicklist_n_try_add_carry_tail(struct quicklist_fill *fill, + struct quicklist_node *node, int compress, + void *value, size_t sz) +{ + return quicklist_n_try_add_carry(1, fill, node, compress, value, sz); +} -/* Insert 'new_node' after 'old_node' if 'after' is 1. - * Insert 'new_node' before 'old_node' if 'after' is 0. - * Note: 'new_node' is *always* uncompressed, so if we assign it to - * head or tail, we do not need to uncompress it. */ -REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist, - quicklistNode *old_node, - quicklistNode *new_node, int after) { - if (after) { - new_node->prev = old_node; - if (old_node) { - new_node->next = old_node->next; - if (old_node->next) - old_node->next->prev = new_node; - old_node->next = new_node; - } - if (quicklist->tail == old_node) - quicklist->tail = new_node; - } else { - new_node->next = old_node; - if (old_node) { - new_node->prev = old_node->prev; - if (old_node->prev) - old_node->prev->next = new_node; - old_node->prev = new_node; - } - if (quicklist->head == old_node) - quicklist->head = new_node; - } - /* If this insert creates the only element so far, initialize head/tail. */ - if (quicklist->len == 0) { - quicklist->head = quicklist->tail = new_node; - } +/** + * quicklist_n_del_element - Delete exactly @n elements from @node start + * from @from and working towards to tail. + * @compress: 1 for compress @node after operation, 0 for not + * + * Note: caller should make sure @from and @n is valid. + */ +static void quicklist_n_del_element(struct quicklist_node *node, int compress, + long from, long n) +{ + assert(0 <= from && from < node->count && n <= node->count - from); + + quicklist_n_decompress(node); + node->carry = lpDeleteRange(node->carry, from, n); + node->raw_sz = lpBytes(node->carry); + node->count -= n; + if (compress) + quicklist_n_compress_raw(node); +} - /* Update len first, so in __quicklistCompress we know exactly len */ - quicklist->len++; +/** + * quicklist_n_del_element_forward - Delete at most @n elements from @node + * start from @from and working towards to tail. + * @compress: 1 for compress @node after operation, 0 for not + * + * Return: number of elements is deleted. + * + * Note: caller should make sure @from and @n is valid. + */ +static long quicklist_n_del_element_forward(struct quicklist_node *node, + int compress, long from, long n) +{ + assert( 0 <= from && from < node->count && n > 0); + + long deletable = node->count - from; + long deleted = n <= deletable ? n : deletable; + quicklist_n_del_element(node, compress, from, deleted); + return deleted; +} - if (old_node) - quicklistCompress(quicklist, old_node); +/** + * quicklist_n_del_element_backward - Delete at most @n elements from @node + * start from @from and working towards to head. + * @compress: is @node requires compress + * + * Return: number of elements is deleted. + * + * Note: caller should make sure @from and @n is valid. + */ +static long quicklist_n_del_element_backward(struct quicklist_node *node, + int compress, long from, long n) +{ + assert( 0 <= from && from < node->count-1 && n > 0); - quicklistCompress(quicklist, new_node); + long deletable = from + 1; + long deleted = n <= deletable ? n : deletable; + quicklist_n_del_element(node, compress, deletable-deleted, deleted); + return deleted; } -/* Wrappers for node inserting around existing node. */ -REDIS_STATIC void _quicklistInsertNodeBefore(quicklist *quicklist, - quicklistNode *old_node, - quicklistNode *new_node) { - __quicklistInsertNode(quicklist, old_node, new_node, 0); +/** + * quicklist_n_split_raw - Split @node into two nodes, @node holds the + * first @n elements, and the node holds the rest elements is returned. + * + * Return: the node holds the elements split from @node. + * + * Note: caller should make sure @node is a raw node and @n is valid. + * TODO: liskpack should implement split. + */ +static struct quicklist_node *quicklist_n_split_raw(struct quicklist_node *node, + long n) +{ + assert(node->raw && n < node->count); + + unsigned char *carry = zmalloc(node->raw_sz); + memcpy(carry, node->carry, node->raw_sz); + carry = lpDeleteRange(carry, 0, n); + + struct quicklist_node *new = zmalloc(sizeof(*new)); + new->carry = carry; + new->raw_sz = lpBytes(carry); + new->container = QUICKLIST_NODE_CONTAINER_PACKED; + new->count = node->count - n; + new->raw = 1; + + node->carry = lpDeleteRange(node->carry, n, new->count); + node->raw_sz = lpBytes(node->carry); + node->count = n; + + return new; } -REDIS_STATIC void _quicklistInsertNodeAfter(quicklist *quicklist, - quicklistNode *old_node, - quicklistNode *new_node) { - __quicklistInsertNode(quicklist, old_node, new_node, 1); +/** + * quicklist_p_init - Init partition @p. + * + * Note: free function is quicklist_p_free(). + */ +static void quicklist_p_init(struct quicklist_partition *p, + struct quicklist_partition *prev, + struct quicklist_partition *next, + int which, unsigned long capacity) +{ + struct quicklist_node *guard = zmalloc(sizeof(*guard)); + guard->next = guard; + guard->prev = guard; + + p->which = which; + p->capacity = capacity; + p->guard = guard; + p->prev = prev; + p->next = next; + p->length = 0; } -#define sizeMeetsSafetyLimit(sz) ((sz) <= SIZE_SAFETY_LIMIT) +/** + * quicklist_p_free - Deallocates the space related to @p. + */ +static void quicklist_p_free(struct quicklist_partition *p) +{ + struct quicklist_node *guard = p->guard; + struct quicklist_node *node = guard->next; + while (node != guard) { + struct quicklist_node *next = node->next; + quicklist_n_free(node); + node = next; + } + zfree(p->guard); + zfree(p); +} -/* Calculate the size limit or length limit of the quicklist node - * based on 'fill', and is also used to limit list listpack. */ -void quicklistNodeLimit(int fill, size_t *size, unsigned int *count) { - *size = SIZE_MAX; - *count = UINT_MAX; +/** + * quicklist_p_copy - Copy nodes form @from to @to. + */ +static void quicklist_p_copy(struct quicklist_partition *to, + struct quicklist_partition *from) +{ + to->length = from->length; + + struct quicklist_node *guard = from->guard; + struct quicklist_node *node = guard->next; + while (node != guard) { + struct quicklist_node *new = quicklist_n_dup(node); + quicklist_n_add_before(new, to->guard); + node = node->next; + } +} - if (fill >= 0) { - /* Ensure that one node have at least one entry */ - *count = (fill == 0) ? 1 : fill; - } else { - size_t offset = (-fill) - 1; - size_t max_level = sizeof(optimization_level) / sizeof(*optimization_level); - if (offset >= max_level) offset = max_level - 1; - *size = optimization_level[offset]; - } +/** + * quicklist_p_debug_print - Print @p's information for debug. + * @i: the index of @p's first node + * @element: 1 for print element information, 0 for not + * + * Return: number of nodes in @p. + */ +static long quicklist_p_debug_print(struct quicklist_partition *p, + long i, int element) +{ + printf("{which: %d}\n", p->which); + printf("{length: %ld}\n", p->length); + printf("{capacity: %ld}\n", p->capacity); + + struct quicklist_node *guard = p->guard; + struct quicklist_node *node = guard->next; + long count = 0; + long node_count = 0; + while (node != guard) { + count += node->count; + node_count++; + printf("{node[%ld]}\n", i++); + quicklist_n_debug_print(node, element); + node = node->next; + } + printf("{length from node: %ld}\n", node_count); + return count; } -/* Check if the limit of the quicklist node has been reached to determine if - * insertions, merges or other operations that would increase the size of - * the node can be performed. - * Return 1 if exceeds the limit, otherwise 0. */ -int quicklistNodeExceedsLimit(int fill, size_t new_sz, unsigned int new_count) { - size_t sz_limit; - unsigned int count_limit; - quicklistNodeLimit(fill, &sz_limit, &count_limit); - - if (likely(sz_limit != SIZE_MAX)) { - return new_sz > sz_limit; - } else if (count_limit != UINT_MAX) { - /* when we reach here we know that the limit is a size limit (which is - * safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */ - if (!sizeMeetsSafetyLimit(new_sz)) return 1; - return new_count > count_limit; - } +/** + * quicklist_p_is_empty - Test @p is empty or not. + * + * Return: 1 if @p is empty, 0 if not. + */ +static int quicklist_p_is_empty(struct quicklist_partition *p) +{ + return p->length == 0; +} - redis_unreachable(); +/** + * quicklist_p_is_full - Test @p is full or not. + * + * Return: 1 if @p is full, 0 if not. + */ +static int quicklist_p_is_full(struct quicklist_partition *p) +{ + return p->length >= p->capacity; } -REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node, - const int fill, const size_t sz) { - if (unlikely(!node)) - return 0; +/** + * quicklist_p_is_overflow - Test @p is overflow or not. + * + * Return: 1 if @p is overflow, 0 if not. + */ +static int quicklist_p_is_overflow(struct quicklist_partition *p) +{ + return p->length > p->capacity; +} - if (unlikely(QL_NODE_IS_PLAIN(node) || isLargeElement(sz))) - return 0; +/** + * quicklist_p_is_head - Test @p is a quicklist's head partition or not. + * + * Return: 1 if @p is a head partition, 0 if not. + */ +static int quicklist_p_is_head(struct quicklist_partition *p) +{ + return p->which == QUICKLIST_P_HEAD; +} - /* Estimate how many bytes will be added to the listpack by this one entry. - * We prefer an overestimation, which would at worse lead to a few bytes - * below the lowest limit of 4k (see optimization_level). - * Note: No need to check for overflow below since both `node->sz` and - * `sz` are to be less than 1GB after the plain/large element check above. */ - size_t new_sz = node->sz + sz + SIZE_ESTIMATE_OVERHEAD; - if (unlikely(quicklistNodeExceedsLimit(fill, new_sz, node->count + 1))) - return 0; - return 1; +/** + * quicklist_p_is_middle - Test @p is a quicklist's middle partition or not. + * + * Return: 1 if @p is a middle partition, 0 if not. + */ +static int quicklist_p_is_middle(struct quicklist_partition *p) +{ + return p->which == QUICKLIST_P_MIDDLE; } -REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a, - const quicklistNode *b, - const int fill) { - if (!a || !b) - return 0; +/** + * quicklist_p_is_tail - Test @p is a quicklist's tail partition or not. + * + * Return: 1 if @p is a tail partition, 0 if not. + */ +static int quicklist_p_is_tail(struct quicklist_partition *p) +{ + return p->which == QUICKLIST_P_TAIL; +} - if (unlikely(QL_NODE_IS_PLAIN(a) || QL_NODE_IS_PLAIN(b))) - return 0; +/** + * quicklist_p_first - Get first node in @p. + * + * Note: caller should make sure @p is not empty. + */ +static struct quicklist_node *quicklist_p_first(struct quicklist_partition *p) +{ + assert(!quicklist_p_is_empty(p)); + return p->guard->next; +} - /* approximate merged listpack size (- 7 to remove one listpack - * header/trailer, see LP_HDR_SIZE and LP_EOF) */ - unsigned int merge_sz = a->sz + b->sz - 7; - if (unlikely(quicklistNodeExceedsLimit(fill, merge_sz, a->count + b->count))) - return 0; - return 1; +/** + * quicklist_p_last - Get last node in @p. + * + * Note: caller should make sure @p is not empty. + */ +static struct quicklist_node *quicklist_p_last(struct quicklist_partition *p) +{ + assert(!quicklist_p_is_empty(p)); + return p->guard->prev; } -#define quicklistNodeUpdateSz(node) \ - do { \ - (node)->sz = lpBytes((node)->entry); \ - } while (0) +/** + * quicklist_p_add_node - Add raw node @new_raw to @p between @prev and @next. + * @keep_raw: 1 for keep @new_raw a raw node, 0 for following partition + * compress strategy. + * + * Note: guard node is acceptable for @prev and @next. + */ +static void quicklist_p_add_node(struct quicklist_partition *p, + struct quicklist_node *prev, struct quicklist_node *next, + struct quicklist_node *new_raw, int keep_raw) +{ + quicklist_n_add(new_raw, prev, next); + p->length++; + if (!keep_raw && quicklist_p_is_middle(p)) + quicklist_n_compress_raw(new_raw); +} -static quicklistNode* __quicklistCreatePlainNode(void *value, size_t sz) { - quicklistNode *new_node = quicklistCreateNode(); - new_node->entry = zmalloc(sz); - new_node->container = QUICKLIST_NODE_CONTAINER_PLAIN; - memcpy(new_node->entry, value, sz); - new_node->sz = sz; - new_node->count++; - return new_node; +/** + * quicklist_p_move_forward - Move @from's tail to @to's head. + * + * Note: caller should make sure @from is not empty. + */ +static void quicklist_p_move_forward(struct quicklist_partition *from, + struct quicklist_partition *to) +{ + struct quicklist_node *last = quicklist_p_last(from); + quicklist_n_decompress(last); + if (quicklist_p_is_middle(to)) + quicklist_n_compress_raw(last); + + quicklist_n_move_after(last, to->guard); + from->length--; + to->length++; } -static void __quicklistInsertPlainNode(quicklist *quicklist, quicklistNode *old_node, - void *value, size_t sz, int after) { - __quicklistInsertNode(quicklist, old_node, __quicklistCreatePlainNode(value, sz), after); - quicklist->count++; +/** + * __quicklist_p_move_forward - Move @from's tail to @to's head, and + * monitor @node's partition. + * @p: partation that @node belongs to before moving. + * + * Return: partation that @node belongs to after moving. + * + * Note: caller should make sure @from is not empty. + * Note: if @node is raw, @node will keet raw, otherwise @node will following + * partition compress strategy. This is to avoid unnecessary compression. + */ +struct quicklist_partition *__quicklist_p_move_forward( + struct quicklist_partition *from, + struct quicklist_partition *to, + struct quicklist_partition *p, + struct quicklist_node *node) +{ + struct quicklist_node *last = quicklist_p_last(from); + quicklist_n_decompress(last); + if (last != node && quicklist_p_is_middle(to)) + quicklist_n_compress_raw(last); + + quicklist_n_move_after(last, to->guard); + from->length--; + to->length++; + + if (last == node) + return to; + return p; } -/* Add new entry to head node of quicklist. - * - * Returns 0 if used existing head. - * Returns 1 if new head created. */ -int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) { - quicklistNode *orig_head = quicklist->head; - - if (unlikely(isLargeElement(sz))) { - __quicklistInsertPlainNode(quicklist, quicklist->head, value, sz, 0); - return 1; - } +/** + * quicklist_p_move_backward - Move @from's head to @to's tail. + * + * Note: caller should make sure @from is not empty. + */ +static void quicklist_p_move_backward(struct quicklist_partition *from, + struct quicklist_partition *to) +{ + struct quicklist_node *first = quicklist_p_first(from); + quicklist_n_decompress(first); + if (quicklist_p_is_middle(to)) + quicklist_n_compress_raw(first); + + quicklist_n_move_before(first, to->guard); + from->length--; + to->length++; +} - if (likely( - _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) { - quicklist->head->entry = lpPrepend(quicklist->head->entry, value, sz); - quicklistNodeUpdateSz(quicklist->head); - } else { - quicklistNode *node = quicklistCreateNode(); - node->entry = lpPrepend(lpNew(0), value, sz); +/** + * __quicklist_p_move_backward - Move @from's head to @to's tail, and + * monitor @node's partition. + * @p: partation that @node belongs to before moving. + * + * Return: @node's partition after moving. + * + * Note: caller should make sure @from is not empty. + * Note: if @node is raw, @node will kept raw, otherwise @node will following + * partition compress strategy. This is to avoid unnecessary compression. + */ +struct quicklist_partition *__quicklist_p_move_backward( + struct quicklist_partition *from, + struct quicklist_partition *to, + struct quicklist_partition *p, + struct quicklist_node *node) +{ + struct quicklist_node *first = quicklist_p_first(from); + quicklist_n_decompress(first); + if (first != node && quicklist_p_is_middle(to)) + quicklist_n_compress_raw(first); + + quicklist_n_move_before(first, to->guard); + from->length--; + to->length++; + + if (first == node) + return to; + return p; +} - quicklistNodeUpdateSz(node); - _quicklistInsertNodeBefore(quicklist, quicklist->head, node); - } - quicklist->count++; - quicklist->head->count++; - return (orig_head != quicklist->head); +/** + * __quicklist_p_del_node - Remove @node from @p and free @node. + * + * Note: don't forget to update bookmark before delete the node. + */ +static void __quicklist_p_del_node(struct quicklist_partition *p, + struct quicklist_node *node) +{ + quicklist_n_remove_entry(node); + quicklist_n_free(node); + p->length--; } -/* Add new entry to tail node of quicklist. - * - * Returns 0 if used existing tail. - * Returns 1 if new tail created. */ -int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) { - quicklistNode *orig_tail = quicklist->tail; - if (unlikely(isLargeElement(sz))) { - __quicklistInsertPlainNode(quicklist, quicklist->tail, value, sz, 1); - return 1; - } +/** + * quicklist_bm_clear - Clear all bookmarks in @quicklist. + * + * Note: we do not shrink (realloc) @quicklist, + * it is called just before free @quicklist. + */ +static void quicklist_bm_clear(struct quicklist *quicklist) +{ + while (quicklist->bookmark_count) + zfree(quicklist->bookmarks[--quicklist->bookmark_count].name); +} - if (likely( - _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) { - quicklist->tail->entry = lpAppend(quicklist->tail->entry, value, sz); - quicklistNodeUpdateSz(quicklist->tail); - } else { - quicklistNode *node = quicklistCreateNode(); - node->entry = lpAppend(lpNew(0), value, sz); +/** + * quicklist_bm_find_by_name - Find bookmark with name @name. + * @quicklist: the quicklist that the bookmark belongs to + * + * Return: the bookmark is found, NULL if not found. + */ +static struct quicklist_bookmark *quicklist_bm_find_by_name( + struct quicklist *quicklist, char *name) { + for (int i = 0; i < quicklist->bookmark_count; i++) { + if (strcmp(quicklist->bookmarks[i].name, name) == 0) + return &quicklist->bookmarks[i]; + } + return NULL; +} - quicklistNodeUpdateSz(node); - _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); - } - quicklist->count++; - quicklist->tail->count++; - return (orig_tail != quicklist->tail); +/** + * quicklist_bm_find_by_name - Find the node which the bookmark + * with name @name marked. + * @quicklist: the quicklist that the bookmark belongs to + * + * Return: the node is found, NULL if not found. + */ +struct quicklist_node *quicklist_bm_find(struct quicklist *quicklist, char *name) +{ + struct quicklist_bookmark *bm = quicklist_bm_find_by_name(quicklist, name); + if (!bm) + return NULL; + return bm->node; } -/* Create new node consisting of a pre-formed listpack. - * Used for loading RDBs where entire listpacks have been stored - * to be retrieved later. */ -void quicklistAppendListpack(quicklist *quicklist, unsigned char *zl) { - quicklistNode *node = quicklistCreateNode(); +/** + * __quicklist_bm_delete - Delete @bm. + * @quicklist: the quicklist that the bookmark belongs to + * + * Note: we do not shrink (realloc) @quicklist yet (to avoid resonance), + * it may be re-used later (a call to realloc may NOP). + */ +static void __quicklist_bm_delete(struct quicklist *quicklist, + struct quicklist_bookmark *bm) { + int index = bm - quicklist->bookmarks; + zfree(bm->name); + quicklist->bookmark_count--; + memmove(bm, bm+1, (quicklist->bookmark_count - index)* sizeof(*bm)); +} - node->entry = zl; - node->count = lpLength(node->entry); - node->sz = lpBytes(zl); +/** + * quicklist_bm_delete - Delete the bookmark with name @name. + * @quicklist: the quicklist that the bookmark belongs to + * + * Return: 1 if the bookmark is found, 0 if not. + */ +int quicklist_bm_delete(struct quicklist *ql, char *name) +{ + struct quicklist_bookmark *bm = quicklist_bm_find_by_name(ql, name); + if (!bm) + return 0; + __quicklist_bm_delete(ql, bm); + return 1; +} - _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); - quicklist->count += node->count; +#define QUICKLIST_MAX_BM 15 + +/** + * quicklist_bm_create - Create a new bookmark with name @name. + * @ql_ref: reference of the quicklist that the bookmark belongs to + * @node: the node marked + * + * Return: 1 if the bookmark is created, 0 if not. + * + * Note: the marked node will replaced with @node if the bookmark + * is already exist. + */ +int quicklist_bm_create(struct quicklist **ql_ref, char *name, + struct quicklist_node *node) +{ + struct quicklist *ql = *ql_ref; + if (ql->bookmark_count >= QUICKLIST_MAX_BM) + return 0; + + struct quicklist_bookmark *bm = quicklist_bm_find_by_name(ql, name); + if (bm) { + bm->node = node; + return 1; + } + + size_t bm_sz = (ql->bookmark_count+1) * sizeof(struct quicklist_bookmark); + ql = zrealloc(ql, sizeof(struct quicklist) + bm_sz); + *ql_ref = ql; + ql->bookmarks[ql->bookmark_count].node = node; + ql->bookmarks[ql->bookmark_count].name = zstrdup(name); + ql->bookmark_count++; + return 1; } -/* Create new node consisting of a pre-formed plain node. - * Used for loading RDBs where entire plain node has been stored - * to be retrieved later. - * data - the data to add (pointer becomes the responsibility of quicklist) */ -void quicklistAppendPlainNode(quicklist *quicklist, unsigned char *data, size_t sz) { - quicklistNode *node = quicklistCreateNode(); +/** + * quicklist_bm_replace - Replace all the marked node @old with @new. + * @quicklist: the quicklist that the bookmark belongs to + * + * Return: the bookmark is found, NULL if not. + * + * Note: caller should make sure @new is not NULL. + */ +static void quicklist_bm_replace(struct quicklist *quicklist, + struct quicklist_node *old, struct quicklist_node *new) +{ + for (int i = 0; i < quicklist->bookmark_count; i++) { + if (quicklist->bookmarks[i].node == old) + quicklist->bookmarks[i].node = new; + } +} - node->entry = data; - node->count = 1; - node->sz = sz; - node->container = QUICKLIST_NODE_CONTAINER_PLAIN; +/** + * quicklist_bm_move_next - Move all the marked node @node to its next. + * @quicklist: the quicklist that the bookmark belongs to + * + * Note: the bookmark will be deleted if @node is the last node. + */ +static void quicklist_bm_move_next(struct quicklist *quicklist, + struct quicklist_node *node) +{ + struct quicklist_bookmark *bms = quicklist->bookmarks; + for (int i = 0; i < quicklist->bookmark_count; i++) { + if (bms[i].node == node) { + bms[i].node = quicklist_next_for_bookmark(quicklist, node); + if (!bms[i].node) + __quicklist_bm_delete(quicklist, &bms[i]); + } + } +} - _quicklistInsertNodeAfter(quicklist, quicklist->tail, node); - quicklist->count += node->count; +/** + * quicklist_new_head - Create a new quicklist->head. + * @raw_cap: capacity of head and tail partition + * @compress_cap: capacity of middle partition + */ +static struct quicklist_partition *quicklist_new_head(long raw_cap, long compress_cap) +{ + struct quicklist_partition *head = zmalloc(sizeof(*head)); + struct quicklist_partition *middle = zmalloc(sizeof(*middle)); + struct quicklist_partition *tail = zmalloc(sizeof(*tail)); + quicklist_p_init(head, tail, middle, QUICKLIST_P_HEAD, raw_cap); + quicklist_p_init(middle, head, tail, QUICKLIST_P_MIDDLE, compress_cap); + quicklist_p_init(tail, middle, head, QUICKLIST_P_TAIL, raw_cap); + return head; } -#define quicklistDeleteIfEmpty(ql, n) \ - do { \ - if ((n)->count == 0) { \ - __quicklistDelNode((ql), (n)); \ - (n) = NULL; \ - } \ - } while (0) +/** + * quicklist_new - Create a new quicklist. + * @fill: the limitation for packed node, See quicklist_fill_new() + * @compress: the compression strategy. No node will be compressed + * if 0 is specified. Otherwise, it specifies the depth of nodes on either + * side of the quicklist that will not be compressed. + * + * Note: if (fill==0), every node is a plain node. + * Note: free function is quicklist_free(). + */ +struct quicklist *quicklist_new(int fill, int compress) +{ + long raw_cap; + long compress_cap; + if (compress == 0) { + raw_cap = LONG_MAX; + compress_cap = 0; + } else { + raw_cap = compress; + compress_cap = LONG_MAX; + } + + struct quicklist *quicklist = zmalloc(sizeof(*quicklist)); + quicklist->head = quicklist_new_head(raw_cap, compress_cap); + quicklist->fill = quicklist_fill_new(fill); + quicklist->count = 0; + quicklist->bookmark_count = 0; + return quicklist; +} -REDIS_STATIC void __quicklistDelNode(quicklist *quicklist, - quicklistNode *node) { - /* Update the bookmark if any */ - quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node); - if (bm) { - bm->node = node->next; - /* if the bookmark was to the last node, delete it. */ - if (!bm->node) - _quicklistBookmarkDelete(quicklist, bm); - } +/** + * quicklist_free - Deallocates the space related to @quicklist. + */ +void quicklist_free(struct quicklist *quicklist) +{ + struct quicklist_partition *p = quicklist->head; + quicklist_p_free(p->prev); + quicklist_p_free(p->next); + quicklist_p_free(p); + zfree(quicklist->fill); + quicklist_bm_clear(quicklist); + zfree(quicklist); +} - if (node->next) - node->next->prev = node->prev; - if (node->prev) - node->prev->next = node->next; +/** + * quicklist_dup - Duplicate @quicklist. + * + * Note: bookmark is not copied. + */ +struct quicklist *quicklist_dup(struct quicklist *quicklist) +{ + long raw_cap = quicklist->head->capacity; + long compress_cap = quicklist->head->next->capacity; - if (node == quicklist->tail) { - quicklist->tail = node->prev; - } + struct quicklist *new = zmalloc(sizeof(*new)); + new->head = quicklist_new_head(raw_cap, compress_cap); + new->fill = quicklist_fill_dup(quicklist->fill); + new->count = quicklist->count; + new->bookmark_count = 0; - if (node == quicklist->head) { - quicklist->head = node->next; - } + quicklist_p_copy(new->head, quicklist->head); + quicklist_p_copy(new->head->next, quicklist->head->next); + quicklist_p_copy(new->head->prev, quicklist->head->prev); - /* Update len first, so in __quicklistCompress we know exactly len */ - quicklist->len--; - quicklist->count -= node->count; + return new; +} - /* If we deleted a node within our compress depth, we - * now have compressed nodes needing to be decompressed. */ - __quicklistCompress(quicklist, NULL); +/** + * quicklist_debug_print - Print @quicklist's information for debug. + * @element: 1 for print element information, 0 for not. + */ +void quicklist_debug_print(struct quicklist *quicklist, int element) +{ + long i = 0; + long count = 0; + printf("{partition head}\n"); + count += quicklist_p_debug_print(quicklist->head, i, element); + + i += quicklist->head->length; + printf("{partition middle}\n"); + count += quicklist_p_debug_print(quicklist->head->next, i, element); + + i += quicklist->head->next->length; + printf("{partition tail}\n"); + count += quicklist_p_debug_print(quicklist->head->prev, i, element); + + printf("{count: %ld}\n", quicklist->count); + printf("{count from partition: %ld}\n", count); + printf("{fill pack_max_count: %u}\n", quicklist->fill->pack_max_count); + printf("{fill pack_max_size: %u}\n", quicklist->fill->pack_max_size); +} - zfree(node->entry); - zfree(node); +/** + * quicklist_count - Get the number of elements in @quicklist. + */ +long quicklist_count(struct quicklist *quicklist) +{ + return quicklist->count; } -/* Delete one entry from list given the node for the entry and a pointer - * to the entry in the node. - * - * Note: quicklistDelIndex() *requires* uncompressed nodes because you - * already had to get *p from an uncompressed node somewhere. - * - * Returns 1 if the entire node was deleted, 0 if node still exists. - * Also updates in/out param 'p' with the next offset in the listpack. */ -REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node, - unsigned char **p) { - int gone = 0; - - if (unlikely(QL_NODE_IS_PLAIN(node))) { - __quicklistDelNode(quicklist, node); - return 1; - } - node->entry = lpDelete(node->entry, *p, p); - node->count--; - if (node->count == 0) { - gone = 1; - __quicklistDelNode(quicklist, node); - } else { - quicklistNodeUpdateSz(node); - } - quicklist->count--; - /* If we deleted the node, the original node is no longer valid */ - return gone ? 1 : 0; +/** + * quicklist_node_count - Get the number of nodes in @quicklist. + */ +long quicklist_node_count(struct quicklist *quicklist) +{ + struct quicklist_partition *p = quicklist->head; + return p->length + p->prev->length + p->next->length; } -/* Delete one element represented by 'entry' - * - * 'entry' stores enough metadata to delete the proper position in - * the correct listpack in the correct quicklist node. */ -void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) { - quicklistNode *prev = entry->node->prev; - quicklistNode *next = entry->node->next; - int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist, - entry->node, &entry->zi); - - /* after delete, the zi is now invalid for any future usage. */ - iter->zi = NULL; - - /* If current node is deleted, we must update iterator node and offset. */ - if (deleted_node) { - if (iter->direction == AL_START_HEAD) { - iter->current = next; - iter->offset = 0; - } else if (iter->direction == AL_START_TAIL) { - iter->current = prev; - iter->offset = -1; - } - } - /* else if (!deleted_node), no changes needed. - * we already reset iter->zi above, and the existing iter->offset - * doesn't move again because: - * - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1 - * - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0 - * if we deleted the last element at offset N and now - * length of this listpack is N-1, the next call into - * quicklistNext() will jump to the next node. */ -} - -/* Replace quicklist entry by 'data' with length 'sz'. */ -void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry, - void *data, size_t sz) -{ - quicklist* quicklist = iter->quicklist; - - if (likely(!QL_NODE_IS_PLAIN(entry->node) && !isLargeElement(sz))) { - entry->node->entry = lpReplace(entry->node->entry, &entry->zi, data, sz); - quicklistNodeUpdateSz(entry->node); - /* quicklistNext() and quicklistGetIteratorEntryAtIdx() provide an uncompressed node */ - quicklistCompress(quicklist, entry->node); - } else if (QL_NODE_IS_PLAIN(entry->node)) { - if (isLargeElement(sz)) { - zfree(entry->node->entry); - entry->node->entry = zmalloc(sz); - entry->node->sz = sz; - memcpy(entry->node->entry, data, sz); - quicklistCompress(quicklist, entry->node); - } else { - quicklistInsertAfter(iter, entry, data, sz); - __quicklistDelNode(quicklist, entry->node); - } - } else { - entry->node->dont_compress = 1; /* Prevent compression in quicklistInsertAfter() */ - quicklistInsertAfter(iter, entry, data, sz); - if (entry->node->count == 1) { - __quicklistDelNode(quicklist, entry->node); - } else { - unsigned char *p = lpSeek(entry->node->entry, -1); - quicklistDelIndex(quicklist, entry->node, &p); - entry->node->dont_compress = 0; /* Re-enable compression */ - quicklistCompress(quicklist, entry->node); - quicklistCompress(quicklist, entry->node->next); - } - } +/** + * quicklist_first_node - Get the first node of @quicklist. + * @p: if quicklist is not empty first node's partition will store in *@p, + * otherwise, *@p will set to NULL. + * @node: if quicklist is not empty first node will store in *@node, + * otherwise, *@node will set to NULL. + */ +void quicklist_first_node(struct quicklist *quicklist, + struct quicklist_partition **p, struct quicklist_node **node) +{ + if (quicklist->count == 0) { + *p = NULL; + *node = NULL; + return; + } + + struct quicklist_partition *_p = quicklist->head; + while (_p->length == 0) + _p = _p->next; + *p = _p; + *node = quicklist_p_first(_p); +} - /* In any case, we reset iterator to forbid use of iterator after insert. - * Notice: iter->current has been compressed above. */ - resetIterator(iter); +/** + * quicklist_last_node - Get the last node of @quicklist. + * @p: if quicklist is not empty last node's partition will store in *@p, + * otherwise, *@p will set to NULL. + * @node: if quicklist is not empty last node will store in *@node, + * otherwise, *@node will set to NULL. + */ +static void quicklist_last_node(struct quicklist *quicklist, + struct quicklist_partition **p, struct quicklist_node **node) +{ + if (quicklist->count == 0) { + *p = NULL; + *node = NULL; + return; + } + + struct quicklist_partition *_p = quicklist->head->prev; + while (_p->length == 0) + _p = _p->prev; + *p = _p; + *node = quicklist_p_last(_p); } -/* Replace quicklist entry at offset 'index' by 'data' with length 'sz'. - * - * Returns 1 if replace happened. - * Returns 0 if replace failed and no changes happened. */ -int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, - size_t sz) { - quicklistEntry entry; - quicklistIter *iter = quicklistGetIteratorEntryAtIdx(quicklist, index, &entry); - if (likely(iter)) { - quicklistReplaceEntry(iter, &entry, data, sz); - quicklistReleaseIterator(iter); - return 1; - } else { - return 0; - } +/** + * quicklist_prev - Find the previous real node of @node, and store the + * partition to *@prev_p, and store the node to *@prev_node. If previous node + * is not found, NULL is stored to *@prev_p and *@prev_node. + * @p: partition @node belongs to + */ +static void quicklist_prev(struct quicklist_partition *p, + struct quicklist_node *node, + struct quicklist_partition **prev_p, + struct quicklist_node **prev_node) +{ + if (node->prev != p->guard) { + *prev_p = p; + *prev_node = node->prev; + return; + } + + struct quicklist_partition *p_prev = p->prev; + while (!quicklist_p_is_tail(p_prev) && p_prev->length == 0) + p_prev = p_prev->prev; + if (quicklist_p_is_tail(p_prev)) { + *prev_p = NULL; + *prev_node = NULL; + } else { + *prev_p = p_prev; + *prev_node = quicklist_p_last(p_prev); + } } -/* Given two nodes, try to merge their listpacks. - * - * This helps us not have a quicklist with 3 element listpacks if - * our fill factor can handle much higher levels. - * - * Note: 'a' must be to the LEFT of 'b'. - * - * After calling this function, both 'a' and 'b' should be considered - * unusable. The return value from this function must be used - * instead of re-using any of the quicklistNode input arguments. - * - * Returns the input node picked to merge against or NULL if - * merging was not possible. */ -REDIS_STATIC quicklistNode *_quicklistListpackMerge(quicklist *quicklist, - quicklistNode *a, - quicklistNode *b) { - D("Requested merge (a,b) (%u, %u)", a->count, b->count); - - quicklistDecompressNode(a); - quicklistDecompressNode(b); - if ((lpMerge(&a->entry, &b->entry))) { - /* We merged listpacks! Now remove the unused quicklistNode. */ - quicklistNode *keep = NULL, *nokeep = NULL; - if (!a->entry) { - nokeep = a; - keep = b; - } else if (!b->entry) { - nokeep = b; - keep = a; - } - keep->count = lpLength(keep->entry); - quicklistNodeUpdateSz(keep); - - nokeep->count = 0; - __quicklistDelNode(quicklist, nokeep); - quicklistCompress(quicklist, keep); - return keep; - } else { - /* else, the merge returned NULL and nothing changed. */ - return NULL; - } +/** + * quicklist_next - Find the next real node of @node, and store the + * partition to *@next_p, and store the node to *@next_node. If next node + * is not found, NULL is stored to *@next_p and *@next_node. + * @p: partition @node belongs to + */ +void quicklist_next(struct quicklist_partition *p, struct quicklist_node *node, + struct quicklist_partition **next_p, struct quicklist_node **next) +{ + if (node->next != p->guard) { + *next_p = p; + *next = node->next; + return; + } + + struct quicklist_partition *p_next = p->next; + while (!quicklist_p_is_head(p_next) && p_next->length == 0) + p_next = p_next->next; + if (quicklist_p_is_head(p_next)) { + *next_p = NULL; + *next = NULL; + } else { + *next_p = p_next; + *next = quicklist_p_first(p_next); + } } -/* Attempt to merge listpacks within two nodes on either side of 'center'. - * - * We attempt to merge: - * - (center->prev->prev, center->prev) - * - (center->next, center->next->next) - * - (center->prev, center) - * - (center, center->next) - */ -REDIS_STATIC void _quicklistMergeNodes(quicklist *quicklist, - quicklistNode *center) { - int fill = quicklist->fill; - quicklistNode *prev, *prev_prev, *next, *next_next, *target; - prev = prev_prev = next = next_next = target = NULL; - - if (center->prev) { - prev = center->prev; - if (center->prev->prev) - prev_prev = center->prev->prev; - } +/** + * quicklist_next_for_bookmark - Find the next real node of @node. + * @quicklist: the quicklist that @node belongs to + * + * Return: the next node, NULL if @node is the last node. + */ +struct quicklist_node *quicklist_next_for_bookmark(struct quicklist *quicklist, + struct quicklist_node *node) +{ + struct quicklist_partition *head = quicklist->head; + struct quicklist_partition *middle = head->next; + struct quicklist_partition *tail = middle->next; - if (center->next) { - next = center->next; - if (center->next->next) - next_next = center->next->next; - } + struct quicklist_partition *next_p; + struct quicklist_node *next; - /* Try to merge prev_prev and prev */ - if (_quicklistNodeAllowMerge(prev, prev_prev, fill)) { - _quicklistListpackMerge(quicklist, prev_prev, prev); - prev_prev = prev = NULL; /* they could have moved, invalidate them. */ - } + if (node->next == head->guard) { + quicklist_next(head, node, &next_p, &next); + return next; + } - /* Try to merge next and next_next */ - if (_quicklistNodeAllowMerge(next, next_next, fill)) { - _quicklistListpackMerge(quicklist, next, next_next); - next = next_next = NULL; /* they could have moved, invalidate them. */ - } + if (node->next == middle->guard) { + quicklist_next(middle, node, &next_p, &next); + return next; + } - /* Try to merge center node and previous node */ - if (_quicklistNodeAllowMerge(center, center->prev, fill)) { - target = _quicklistListpackMerge(quicklist, center->prev, center); - center = NULL; /* center could have been deleted, invalidate it. */ - } else { - /* else, we didn't merge here, but target needs to be valid below. */ - target = center; - } + if (node->next == tail->guard) + return NULL; - /* Use result of center merge (or original) to merge with next node. */ - if (_quicklistNodeAllowMerge(target, target->next, fill)) { - _quicklistListpackMerge(quicklist, target, target->next); - } + return node->next; } -/* Split 'node' into two parts, parameterized by 'offset' and 'after'. - * - * The 'after' argument controls which quicklistNode gets returned. - * If 'after'==1, returned node has elements after 'offset'. - * input node keeps elements up to 'offset', including 'offset'. - * If 'after'==0, returned node has elements up to 'offset'. - * input node keeps elements after 'offset', including 'offset'. - * - * Or in other words: - * If 'after'==1, returned node will have elements after 'offset'. - * The returned node will have elements [OFFSET+1, END]. - * The input node keeps elements [0, OFFSET]. - * If 'after'==0, returned node will keep elements up to but not including 'offset'. - * The returned node will have elements [0, OFFSET-1]. - * The input node keeps elements [OFFSET, END]. - * - * The input node keeps all elements not taken by the returned node. - * - * Returns newly created node or NULL if split not possible. */ -REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset, - int after) { - size_t zl_sz = node->sz; +/** + * quicklist_fix_compress - Keep @quicklist fit compress strategy. + */ +static void quicklist_fix_compress(struct quicklist *quicklist) { + struct quicklist_partition *head = quicklist->head; + struct quicklist_partition *middle = head->next; + struct quicklist_partition *tail = middle->next; - quicklistNode *new_node = quicklistCreateNode(); - new_node->entry = zmalloc(zl_sz); + while (!quicklist_p_is_full(head) && !quicklist_p_is_empty(middle)) + quicklist_p_move_backward(middle, head); - /* Copy original listpack so we can split it */ - memcpy(new_node->entry, node->entry, zl_sz); + while (!quicklist_p_is_full(head) && quicklist_p_is_overflow(tail)) + quicklist_p_move_backward(tail, head); - /* Need positive offset for calculating extent below. */ - if (offset < 0) offset = node->count + offset; + while (!quicklist_p_is_full(tail) && !quicklist_p_is_empty(middle)) + quicklist_p_move_forward(middle, tail); - /* Ranges to be trimmed: -1 here means "continue deleting until the list ends" */ - int orig_start = after ? offset + 1 : 0; - int orig_extent = after ? -1 : offset; - int new_start = after ? 0 : offset; - int new_extent = after ? offset + 1 : -1; + while (!quicklist_p_is_full(tail) && quicklist_p_is_overflow(head)) + quicklist_p_move_forward(head, tail); - D("After %d (%d); ranges: [%d, %d], [%d, %d]", after, offset, orig_start, - orig_extent, new_start, new_extent); + while (quicklist_p_is_overflow(head)) + quicklist_p_move_forward(head, middle); - node->entry = lpDeleteRange(node->entry, orig_start, orig_extent); - node->count = lpLength(node->entry); - quicklistNodeUpdateSz(node); + while (quicklist_p_is_overflow(tail)) + quicklist_p_move_backward(tail, middle); +} - new_node->entry = lpDeleteRange(new_node->entry, new_start, new_extent); - new_node->count = lpLength(new_node->entry); - quicklistNodeUpdateSz(new_node); +/** + * quicklist_p_add_node_head - Add @raw_node to @quicklist as @quicklist's head. + */ +static void quicklist_add_node_head(struct quicklist *quicklist, + struct quicklist_node *raw_node) +{ + struct quicklist_partition *head = quicklist->head; + struct quicklist_node *head_guard = head->guard; + quicklist_p_add_node(head, head_guard, head_guard->next, raw_node, 1); + quicklist_fix_compress(quicklist); + quicklist->count += raw_node->count; +} - D("After split lengths: orig (%d), new (%d)", node->count, new_node->count); - return new_node; +/** + * quicklist_p_add_node_tail - Add @raw_node to @quicklist as @quicklist's tail. + */ +static void quicklist_add_node_tail(struct quicklist *quicklist, + struct quicklist_node *raw_node) +{ + struct quicklist_partition *tail = quicklist->head->prev; + struct quicklist_node *tail_guard = tail->guard; + quicklist_p_add_node(tail, tail_guard->prev, tail_guard, raw_node, 1); + quicklist_fix_compress(quicklist); + quicklist->count += raw_node->count; } -/* Insert a new entry before or after existing entry 'entry'. - * - * If after==1, the new value is inserted after 'entry', otherwise - * the new value is inserted before 'entry'. */ -REDIS_STATIC void _quicklistInsert(quicklistIter *iter, quicklistEntry *entry, - void *value, const size_t sz, int after) -{ - quicklist *quicklist = iter->quicklist; - int full = 0, at_tail = 0, at_head = 0, avail_next = 0, avail_prev = 0; - int fill = quicklist->fill; - quicklistNode *node = entry->node; - quicklistNode *new_node = NULL; - - if (!node) { - /* we have no reference node, so let's create only node in the list */ - D("No node given!"); - if (unlikely(isLargeElement(sz))) { - __quicklistInsertPlainNode(quicklist, quicklist->tail, value, sz, after); - return; - } - new_node = quicklistCreateNode(); - new_node->entry = lpPrepend(lpNew(0), value, sz); - __quicklistInsertNode(quicklist, NULL, new_node, after); - new_node->count++; - quicklist->count++; - return; - } +/** + * quicklist_push_head - Push a new element with value @value and + * size @sz as @quicklist's head. + */ +void quicklist_push_head(struct quicklist *quicklist, void *value, size_t sz) +{ + struct quicklist_fill *fill = quicklist->fill; + + struct quicklist_partition *first_p; + struct quicklist_node *first; + quicklist_first_node(quicklist, &first_p, &first); + if (first && quicklist_n_try_add_carry_head(fill, first, 0, value, sz)) { + quicklist->count++; + return; + } + + struct quicklist_node *new_raw = quicklist_n_new_raw(fill, value, sz); + quicklist_add_node_head(quicklist, new_raw); +} - /* Populate accounting flags for easier boolean checks later */ - if (!_quicklistNodeAllowInsert(node, fill, sz)) { - D("Current node is full with count %d with requested fill %d", - node->count, fill); - full = 1; - } +/** + * quicklist_push_tail - Push a new element with value @value and + * size @sz as @quicklist's tail. + */ +void quicklist_push_tail(struct quicklist *quicklist, void *value, size_t sz) +{ + struct quicklist_fill *fill = quicklist->fill; + + struct quicklist_partition *last_p; + struct quicklist_node *last; + quicklist_last_node(quicklist, &last_p, &last); + if (last && quicklist_n_try_add_carry_tail(fill, last, 0, value, sz)) { + quicklist->count++; + return; + } + struct quicklist_node *new_raw = quicklist_n_new_raw(fill, value, sz); + quicklist_add_node_tail(quicklist, new_raw); +} - if (after && (entry->offset == node->count - 1 || entry->offset == -1)) { - D("At Tail of current listpack"); - at_tail = 1; - if (_quicklistNodeAllowInsert(node->next, fill, sz)) { - D("Next node is available."); - avail_next = 1; - } - } +/** + * __quicklist_fix_compress - Keep @quicklist fit compress strategy, and + * monitor @node's partition. + * @p: partation that @node belongs to before fix. + * + * Return: @node's partition after fix. + * + * Note: if @node is raw, @node will kept raw, otherwise keeps @node following + * partition compress strategy. + */ +static struct quicklist_partition *__quicklist_fix_compress( + struct quicklist *quicklist, + struct quicklist_partition *p, + struct quicklist_node *node) +{ + struct quicklist_partition *head = quicklist->head; + struct quicklist_partition *middle = head->next; + struct quicklist_partition *tail = middle->next; - if (!after && (entry->offset == 0 || entry->offset == -(node->count))) { - D("At Head"); - at_head = 1; - if (_quicklistNodeAllowInsert(node->prev, fill, sz)) { - D("Prev node is available."); - avail_prev = 1; - } - } + while (!quicklist_p_is_full(head) && !quicklist_p_is_empty(middle)) + p = __quicklist_p_move_backward(middle, head, p, node); - if (unlikely(isLargeElement(sz))) { - if (QL_NODE_IS_PLAIN(node) || (at_tail && after) || (at_head && !after)) { - __quicklistInsertPlainNode(quicklist, node, value, sz, after); - } else { - quicklistDecompressNodeForUse(node); - new_node = _quicklistSplitNode(node, entry->offset, after); - quicklistNode *entry_node = __quicklistCreatePlainNode(value, sz); - __quicklistInsertNode(quicklist, node, entry_node, after); - __quicklistInsertNode(quicklist, entry_node, new_node, after); - quicklist->count++; - } - return; - } + while (!quicklist_p_is_full(head) && quicklist_p_is_overflow(tail)) + p = __quicklist_p_move_backward(tail, head, p, node); - /* Now determine where and how to insert the new element */ - if (!full && after) { - D("Not full, inserting after current position."); - quicklistDecompressNodeForUse(node); - node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_AFTER, NULL); - node->count++; - quicklistNodeUpdateSz(node); - quicklistRecompressOnly(node); - } else if (!full && !after) { - D("Not full, inserting before current position."); - quicklistDecompressNodeForUse(node); - node->entry = lpInsertString(node->entry, value, sz, entry->zi, LP_BEFORE, NULL); - node->count++; - quicklistNodeUpdateSz(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 = lpPrepend(new_node->entry, value, sz); - new_node->count++; - quicklistNodeUpdateSz(new_node); - quicklistRecompressOnly(new_node); - quicklistRecompressOnly(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 = lpAppend(new_node->entry, value, sz); - new_node->count++; - quicklistNodeUpdateSz(new_node); - quicklistRecompressOnly(new_node); - quicklistRecompressOnly(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 = lpPrepend(lpNew(0), value, sz); - new_node->count++; - quicklistNodeUpdateSz(new_node); - __quicklistInsertNode(quicklist, node, new_node, after); - } else if (full) { - /* else, node is full we need to split it. */ - /* covers both after and !after cases */ - D("\tsplitting node..."); - quicklistDecompressNodeForUse(node); - new_node = _quicklistSplitNode(node, entry->offset, after); - if (after) - new_node->entry = lpPrepend(new_node->entry, value, sz); - else - new_node->entry = lpAppend(new_node->entry, value, sz); - new_node->count++; - quicklistNodeUpdateSz(new_node); - __quicklistInsertNode(quicklist, node, new_node, after); - _quicklistMergeNodes(quicklist, node); - } + while (!quicklist_p_is_full(tail) && !quicklist_p_is_empty(middle)) + p = __quicklist_p_move_forward(middle, tail, p, node); + + while (!quicklist_p_is_full(tail) && quicklist_p_is_overflow(head)) + p = __quicklist_p_move_forward(head, tail, p, node); - quicklist->count++; + while (quicklist_p_is_overflow(head)) + p = __quicklist_p_move_forward(head, middle, p, node); - /* In any case, we reset iterator to forbid use of iterator after insert. - * Notice: iter->current has been compressed in _quicklistInsert(). */ - resetIterator(iter); + while (quicklist_p_is_overflow(tail)) + p = __quicklist_p_move_backward(tail, middle, p, node); + + return p; } -void quicklistInsertBefore(quicklistIter *iter, quicklistEntry *entry, - void *value, const size_t sz) +/** + * __quicklist_merge - Merge node @prev and @next. + * @prev_p: partition @prev belongs to + * @next_p: partition @next belongs to + * @forward: 1 for merged @prev to @next as @next's head, @prev will be deleted; + * 0 for merged @next to @prev as @prev's tail, @next will be deleted. + * @keep_raw: 1 for keep the remain node raw, 0 for following partition + * compress strategy. + */ +static void __quicklist_merge(struct quicklist *quicklist, + struct quicklist_partition *prev_p, struct quicklist_node *prev, + struct quicklist_partition *next_p, struct quicklist_node *next, + int forward, int keep_raw) { - _quicklistInsert(iter, entry, value, sz, 0); + struct quicklist_partition *remain_p, *removed_p; + struct quicklist_node *remain, *removed; + if (forward) { + remain_p = next_p; + remain = next; + removed_p = prev_p; + removed = prev; + } else { + remain_p = prev_p; + remain = prev; + removed_p = next_p; + removed = next; + } + + quicklist_n_decompress(prev); + quicklist_n_decompress(next); + + remain->carry = lpMerge((unsigned char **)&prev->carry, + (unsigned char **)&next->carry); + removed->carry = NULL; + + remain->raw_sz = lpBytes(remain->carry); + remain->count += removed->count; + + if (!keep_raw && quicklist_p_is_middle(remain_p)) + quicklist_n_compress_raw(remain); + + quicklist_bm_replace(quicklist, removed, remain); + __quicklist_p_del_node(removed_p, removed); } -void quicklistInsertAfter(quicklistIter *iter, quicklistEntry *entry, - void *value, const size_t sz) +/** + * __quicklist_try_merge - Try to merge @prev and @next. + * @prev_p: partition @prev belongs to + * @next_p: partition @next belongs to + * @forward: 1 for try to merged @prev to @next as @next's head, @prev will + * be deleted after merge; 0 for try to merged @next to @prev as @prev's + * tail, @next will be freed after merge. + * @keep_raw: 1 for keep the remain node raw, 0 for following partition + * compress strategy. + * + * Note: we will not try to decompress @prev nor @next if merge is not performed. + */ +static int __quicklist_try_merge(struct quicklist *quicklist, + struct quicklist_partition *prev_p, struct quicklist_node *prev, + struct quicklist_partition *next_p, struct quicklist_node *next, + int forward, int keep_raw) { - _quicklistInsert(iter, entry, value, sz, 1); + if (!quicklist_n_allow_merge(quicklist->fill, prev, next)) + return 0; + + __quicklist_merge(quicklist, prev_p, prev, next_p, next, forward, keep_raw); + return 1; } -/* Delete a range of elements from the quicklist. - * - * elements may span across multiple quicklistNodes, so we - * have to be careful about tracking where we start and end. - * - * Returns 1 if entries were deleted, 0 if nothing was deleted. */ -int quicklistDelRange(quicklist *quicklist, const long start, - const long count) { - if (count <= 0) - return 0; - - unsigned long extent = count; /* range is inclusive of start position */ - - if (start >= 0 && extent > (quicklist->count - start)) { - /* if requesting delete more elements than exist, limit to list size. */ - extent = quicklist->count - start; - } else if (start < 0 && extent > (unsigned long)(-start)) { - /* else, if at negative offset, limit max size to rest of list. */ - extent = -start; /* c.f. LREM -29 29; just delete until end. */ - } +/** + * __quicklist_try_merge_prev_raw - Try to merge *@node with its previous node. + * @p: *@p is the partition @node belongs to, and *@p will change to the remain + * node's partition after merge. + * @node: *@node will change to the remain node after merge. + * + * Note: caller should make sure *@node is a raw node, and *@node is guarantee + * a raw node after the operation. + */ +static void __quicklist_try_merge_prev_raw(struct quicklist *quicklist, + struct quicklist_partition **p, + struct quicklist_node **node) +{ + struct quicklist_partition *prev_p; + struct quicklist_node *prev; + quicklist_prev(*p, *node, &prev_p, &prev); + if (!prev) + return; + + if (quicklist_p_is_middle(prev_p)) { + __quicklist_try_merge(quicklist, prev_p, prev, *p, *node, 1, 1); + } else if (__quicklist_try_merge(quicklist, prev_p, prev, *p, *node, 0, 1)){ + *p = prev_p; + *node = prev; + } +} - quicklistIter *iter = quicklistGetIteratorAtIdx(quicklist, AL_START_TAIL, start); - if (!iter) - return 0; - - D("Quicklist delete request for start %ld, count %ld, extent: %ld", start, - count, extent); - quicklistNode *node = iter->current; - long offset = iter->offset; - quicklistReleaseIterator(iter); - - /* iterate over next nodes until everything is deleted. */ - while (extent) { - quicklistNode *next = node->next; - - unsigned long del; - int delete_entire_node = 0; - if (offset == 0 && extent >= node->count) { - /* If we are deleting more than the count of this node, we - * can just delete the entire node without listpack math. */ - delete_entire_node = 1; - del = node->count; - } else if (offset >= 0 && extent + offset >= node->count) { - /* If deleting more nodes after this one, calculate delete based - * on size of current node. */ - del = node->count - offset; - } else if (offset < 0) { - /* If offset is negative, we are in the first run of this loop - * and we are deleting the entire range - * from this start offset to end of list. Since the Negative - * offset is the number of elements until the tail of the list, - * just use it directly as the deletion count. */ - del = -offset; - - /* If the positive offset is greater than the remaining extent, - * we only delete the remaining extent, not the entire offset. - */ - if (del > extent) - del = extent; - } else { - /* else, we are deleting less than the extent of this node, so - * use extent directly. */ - del = extent; - } +/** + * __quicklist_try_merge_prev - Try to merge @node with its previous node. + * @p: partition @node belongs to + * + * Note: if compress performed, the remain node will following partition + * compress strategy. Otherwise, nothing happens. + */ +static void __quicklist_try_merge_prev(struct quicklist *quicklist, + struct quicklist_partition *p, + struct quicklist_node *node) +{ + struct quicklist_partition *prev_p; + struct quicklist_node *prev; + quicklist_prev(p, node, &prev_p, &prev); + if (!prev) + return; + + int forward = quicklist_p_is_middle(prev_p); + __quicklist_try_merge(quicklist, prev_p, prev, p, node, forward, 0); +} - D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), " - "node count: %u", - extent, del, offset, delete_entire_node, node->count); - - if (delete_entire_node || QL_NODE_IS_PLAIN(node)) { - __quicklistDelNode(quicklist, node); - } else { - quicklistDecompressNodeForUse(node); - node->entry = lpDeleteRange(node->entry, offset, del); - quicklistNodeUpdateSz(node); - node->count -= del; - quicklist->count -= del; - quicklistDeleteIfEmpty(quicklist, node); - if (node) - quicklistRecompressOnly(node); - } +/** + * __quicklist_try_merge_next_raw - Try to merge *@node with its next node. + * @p: *@p is the partition @node belongs to, and *@p will change to the remain + * node's partition after merge. + * @node: *@node will change to the remain node. + * + * Note: caller should make sure *@node is a raw node, and *@node is guarantee + * a raw node after the operation. + */ +static void __quicklist_try_merge_next_raw(struct quicklist *quicklist, + struct quicklist_partition **p, + struct quicklist_node **node) +{ + struct quicklist_partition *next_p; + struct quicklist_node *next; + quicklist_next(*p, *node, &next_p, &next); + if (!next) + return; + + if (quicklist_p_is_middle(next_p)) { + __quicklist_try_merge(quicklist, *p, *node, next_p, next, 0, 1); + } else if (__quicklist_try_merge(quicklist, *p, *node, next_p, next, 1, 1)){ + *p = next_p; + *node = next; + } +} - extent -= del; +/** + * __quicklist_try_merge_next - Try to merge @node with its next node. + * @p: partition @node belongs to + * + * Note: if compress performed, the remain node will following partition + * compress strategy. Otherwise, nothing happens. + */ +static void __quicklist_try_merge_next(struct quicklist *quicklist, + struct quicklist_partition *p, + struct quicklist_node *node) +{ + struct quicklist_partition *next_p; + struct quicklist_node *next; + quicklist_next(p, node, &next_p, &next); + if (!next) + return; + + int forward = !quicklist_p_is_middle(next_p); + __quicklist_try_merge(quicklist, p, node, next_p, next, forward, 0); +} - node = next; +/** + * __quicklist_fix_merge_1 - Fix merge after delete some elements from @node. + * @p: partition @node belongs to + * + * Note: caller should make sure @node is a raw node, and @node will following + * partition compress strategy after operation. + */ +static void __quicklist_fix_merge_1(struct quicklist *quicklist, + struct quicklist_partition *p, + struct quicklist_node *node) +{ + __quicklist_try_merge_prev_raw(quicklist, &p, &node); + __quicklist_try_merge_next_raw(quicklist, &p, &node); + if (quicklist_p_is_middle(p)) + quicklist_n_compress_raw(node); +} - offset = 0; - } - return 1; +/** + * __quicklist_fix_merge_2 - Fix merge after delete some elements + * from @prev and @next. + * @prev_p: partition @prev belongs to. + * @next_p: partition @next belongs to. + * + * Note: caller should make sure @prev and @next are raw node, @prev and @next + * will following partition compress strategy after operation. + */ +static void __quicklist_fix_merge_2(struct quicklist *quicklist, + struct quicklist_partition *prev_p, + struct quicklist_node *prev, + struct quicklist_partition *next_p, + struct quicklist_node *next) +{ + __quicklist_try_merge_prev_raw(quicklist, &prev_p, &prev); + __quicklist_try_merge_next_raw(quicklist, &next_p, &next); + int forward = quicklist_p_is_middle(prev_p); + if (__quicklist_try_merge(quicklist, prev_p, prev, next_p, next, forward, 0)) + return; + if (quicklist_p_is_middle(prev_p)) + quicklist_n_compress_raw(prev); + if (quicklist_p_is_middle(next_p)) + quicklist_n_compress_raw(next); } -/* compare between a two entries */ -int quicklistCompare(quicklistEntry* entry, unsigned char *p2, const size_t p2_len) { - if (unlikely(QL_NODE_IS_PLAIN(entry->node))) { - return ((entry->sz == p2_len) && (memcmp(entry->value, p2, p2_len) == 0)); - } - return lpCompare(entry->zi, p2, p2_len); +/** + * quicklist_del_forward - Delete exactly @n elements from @quicklist start + * from @from and working towards to tail. + * + * Note: caller should make sure @from and @n is valid. + */ +static void quicklist_del_forward(struct quicklist *quicklist, long from, long n) +{ + long deleted = n; + + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(quicklist, &p, &node); + + while (from >= node->count) { + from -= node->count; + quicklist_next(p, node, &p, &node); + } + + struct quicklist_partition *first_p = NULL; + struct quicklist_node *first = NULL; + if (from != 0) { + first_p = p; + first = node; + n -= quicklist_n_del_element_forward(node, 0, from, n); + quicklist_next(p, node, &p, &node); + } + + struct quicklist_partition *last_p = NULL; + struct quicklist_node *last = NULL; + while (n > 0) { + if (n < node->count) { + last_p = p; + last = node; + quicklist_n_del_element(node, 0, 0, n); + break; + } + + struct quicklist_partition *next_p; + struct quicklist_node *next; + quicklist_next(p, node, &next_p, &next); + + n -= node->count; + quicklist_bm_move_next(quicklist, node); + __quicklist_p_del_node(p, node); + + p = next_p; + node = next; + } + + if (first && last) + __quicklist_fix_merge_2(quicklist, first_p, first, last_p, last); + else if (first) + __quicklist_fix_merge_1(quicklist, first_p, first); + else if (last) + __quicklist_fix_merge_1(quicklist, last_p, last); + else if (node) + __quicklist_try_merge_prev(quicklist, p, node); + + quicklist_fix_compress(quicklist); + + quicklist->count -= deleted; } -/* Returns a quicklist iterator 'iter'. After the initialization every - * call to quicklistNext() will return the next element of the quicklist. */ -quicklistIter *quicklistGetIterator(quicklist *quicklist, int direction) { - quicklistIter *iter; +/** + * quicklist_del_backward - Delete exactly @n elements from @quicklist start + * from @from and working towards to head. + * + * Note: caller should make sure @from and @n is valid. + */ +static void quicklist_del_backward(struct quicklist *quicklist, long from, long n) +{ + long deleted = n; + + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_last_node(quicklist, &p, &node); + + long i = quicklist->count - node->count; + while (i > from) { + quicklist_prev(p, node, &p, &node); + i -= node->count; + } + + struct quicklist_partition *last_p = NULL; + struct quicklist_node *last = NULL; + if (from-i != node->count-1) { + last_p = p; + last = node; + n -= quicklist_n_del_element_backward(node, 0, from-i, n); + quicklist_prev(p, node, &p, &node); + } + + struct quicklist_partition *first_p = NULL; + struct quicklist_node *first = NULL; + while (n > 0) { + if (n < node->count) { + first_p = p; + first = node; + quicklist_n_del_element(node, 0, node->count-n, n); + break; + } + + struct quicklist_partition *prev_p; + struct quicklist_node *prev; + quicklist_prev(p, node, &prev_p, &prev); + + n -= node->count; + quicklist_bm_move_next(quicklist, node); + __quicklist_p_del_node(p, node); + + p = prev_p; + node = prev; + } + + if (first && last) + __quicklist_fix_merge_2(quicklist, first_p, first, last_p, last); + else if (first) + __quicklist_fix_merge_1(quicklist, first_p, first); + else if (last) + __quicklist_fix_merge_1(quicklist, last_p, last); + else if (node) + __quicklist_try_merge_next(quicklist, p, node); + + quicklist_fix_compress(quicklist); + + quicklist->count -= deleted; +} - iter = zmalloc(sizeof(*iter)); +/** + * quicklist_del - Delete at most @n elements from @quicklist start from @from. + * + * Return: number of elements is deleted. + * + * Note: negative @from is acceptable, see quicklist_transform_index(). + * Note: caller should make sure @n is valid. + */ +long quicklist_del(struct quicklist *quicklist, long from, long n) +{ + assert(n > 0); - if (direction == AL_START_HEAD) { - iter->current = quicklist->head; - iter->offset = 0; - } else if (direction == AL_START_TAIL) { - iter->current = quicklist->tail; - iter->offset = -1; - } + from = quicklist_transform_index(quicklist->count, from); + if (from < 0) + return 0; - iter->direction = direction; - iter->quicklist = quicklist; + long deletable = quicklist->count - from; - iter->zi = NULL; + if (n >= deletable) + n = deletable; - return iter; + if (from <= deletable - n) + quicklist_del_forward(quicklist, from, n); + else + quicklist_del_backward(quicklist, from + n - 1, n); + + return n; } -/* Initialize an iterator at a specific offset 'idx' and make the iterator - * return nodes in 'direction' direction. */ -quicklistIter *quicklistGetIteratorAtIdx(quicklist *quicklist, - const int direction, - const long long idx) +/** + * quicklist_append_plain - Add a plain node with value @value and size @sz + * as @quicklist's tail. + * + * Note: this function takes over ownership of @value from the caller. + */ +void quicklist_append_plain(struct quicklist *quicklist, void *value, size_t sz) { - quicklistNode *n; - unsigned long long accum = 0; - unsigned long long index; - int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */ - - index = forward ? idx : (-idx) - 1; - if (index >= quicklist->count) - return NULL; - - /* Seek in the other direction if that way is shorter. */ - int seek_forward = forward; - unsigned long long seek_index = index; - if (index > (quicklist->count - 1) / 2) { - seek_forward = !forward; - seek_index = quicklist->count - 1 - index; - } - - n = seek_forward ? quicklist->head : quicklist->tail; - while (likely(n)) { - if ((accum + n->count) > seek_index) { - break; - } else { - D("Skipping over (%p) %u at accum %lld", (void *)n, n->count, - accum); - accum += n->count; - n = seek_forward ? n->next : n->prev; - } - } - - if (!n) - return NULL; + struct quicklist_node *node = zmalloc(sizeof(*node)); + node->carry = value; + node->raw_sz = sz; + node->container = QUICKLIST_NODE_CONTAINER_PLAIN; + node->count = 1; + node->raw = 1; + quicklist_add_node_tail(quicklist, node); +} - /* Fix accum so it looks like we seeked in the other direction. */ - if (seek_forward != forward) accum = quicklist->count - n->count - accum; +/** + * quicklist_append_listpack - Add a packed node with listpack @lp + * as @quicklist's tail. + * + * Note: this function takes over ownership of @lp from the caller. + */ +void quicklist_append_listpack(struct quicklist *quicklist, unsigned char *lp) +{ + struct quicklist_node *node = zmalloc(sizeof(*node)); + node->carry = lp; + node->raw_sz = lpBytes(lp); + node->container = QUICKLIST_NODE_CONTAINER_PACKED; + node->count = lpLength(lp); + node->raw = 1; + quicklist_add_node_tail(quicklist, node); +} - D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n, - accum, index, index - accum, (-index) - 1 + accum); +/** + * quicklist_iter_get_element - Get the element that @iter point to. + * @elem: the element is stored to *@elem + * + * Note: caller should make sure @iter is normal. + */ +void quicklist_iter_get_element(struct quicklist_iter *iter, + struct quicklist_element *elem) +{ + assert(iter->status == QUICKLIST_ITER_STATUS_NORMAL); + + struct quicklist_node *node = iter->raw_node; + if (node->container == QUICKLIST_NODE_CONTAINER_PLAIN) { + elem->value = node->carry; + elem->sz = node->raw_sz; + } else { + unsigned int sz = 0; + elem->value = lpGetValue(iter->lp_element, &sz, &elem->longval); + elem->sz = sz; + } +} - quicklistIter *iter = quicklistGetIterator(quicklist, direction); - iter->current = n; - if (forward) { - /* forward = normal head-to-tail offset. */ - iter->offset = index - accum; - } else { - /* reverse = need negative offset for tail-to-head, so undo - * the result of the original index = (-idx) - 1 above. */ - iter->offset = (-index) - 1 + accum; - } +/** + * quicklist_iter_set - Set @iter point to the @ith element of @node. + * @p: partition @node belongs to + * + * Note: this function does nothing to the element that iter previous point to. + * Note: caller should make sure @i is valid. + */ +static void quicklist_iter_set(struct quicklist_iter *iter, + struct quicklist_partition *p, + struct quicklist_node *node, long i) +{ + iter->p = p; + quicklist_n_decompress(node); + iter->raw_node = node; + iter->offset = i; + if (node->container == QUICKLIST_NODE_CONTAINER_PACKED) + iter->lp_element = lpSeek(node->carry, i); +} - return iter; +/** + * quicklist_iter_set_node_first - Set @iter point to the first element of @node. + * @p: partition @node belongs to + * + * Note: this function does nothing to the element that iter previous point to. + */ +static void quicklist_iter_set_node_first(struct quicklist_iter *iter, + struct quicklist_partition *p, struct quicklist_node *node) +{ + quicklist_iter_set(iter, p, node, 0); } -/* Release iterator. - * If we still have a valid current node, then re-encode current node. */ -void quicklistReleaseIterator(quicklistIter *iter) { - if (!iter) return; - if (iter->current) - quicklistCompress(iter->quicklist, iter->current); +/** + * quicklist_iter_set_node_last - Set @iter point to the last element of @node. + * @p: partition @node belongs to + * + * Note: this function does nothing to the element that iter previous point to. + */ +static void quicklist_iter_set_node_last(struct quicklist_iter *iter, + struct quicklist_partition *p, struct quicklist_node *node) +{ + quicklist_iter_set(iter, p, node, node->count-1); +} - zfree(iter); +/** + * quicklist_iter_new_from_head - Create a new quicklist_iter for @quicklist, + * search target element from head towards to tail. + * @i: index of the element that the iterator point to + * @forward: 1 for iterate forward, 0 for iterate backward + * + * Note: caller should make sure @ith element is exist. + */ +static struct quicklist_iter *quicklist_iter_new_from_head( + struct quicklist *quicklist, long i, int forward) +{ + struct quicklist_iter *iter = zmalloc(sizeof(*iter)); + iter->status = QUICKLIST_ITER_STATUS_NORMAL; + iter->forward = forward; + iter->quicklist = quicklist; + + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(iter->quicklist, &p, &node); + + while (i >= node->count) { + i -= node->count; + quicklist_next(p, node, &p, &node); + } + + quicklist_iter_set(iter, p, node, i); + return iter; } -/* Get next element in iterator. - * - * Note: You must NOT insert into the list while iterating over it. - * You *may* delete from the list while iterating using the - * quicklistDelEntry() function. - * If you insert into the quicklist while iterating, you should - * re-create the iterator after your addition. - * - * iter = quicklistGetIterator(quicklist,); - * quicklistEntry entry; - * while (quicklistNext(iter, &entry)) { - * if (entry.value) - * [[ use entry.value with entry.sz ]] - * else - * [[ use entry.longval ]] - * } - * - * Populates 'entry' with values for this iteration. - * Returns 0 when iteration is complete or if iteration not possible. - * If return value is 0, the contents of 'entry' are not valid. +/** + * quicklist_iter_new_from_tail - Create a new quicklist_iter for @quicklist, + * search target element from tail towards to head. + * @i: index of the element that the iterator point to + * @forward: 1 for iterate forward, 0 for iterate backward + * + * Note: caller should make sure @ith element is exist. */ -int quicklistNext(quicklistIter *iter, quicklistEntry *entry) { - initEntry(entry); +static struct quicklist_iter *quicklist_iter_new_from_tail( + struct quicklist *quicklist, long i, int forward) +{ + struct quicklist_iter *iter = zmalloc(sizeof(*iter)); + iter->status = QUICKLIST_ITER_STATUS_NORMAL; + iter->forward = forward; + iter->quicklist = quicklist; + + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_last_node(quicklist, &p, &node); + + long j = quicklist->count - node->count; + while (j > i) { + quicklist_prev(p, node, &p, &node); + j -= node->count; + } + + quicklist_iter_set(iter, p, node, i-j); + return iter; +} - if (!iter) { - D("Returning because no iter!"); - return 0; - } +/** + * quicklist_iter_new - Create a new quicklist_iter for @quicklist. + * @i: index of the element that the iterator point to + * @forward: 1 for iterate forward, 0 for iterate backward + * + * Return: created quicklist_iter, NULL if @ith element is exist. + * + * Note: free function is quicklist_iter_free(). + * Note: negative @i is acceptable, see quicklist_transform_index(). + */ +struct quicklist_iter *quicklist_iter_new(struct quicklist *quicklist, + long i, int forward) +{ + i = quicklist_transform_index(quicklist->count, i); + if (i < 0) + return NULL; + + if (i < quicklist->count/2) + return quicklist_iter_new_from_head(quicklist, i, forward); + + return quicklist_iter_new_from_tail(quicklist, i, forward); +} - entry->quicklist = iter->quicklist; - entry->node = iter->current; +/** + * quicklist_iter_new_ahead - Create a new ahead quicklist_iter for @quicklist. + * @i: index of the element that the iterator point to + * @forward: 1 for iterate forward, 0 for iterate backward + * + * Return: created quicklist_iter, NULL if @ith element is exist. + * + * Note: free function is quicklist_iter_free(). + * Note: negative @i is acceptable, see quicklist_transform_index(). + * Note: returned quicklist_iter is a ahead iterator, is not valid until + * quicklist_iter_next() is called. See QUICKLIST_ITER_STATUS_AHEAD. + */ +struct quicklist_iter *quicklist_iter_new_ahead(struct quicklist *quicklist, + long i, int forward) +{ + /* skip quicklist_transform_index() */ + struct quicklist_iter *iter = quicklist_iter_new(quicklist, i, forward); + if (iter != NULL) + iter->status = QUICKLIST_ITER_STATUS_AHEAD; + return iter; +} - if (!iter->current) { - D("Returning because current node is NULL"); - return 0; - } +/** + * quicklist_iter_free - Deallocates the space related to @iter. + */ +void quicklist_iter_free(struct quicklist_iter *iter) +{ + if (iter->status != QUICKLIST_ITER_STATUS_COMPLETE && quicklist_p_is_middle(iter->p)) + quicklist_n_compress_raw(iter->raw_node); + + zfree(iter); +} - unsigned char *(*nextFn)(unsigned char *, unsigned char *) = NULL; - int offset_update = 0; - - int plain = QL_NODE_IS_PLAIN(iter->current); - if (!iter->zi) { - /* If !zi, use current index. */ - quicklistDecompressNodeForUse(iter->current); - if (unlikely(plain)) - iter->zi = iter->current->entry; - else - iter->zi = lpSeek(iter->current->entry, iter->offset); - } else if (unlikely(plain)) { - iter->zi = NULL; - } else { - /* else, use existing iterator offset and get prev/next as necessary. */ - if (iter->direction == AL_START_HEAD) { - nextFn = lpNext; - offset_update = 1; - } else if (iter->direction == AL_START_TAIL) { - nextFn = lpPrev; - offset_update = -1; - } - iter->zi = nextFn(iter->current->entry, iter->zi); - iter->offset += offset_update; - } +/** + * quicklist_iter_debug_print - Print @iter's information for debug. + */ +void quicklist_iter_debug_print(struct quicklist_iter *iter) +{ + printf("{iter}\n"); + printf("{status: %d}\n", iter->status); + printf("{forward: %d}\n", iter->forward); + printf("{p: %d}\n", iter->p->which); + printf("{offset: %d}\n", iter->offset); +} - entry->zi = iter->zi; - entry->offset = iter->offset; +/** + * quicklist_iter_move_to - Move @iter to @node and point to the + * first or last element base on @iter->forward. + * @p: partition @node belongs to + */ +static void quicklist_iter_move_to(struct quicklist_iter *iter, + struct quicklist_partition *p, struct quicklist_node *node) +{ + if (quicklist_p_is_middle(iter->p)) + quicklist_n_compress_raw(iter->raw_node); - if (iter->zi) { - if (unlikely(plain)) { - entry->value = entry->node->entry; - entry->sz = entry->node->sz; - return 1; - } - /* Populate value from existing listpack position */ - unsigned int sz = 0; - entry->value = lpGetValue(entry->zi, &sz, &entry->longval); - entry->sz = sz; - return 1; - } else { - /* We ran out of listpack entries. - * Pick next node, update offset, then re-run retrieval. */ - quicklistCompress(iter->quicklist, iter->current); - if (iter->direction == AL_START_HEAD) { - /* Forward traversal */ - D("Jumping to start of next node"); - iter->current = iter->current->next; - iter->offset = 0; - } else if (iter->direction == AL_START_TAIL) { - /* Reverse traversal */ - D("Jumping to end of previous node"); - iter->current = iter->current->prev; - iter->offset = -1; - } - iter->zi = NULL; - return quicklistNext(iter, entry); - } + if (iter->forward) + quicklist_iter_set_node_first(iter, p, node); + else + quicklist_iter_set_node_last(iter, p, node); } -/* Sets the direction of a quicklist iterator. */ -void quicklistSetDirection(quicklistIter *iter, int direction) { - iter->direction = direction; +/** + * quicklist_iter_pack_next_forward - Move @iter to next element of + * node @iter->raw_node working towards to tail. + * + * Note: caller should make sure next element is exist. + */ +static void quicklist_iter_pack_next_forward(struct quicklist_iter *iter) +{ + iter->offset++; + iter->lp_element = lpNext(iter->raw_node->carry, iter->lp_element); } -/* Duplicate the quicklist. - * On success a copy of the original quicklist is returned. - * - * The original quicklist both on success or error is never modified. - * - * Returns newly allocated quicklist. */ -quicklist *quicklistDup(quicklist *orig) { - quicklist *copy; - - copy = quicklistNew(orig->fill, orig->compress); - - for (quicklistNode *current = orig->head; current; - current = current->next) { - quicklistNode *node = quicklistCreateNode(); - - if (current->encoding == QUICKLIST_NODE_ENCODING_LZF) { - quicklistLZF *lzf = (quicklistLZF *)current->entry; - size_t lzf_sz = sizeof(*lzf) + lzf->sz; - node->entry = zmalloc(lzf_sz); - memcpy(node->entry, current->entry, lzf_sz); - } else if (current->encoding == QUICKLIST_NODE_ENCODING_RAW) { - node->entry = zmalloc(current->sz); - memcpy(node->entry, current->entry, current->sz); - } +/** + * quicklist_iter_pack_next_backward - Move @iter to next element of + * node @iter->raw_node working towards to head. + * + * Note: caller should make sure next element is exist. + */ +static void quicklist_iter_pack_next_backward(struct quicklist_iter *iter) +{ + iter->offset--; + iter->lp_element = lpPrev(iter->raw_node->carry, iter->lp_element); +} - node->count = current->count; - copy->count += node->count; - node->sz = current->sz; - node->encoding = current->encoding; - node->container = current->container; +/** + * quicklist_iter_complete - Complete @iter and disable it for further use. + */ +static void quicklist_iter_complete(struct quicklist_iter *iter) +{ + if (quicklist_p_is_middle(iter->p)) + quicklist_n_compress_raw(iter->raw_node); + iter->status = QUICKLIST_ITER_STATUS_COMPLETE; +} - _quicklistInsertNodeAfter(copy, copy->tail, node); - } +/** + * __quicklist_iter_next_forward - Move @iter to next element working + * towards to tail. + * + * Return: 1 if next element exists, 0 if not. + * + * Note: @iter is not available for further use if 0 is returned. + */ +static int __quicklist_iter_next_forward(struct quicklist_iter *iter) +{ + if (iter->offset+1 < iter->raw_node->count) { + quicklist_iter_pack_next_forward(iter); + return 1; + } + + struct quicklist_partition *next_p; + struct quicklist_node *next_node; + quicklist_next(iter->p, iter->raw_node, &next_p, &next_node); + if (next_node == NULL) { + quicklist_iter_complete(iter); + return 0; + } + quicklist_iter_move_to(iter, next_p, next_node); + return 1; +} - /* copy->count must equal orig->count here */ - return copy; +/** + * __quicklist_iter_next_backward - Move @iter to next element working + * towards to head. + * + * Return: 1 if next element exists, 0 if not. + * + * Note: @iter is not available for further use if 0 is returned. + */ +static int __quicklist_iter_next_backward(struct quicklist_iter *iter) +{ + if (iter->offset > 0) { + quicklist_iter_pack_next_backward(iter); + return 1; + } + + struct quicklist_partition *prev_p; + struct quicklist_node *prev_node; + quicklist_prev(iter->p, iter->raw_node, &prev_p, &prev_node); + if (prev_node == NULL) { + quicklist_iter_complete(iter); + return 0; + } + quicklist_iter_move_to(iter, prev_p, prev_node); + return 1; } -/* Populate 'entry' with the element at the specified zero-based index - * where 0 is the head, 1 is the element next to head - * and so on. Negative integers are used in order to count - * from the tail, -1 is the last element, -2 the penultimate - * and so on. If the index is out of range 0 is returned. - * - * Returns an iterator at a specific offset 'idx' if element found - * Returns NULL if element not found */ -quicklistIter *quicklistGetIteratorEntryAtIdx(quicklist *quicklist, const long long idx, - quicklistEntry *entry) -{ - quicklistIter *iter = quicklistGetIteratorAtIdx(quicklist, AL_START_TAIL, idx); - if (!iter) return NULL; - assert(quicklistNext(iter, entry)); - return iter; -} - -static void quicklistRotatePlain(quicklist *quicklist) { - quicklistNode *new_head = quicklist->tail; - quicklistNode *new_tail = quicklist->tail->prev; - quicklist->head->prev = new_head; - new_tail->next = NULL; - new_head->next = quicklist->head; - new_head->prev = NULL; - quicklist->head = new_head; - quicklist->tail = new_tail; -} - -/* Rotate quicklist by moving the tail element to the head. */ -void quicklistRotate(quicklist *quicklist) { - if (quicklist->count <= 1) - return; - - if (unlikely(QL_NODE_IS_PLAIN(quicklist->tail))) { - quicklistRotatePlain(quicklist); - return; - } +/** + * quicklist_iter_next - Move @iter to next element base on @iter->forward. + * + * Return: 1 if next element exist, 0 if not. + * + * Note: @iter is not available for further use if 0 is returned. + */ +int quicklist_iter_next(struct quicklist_iter *iter) +{ + switch (iter->status) { + case QUICKLIST_ITER_STATUS_COMPLETE: + return 0; + case QUICKLIST_ITER_STATUS_AHEAD: + iter->status = QUICKLIST_ITER_STATUS_NORMAL; + return 1; + } + + if (iter->forward) + return __quicklist_iter_next_forward(iter); + else + return __quicklist_iter_next_backward(iter); +} - /* First, get the tail entry */ - unsigned char *p = lpSeek(quicklist->tail->entry, -1); - unsigned char *value, *tmp; - long long longval; - unsigned int sz; - char longstr[32] = {0}; - tmp = lpGetValue(p, &sz, &longval); - - /* If value found is NULL, then lpGet populated longval instead */ - if (!tmp) { - /* Write the longval as a string so we can re-add it */ - sz = ll2string(longstr, sizeof(longstr), longval); - value = (unsigned char *)longstr; - } else if (quicklist->len == 1) { - /* Copy buffer since there could be a memory overlap when move - * entity from tail to head in the same listpack. */ - value = zmalloc(sz); - memcpy(value, tmp, sz); - } else { - value = tmp; - } +/** + * quicklist_iter_del_single - Delete @iter->raw_node and move @iter to + * next element base on @iter->forward. + * + * Note: if next element is not exist, @iter's status will change to + * QUICKLIST_ITER_STATUS_COMPLETE; otherwise, @iter's status will change to + * QUICKLIST_ITER_STATUS_AHEAD. + */ +static void quicklist_iter_del_single(struct quicklist_iter *iter) +{ + struct quicklist *quicklist = iter->quicklist; + + struct quicklist_partition *prev_p, *next_p; + struct quicklist_node *prev, *next; + quicklist_prev(iter->p, iter->raw_node, &prev_p, &prev); + quicklist_next(iter->p, iter->raw_node, &next_p, &next); + + quicklist_bm_move_next(quicklist, iter->raw_node); + __quicklist_p_del_node(iter->p, iter->raw_node); + iter->quicklist->count--; + + if (!prev && !next) + goto iter_complete; + + if (!prev) { + if (!iter->forward) + goto iter_complete; + + next_p = __quicklist_fix_compress(quicklist, next_p, next); + quicklist_iter_set_node_first(iter, next_p, next); + goto iter_ahead; + } + + if (!next) { + if (iter->forward) + goto iter_complete; + + prev_p = __quicklist_fix_compress(quicklist, prev_p, prev); + quicklist_iter_set_node_last(iter, prev_p, prev); + goto iter_ahead; + } + + long i = prev->count; + if (__quicklist_try_merge(quicklist, prev_p, prev, next_p, next, 0, 1)) { + prev_p = __quicklist_fix_compress(quicklist, prev_p, prev); + if (iter->forward) + quicklist_iter_set(iter, prev_p, prev, i); + else + quicklist_iter_set(iter, prev_p, prev, i-1); + goto iter_ahead; + } + + if (iter->forward) { + next_p = __quicklist_fix_compress(quicklist, next_p, next); + quicklist_iter_set_node_first(iter, next_p, next); + goto iter_ahead; + } + + prev_p = __quicklist_fix_compress(quicklist, prev_p, prev); + quicklist_iter_set_node_last(iter, prev_p, prev); + goto iter_ahead; + +iter_complete: + iter->status = QUICKLIST_ITER_STATUS_COMPLETE; + return; + +iter_ahead: + iter->status = QUICKLIST_ITER_STATUS_AHEAD; +} - /* Add tail entry to head (must happen before tail is deleted). */ - quicklistPushHead(quicklist, value, sz); +/** + * quicklist_iter_del_multi - Delete the element that @iter point to and + * move @iter to next element base on @iter->forward. + * + * Note: if next element is not exist, @iter's status will change to + * QUICKLIST_ITER_STATUS_COMPLETE; otherwise, @iter's status will change to + * QUICKLIST_ITER_STATUS_AHEAD. + */ +static void quicklist_iter_del_multi(struct quicklist_iter *iter) +{ + struct quicklist *quicklist = iter->quicklist; + struct quicklist_partition *p = iter->p; + struct quicklist_node *node = iter->raw_node; + + node->carry = lpDelete(node->carry, iter->lp_element, NULL); + node->count--; + quicklist->count--; + + struct quicklist_partition *prev_p, *next_p; + struct quicklist_node *prev, *next; + quicklist_prev(p, node, &prev_p, &prev); + quicklist_next(p, node, &next_p, &next); + + long i = iter->offset; + if (prev) { + long j = i + prev->count; + if (__quicklist_try_merge(quicklist, prev_p, prev, p, node, 1, 1)) + i = j; + } + if (next) + __quicklist_try_merge(quicklist, p, node, next_p, next, 0, 1); + + p = __quicklist_fix_compress(quicklist, p, node); + int compress = quicklist_p_is_middle(p); + quicklist_prev(p, node, &prev_p, &prev); + quicklist_next(p, node, &next_p, &next); + + if (iter->forward) { + if (i == node->count) { + if (!next) + goto iter_complete; + + if (compress) + quicklist_n_compress_raw(node); + quicklist_iter_set_node_first(iter, next_p, next); + goto iter_ahead; + } else { + quicklist_iter_set(iter, p, node, i); + goto iter_ahead; + } + } else { + if (i == 0) { + if (!prev) + goto iter_complete; + + if (compress) + quicklist_n_compress_raw(node); + quicklist_iter_set_node_last(iter, prev_p, prev); + goto iter_ahead; + } else { + quicklist_iter_set(iter, p, node, i-1); + goto iter_ahead; + } + } + +iter_complete: + iter->status = QUICKLIST_ITER_STATUS_COMPLETE; + return; + +iter_ahead: + iter->status = QUICKLIST_ITER_STATUS_AHEAD; +} - /* If quicklist has only one node, the head listpack is also the - * tail listpack and PushHead() could have reallocated our single listpack, - * which would make our pre-existing 'p' unusable. */ - if (quicklist->len == 1) { - p = lpSeek(quicklist->tail->entry, -1); - } +/** + * quicklist_iter_del - Delete the element that @iter point to and + * move @iter to next element base on @iter->forward. + * + * Note: caller should make sure iter is normal. + * Note: if next element is not exist, @iter's status will change to + * QUICKLIST_ITER_STATUS_COMPLETE; otherwise, @iter's status will change to + * QUICKLIST_ITER_STATUS_AHEAD. + */ +void quicklist_iter_del(struct quicklist_iter *iter) +{ + assert(iter->status == QUICKLIST_ITER_STATUS_NORMAL); - /* Remove tail entry. */ - quicklistDelIndex(quicklist, quicklist->tail, &p); - if (value != (unsigned char*)longstr && value != tmp) - zfree(value); + if (iter->raw_node->count == 1) + quicklist_iter_del_single(iter); + else + quicklist_iter_del_multi(iter); } -/* pop from quicklist and return result in 'data' ptr. Value of 'data' - * is the return value of 'saver' function pointer if the data is NOT a number. - * - * If the quicklist element is a long long, then the return value is returned in - * 'sval'. - * - * Return value of 0 means no elements available. - * Return value of 1 means check 'data' and 'sval' for values. - * If 'data' is set, use 'data' and 'sz'. Otherwise, use 'sval'. */ -int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data, - size_t *sz, long long *sval, - void *(*saver)(unsigned char *data, size_t sz)) { - unsigned char *p; - unsigned char *vstr; - unsigned int vlen; - long long vlong; - int pos = (where == QUICKLIST_HEAD) ? 0 : -1; - - if (quicklist->count == 0) - return 0; - - if (data) - *data = NULL; - if (sz) - *sz = 0; - if (sval) - *sval = -123456789; - - quicklistNode *node; - if (where == QUICKLIST_HEAD && quicklist->head) { - node = quicklist->head; - } else if (where == QUICKLIST_TAIL && quicklist->tail) { - node = quicklist->tail; - } else { - return 0; - } - - /* The head and tail should never be compressed */ - assert(node->encoding != QUICKLIST_NODE_ENCODING_LZF); +/** + * quicklist_iter_add_carry - Add a new element with value @value and + * size @sz after or before the element that @iter point to. + * @after: 1 for add the new element right after the element that @iter point to, + * 0 for add the new element right before the element that @iter point to. + * + * Note: caller should make sure @iter->raw_node can hold the new element. + * Note: @iter will stay in place after the addition. + */ +static void quicklist_iter_add_carry(struct quicklist_iter *iter, + void *value, size_t sz, int after) +{ + struct quicklist_node *node = iter->raw_node; + node->carry = lpInsertString(node->carry, value, sz,iter->lp_element, + after, &iter->lp_element); + node->raw_sz = lpBytes(node->carry); + node->count++; + if (after) + quicklist_iter_set(iter, iter->p, node, iter->offset); + else + quicklist_iter_set(iter, iter->p, node, iter->offset+1); + + iter->quicklist->count++; +} - if (unlikely(QL_NODE_IS_PLAIN(node))) { - if (data) - *data = saver(node->entry, node->sz); - if (sz) - *sz = node->sz; - quicklistDelIndex(quicklist, node, NULL); - return 1; - } +/** + * quicklist_iter_add_after_node - Add a new element with value @value and + * size @sz right after node @iter->raw_node. + * + * Note: caller should make sure @iter->raw_node can't hold the new element. + * Note: @iter will stay in place after the addition. + */ +static void quicklist_iter_add_after_node(struct quicklist_iter *iter, + void *value, size_t sz) +{ - p = lpSeek(node->entry, pos); - vstr = lpGetValue(p, &vlen, &vlong); - if (vstr) { - if (data) - *data = saver(vstr, vlen); - if (sz) - *sz = vlen; - } else { - if (data) - *data = NULL; - if (sval) - *sval = vlong; - } - quicklistDelIndex(quicklist, node, &p); - return 1; + struct quicklist *quicklist = iter->quicklist; + struct quicklist_fill *fill = quicklist->fill; + struct quicklist_partition *p = iter->p; + struct quicklist_node *node = iter->raw_node; + + struct quicklist_partition *next_p; + struct quicklist_node *next; + quicklist_next(p, node, &next_p, &next); + if (next) { + int compress = quicklist_p_is_middle(next_p); + if (quicklist_n_try_add_carry_head(fill, next, compress, value, sz)) + goto added; + } + + struct quicklist_node *new_raw = quicklist_n_new_raw(fill, value, sz); + quicklist_p_add_node(p, node, node->next, new_raw, 0); + iter->p = __quicklist_fix_compress(quicklist, p, node); + +added: + iter->quicklist->count++; } -/* Return a malloc'd copy of data passed in */ -REDIS_STATIC void *_quicklistSaver(unsigned char *data, size_t sz) { - unsigned char *vstr; - if (data) { - vstr = zmalloc(sz); - memcpy(vstr, data, sz); - return vstr; - } - return NULL; +/** + * quicklist_iter_add_before_node - Add a new element with value @value and + * size @sz right before node @iter->raw_node. + * + * Note: caller should make sure @iter->raw_node can't hold the new element. + * Note: @iter will stay in place after the addition. + */ +static void quicklist_iter_add_before_node(struct quicklist_iter *iter, + void *value, size_t sz) +{ + struct quicklist *quicklist = iter->quicklist; + struct quicklist_fill *fill = quicklist->fill; + struct quicklist_partition *p = iter->p; + struct quicklist_node *node = iter->raw_node; + + struct quicklist_partition *prev_p; + struct quicklist_node *prev; + quicklist_prev(p, node, &prev_p, &prev); + if (prev) { + int compress = quicklist_p_is_middle(prev_p); + if (quicklist_n_try_add_carry_tail(fill, prev, compress, value, sz)) + goto added; + } + + struct quicklist_node *new = quicklist_n_new_raw(fill, value, sz); + quicklist_p_add_node(p, node->prev, node, new, 0); + iter->p = __quicklist_fix_compress(quicklist, p, node); + +added: + iter->quicklist->count++; } -/* Default pop function - * - * Returns malloc'd value from quicklist */ -int quicklistPop(quicklist *quicklist, int where, unsigned char **data, - size_t *sz, long long *slong) { - unsigned char *vstr = NULL; - size_t vlen = 0; - long long vlong = 0; - if (quicklist->count == 0) - return 0; - int ret = quicklistPopCustom(quicklist, where, &vstr, &vlen, &vlong, - _quicklistSaver); - if (data) - *data = vstr; - if (slong) - *slong = vlong; - if (sz) - *sz = vlen; - return ret; -} - -/* Wrapper to allow argument-based switching between HEAD/TAIL pop */ -void quicklistPush(quicklist *quicklist, void *value, const size_t sz, - int where) { - /* The head and tail should never be compressed (we don't attempt to decompress them) */ - if (quicklist->head) - assert(quicklist->head->encoding != QUICKLIST_NODE_ENCODING_LZF); - if (quicklist->tail) - assert(quicklist->tail->encoding != QUICKLIST_NODE_ENCODING_LZF); - - if (where == QUICKLIST_HEAD) { - quicklistPushHead(quicklist, value, sz); - } else if (where == QUICKLIST_TAIL) { - quicklistPushTail(quicklist, value, sz); - } +/** + * quicklist_iter_split_add_after - Split @iter->raw_node after the element + * @iter point to, and add a new element with value @value and size @sz between. + * + * Note: caller should make sure @iter->raw_node can't hold the new element. + * Note: caller should make sure @iter->raw_node can split into two parts. + * Note: @iter will stay in place after the addition. + */ +static void quicklist_iter_split_add_after(struct quicklist_iter *iter, + void *value, size_t sz) +{ + struct quicklist *quicklist = iter->quicklist; + struct quicklist_fill *fill = quicklist->fill; + struct quicklist_partition *p = iter->p; + struct quicklist_node *node = iter->raw_node; + + struct quicklist_partition *prev_p, *next_p; + struct quicklist_node *prev, *next; + quicklist_prev(p, node, &prev_p, &prev); + quicklist_next(p, node, &next_p, &next); + + struct quicklist_partition *split_p = p; + struct quicklist_node *split = quicklist_n_split_raw(node, iter->offset+1); + quicklist_p_add_node(p, node, node->next, split, 1); + + if(prev) { + int forward = quicklist_p_is_middle(prev_p); + if (__quicklist_try_merge(quicklist, prev_p, prev, p, node, forward, 1)) { + if (!forward) { + p = prev_p; + node = prev; + } + } + } + + if (next) { + int forward = !quicklist_p_is_middle(next_p); + if (__quicklist_try_merge(quicklist, split_p, split, next_p, next, forward, 1)) { + if (forward) { + split_p = next_p; + split = next; + } + } + } + + int split_compress = quicklist_p_is_middle(split_p); + if (quicklist_n_try_add_carry_head(fill, split, split_compress, value, sz)) { + p = __quicklist_fix_compress(quicklist, p, node); + quicklist_iter_set_node_last(iter, p, node); + goto added; + } + + if (split_compress) + quicklist_n_compress_raw(split); + + if (quicklist_n_try_add_carry_tail(fill, node, 0, value, sz)) { + p = __quicklist_fix_compress(quicklist, p, node); + quicklist_iter_set(iter, p, node, node->count-2); + goto added; + } + + struct quicklist_node *new = quicklist_n_new_raw(fill, value, sz); + quicklist_p_add_node(p, node, node->next, new, 0); + p = __quicklist_fix_compress(quicklist, p, node); + quicklist_iter_set_node_last(iter, p, node); + +added: + iter->quicklist->count++; } -/* Print info of quicklist which is used in debugCommand. */ -void quicklistRepr(unsigned char *ql, int full) { - int i = 0; - quicklist *quicklist = (struct quicklist*) ql; - printf("{count : %ld}\n", quicklist->count); - printf("{len : %ld}\n", quicklist->len); - printf("{fill : %d}\n", quicklist->fill); - printf("{compress : %d}\n", quicklist->compress); - printf("{bookmark_count : %d}\n", quicklist->bookmark_count); - quicklistNode* node = quicklist->head; - - while(node != NULL) { - printf("{quicklist node(%d)\n", i++); - printf("{container : %s, encoding: %s, size: %zu, count: %d, recompress: %d, attempted_compress: %d}\n", - QL_NODE_IS_PLAIN(node) ? "PLAIN": "PACKED", - (node->encoding == QUICKLIST_NODE_ENCODING_RAW) ? "RAW": "LZF", - node->sz, - node->count, - node->recompress, - node->attempted_compress); - - if (full) { - quicklistDecompressNode(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; - } +/** + * quicklist_iter_split_add_before - Split @iter->raw_node before the element + * @iter point to, and add a new element with value @value and size @sz between. + * + * Note: caller should make sure @iter->raw_node can't hold the new element. + * Note: caller should make sure @iter->raw_node can split into two parts. + * Note: @iter will stay in place after the addition. + */ +static void quicklist_iter_split_add_before(struct quicklist_iter *iter, + void *value, size_t sz) +{ + struct quicklist *quicklist = iter->quicklist; + struct quicklist_fill *fill = quicklist->fill; + struct quicklist_partition *p = iter->p; + struct quicklist_node *node = iter->raw_node; + + struct quicklist_partition *prev_p, *next_p; + struct quicklist_node *prev, *next; + quicklist_prev(p, node, &prev_p, &prev); + quicklist_next(p, node, &next_p, &next); + + struct quicklist_partition *split_p = p; + struct quicklist_node *split = quicklist_n_split_raw(node, iter->offset); + quicklist_p_add_node(p, node, node->next, split, 1); + + if(prev) { + int forward = quicklist_p_is_middle(prev_p); + if (__quicklist_try_merge(quicklist, prev_p, prev, p, node, forward, 1)) { + if (!forward) { + p = prev_p; + node = prev; + } + } + } + + if (next) { + int forward = !quicklist_p_is_middle(next_p); + if (__quicklist_try_merge(quicklist, split_p, split, next_p, next, forward, 1)) { + if (forward) { + split_p = next_p; + split = next; + } + } + } + + int compress = quicklist_p_is_middle(p); + if (quicklist_n_try_add_carry_tail(fill, node, compress, value, sz)) { + split_p = __quicklist_fix_compress(quicklist, split_p, split); + quicklist_iter_set_node_first(iter, split_p, split); + goto added; + } + + if (compress) + quicklist_n_compress_raw(node); + + if (quicklist_n_try_add_carry_head(fill, split, 0, value, sz)) { + split_p = __quicklist_fix_compress(quicklist, split_p, split); + quicklist_iter_set(iter, split_p, split, 1); + goto added; + } + + struct quicklist_node *new = quicklist_n_new_raw(fill, value, sz); + quicklist_p_add_node(p, node, node->next, new, 0); + split_p = __quicklist_fix_compress(quicklist, split_p, split); + quicklist_iter_set_node_first(iter, split_p, split); + +added: + iter->quicklist->count++; } -/* Create or update a bookmark in the list which will be updated to the next node - * automatically when the one referenced gets deleted. - * Returns 1 on success (creation of new bookmark or override of an existing one). - * Returns 0 on failure (reached the maximum supported number of bookmarks). - * NOTE: use short simple names, so that string compare on find is quick. - * NOTE: bookmark creation may re-allocate the quicklist, so the input pointer - may change and it's the caller responsibility to update the reference. - */ -int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) { - quicklist *ql = *ql_ref; - if (ql->bookmark_count >= QL_MAX_BM) - return 0; - quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); - if (bm) { - bm->node = node; - return 1; - } - ql = zrealloc(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark)); - *ql_ref = ql; - ql->bookmarks[ql->bookmark_count].node = node; - ql->bookmarks[ql->bookmark_count].name = zstrdup(name); - ql->bookmark_count++; - return 1; -} - -/* Find the quicklist node referenced by a named bookmark. - * When the bookmarked node is deleted the bookmark is updated to the next node, - * and if that's the last node, the bookmark is deleted (so find returns NULL). */ -quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) { - quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); - if (!bm) return NULL; - return bm->node; -} +/** + * quicklist_iter_add_after - Add a new element with value @value and size @sz + * right after the element that @iter point to. + * + * Note: @iter will stay in place after the addition. + * Note: caller should make sure @iter is normal. + */ +void quicklist_iter_add_after(struct quicklist_iter *iter, void *value, size_t sz) +{ + assert(iter->status == QUICKLIST_ITER_STATUS_NORMAL); -/* Delete a named bookmark. - * returns 0 if bookmark was not found, and 1 if deleted. - * Note that the bookmark memory is not freed yet, and is kept for future use. */ -int quicklistBookmarkDelete(quicklist *ql, const char *name) { - quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name); - if (!bm) - return 0; - _quicklistBookmarkDelete(ql, bm); - return 1; + struct quicklist_fill *fill = iter->quicklist->fill; + struct quicklist_node *node = iter->raw_node; + + if (quicklist_n_allow_add_carry(fill, node, sz)) + quicklist_iter_add_carry(iter, value, sz, 1); + else if (iter->offset == node->count-1) + quicklist_iter_add_after_node(iter, value, sz); + else + quicklist_iter_split_add_after(iter, value, sz); } -quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) { - unsigned i; - for (i=0; ibookmark_count; i++) { - if (!strcmp(ql->bookmarks[i].name, name)) { - return &ql->bookmarks[i]; - } - } - return NULL; +/** + * quicklist_iter_add_before - Add a new element with value @value and size @sz + * right before the element that @iter point to. + * + * Note: @iter will stay in place after the addition. + * Note: caller should make sure @iter is normal. + */ +void quicklist_iter_add_before(struct quicklist_iter *iter, void *value, size_t sz) +{ + assert(iter->status == QUICKLIST_ITER_STATUS_NORMAL); + + struct quicklist_fill *fill = iter->quicklist->fill; + struct quicklist_node *node = iter->raw_node; + + if (quicklist_n_allow_add_carry(fill, node, sz)) + quicklist_iter_add_carry(iter, value, sz, 0); + else if (iter->offset == 0) + quicklist_iter_add_before_node(iter, value, sz); + else + quicklist_iter_split_add_before(iter, value, sz); } -quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) { - unsigned i; - for (i=0; ibookmark_count; i++) { - if (ql->bookmarks[i].node == node) { - return &ql->bookmarks[i]; - } - } - return NULL; +/** + * quicklist_iter_replace - Replace the element that @iter point to with + * value @value and size @sz. + * + * Note: @iter will stay in place after replacement. + * Note: caller should make sure @iter is normal. + */ +void quicklist_iter_replace(struct quicklist_iter *iter, void *value, size_t sz) +{ + assert(iter->status == QUICKLIST_ITER_STATUS_NORMAL); + + if (iter->forward) + quicklist_iter_add_after(iter, value, sz); + else + quicklist_iter_add_before(iter, value, sz); + + quicklist_iter_del(iter); + quicklist_iter_next(iter); } -void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) { - int index = bm - ql->bookmarks; - zfree(bm->name); - ql->bookmark_count--; - memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm)); - /* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance, - * it may be re-used later (a call to realloc may NOP). */ +/** + * quicklist_replace - Replace @quicklist's @ith element with value @value and + * size @sz. + * + * Return: 1 if @i is valid, 0 if not. + * + * Note: negative @i is acceptable, see quicklist_transform_index(). + */ +int quicklist_replace(struct quicklist *quicklist, long i, void *value, size_t sz) +{ + /* skip quicklist_transform_index() */ + struct quicklist_iter *iter = quicklist_iter_new(quicklist, i, 1); + if (iter == NULL) + return 0; + quicklist_iter_replace(iter, value, sz); + quicklist_iter_free(iter); + return 1; } -void quicklistBookmarksClear(quicklist *ql) { - while (ql->bookmark_count) - zfree(ql->bookmarks[--ql->bookmark_count].name); - /* NOTE: We do not shrink (realloc) the quick list. main use case for this - * function is just before releasing the allocation. */ +/** + * quicklist_iter_equal - Test the element that @iter point to is equal with + * value @value and size @sz or not. + * + * Return: 1 if equal, 0 if not. + */ +int quicklist_iter_equal(struct quicklist_iter *iter, void *value, size_t sz) +{ + + if (iter->status != QUICKLIST_ITER_STATUS_NORMAL) + return 0; + + struct quicklist_node *node = iter->raw_node; + if (node->container == QUICKLIST_NODE_CONTAINER_PACKED) + return lpCompare(iter->lp_element, value, sz); + return (node->raw_sz == sz) && (memcmp(node->carry, value, sz) == 0); } /* The rest of this file is test cases and test helpers. */ @@ -1759,29 +2621,25 @@ void quicklistBookmarksClear(quicklist *ql) { #define QL_TEST_VERBOSE 0 #define UNUSED(x) (void)(x) -static void ql_info(quicklist *ql) { +static void ql_info(struct quicklist *ql) { #if QL_TEST_VERBOSE - printf("Container length: %lu\n", ql->len); - printf("Container size: %lu\n", ql->count); - if (ql->head) - printf("\t(zsize head: %lu)\n", lpLength(ql->head->entry)); - if (ql->tail) - printf("\t(zsize tail: %lu)\n", lpLength(ql->tail->entry)); - printf("\n"); + printf("Container length: %lu\n", quicklist_node_count(ql)); + printf("Container size: %lu\n", ql->count); + printf("\n"); #else - UNUSED(ql); + UNUSED(ql); #endif } /* Return the UNIX time in microseconds */ static long long ustime(void) { - struct timeval tv; - long long ust; + struct timeval tv; + long long ust; - gettimeofday(&tv, NULL); - ust = ((long long)tv.tv_sec) * 1000000; - ust += tv.tv_usec; - return ust; + gettimeofday(&tv, NULL); + ust = ((long long)tv.tv_sec) * 1000000; + ust += tv.tv_usec; + return ust; } /* Return the UNIX time in milliseconds */ @@ -1791,35 +2649,42 @@ static long long mstime(void) { return ustime() / 1000; } * Print the list if 'print' == 1. * * Returns physical count of elements found by iterating over the list. */ -static int _itrprintr(quicklist *ql, int print, int forward) { - quicklistIter *iter = - quicklistGetIterator(ql, forward ? AL_START_HEAD : AL_START_TAIL); - quicklistEntry entry; - int i = 0; - int p = 0; - quicklistNode *prev = NULL; - while (quicklistNext(iter, &entry)) { - if (entry.node != prev) { - /* Count the number of list nodes too */ - p++; - prev = entry.node; - } - if (print) { - int size = (entry.sz > (1<<20)) ? 1<<20 : entry.sz; - printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, size, - (char *)entry.value, entry.longval); - } - i++; - } - quicklistReleaseIterator(iter); - return i; +static int _itrprintr(struct quicklist *ql, int print, int forward) { + struct quicklist_iter *iter; + if (forward) + iter = quicklist_iter_new_ahead(ql, 0, 1); + else + iter = quicklist_iter_new_ahead(ql, -1, 0); + if (iter == NULL) + return 0; + int i = 0; + int p = 0; + struct quicklist_node *prev = NULL; + while (quicklist_iter_next(iter)) { + if (iter->raw_node != prev) { + /* Count the number of list nodes too */ + p++; + prev = iter->raw_node; + } + if (print) { + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + int size = (elem.sz > (1<<20)) ? 1<<20 : elem.sz; + printf("[%3d (%2d)]: [%.*s] (%lld)\n", i, p, size, + (char *)elem.value, elem.longval); + } + i++; + } + quicklist_iter_free(iter); + return i; } -static int itrprintr(quicklist *ql, int print) { - return _itrprintr(ql, print, 1); + +static int itrprintr(struct quicklist *ql, int print) { + return _itrprintr(ql, print, 1); } -static int itrprintr_rev(quicklist *ql, int print) { - return _itrprintr(ql, print, 0); +static int itrprintr_rev(struct quicklist *ql, int print) { + return _itrprintr(ql, print, 0); } #define ql_verify(a, b, c, d, e) \ @@ -1827,453 +2692,546 @@ static int itrprintr_rev(quicklist *ql, int print) { err += _ql_verify((a), (b), (c), (d), (e)); \ } while (0) -static int _ql_verify_compress(quicklist *ql) { - int errors = 0; - if (quicklistAllowsCompression(ql)) { - quicklistNode *node = ql->head; - unsigned int low_raw = ql->compress; - unsigned int high_raw = ql->len - ql->compress; - - for (unsigned int at = 0; at < ql->len; at++, node = node->next) { - if (node && (at < low_raw || at >= high_raw)) { - if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) { - yell("Incorrect compression: node %d is " - "compressed at depth %d ((%u, %u); total " - "nodes: %lu; size: %zu; recompress: %d)", - at, ql->compress, low_raw, high_raw, ql->len, node->sz, - node->recompress); - errors++; - } - } else { - if (node->encoding != QUICKLIST_NODE_ENCODING_LZF && - !node->attempted_compress) { - yell("Incorrect non-compression: node %d is NOT " - "compressed at depth %d ((%u, %u); total " - "nodes: %lu; size: %zu; recompress: %d; attempted: %d)", - at, ql->compress, low_raw, high_raw, ql->len, node->sz, - node->recompress, node->attempted_compress); - errors++; - } - } - } - } - return errors; +static int ql_p_verify_not_compress(struct quicklist_partition *p, long *i) +{ + int error_count = 0; + long node_count = 0; + struct quicklist_node *guard = p->guard; + struct quicklist_node *node = guard->next; + while (node != guard) { + if (!node->raw) { + error_count++; + yell("Incorrect compression: node: %ld should be raw", *i); + } + + node = node->next; + node_count++; + } + if (node_count != p->length) { + error_count++; + yell("Incorrect compression: partition: %d length: %ld actual: %ld", + p->which, p->length, node_count); + } + if (p->length > p->capacity) { + error_count++; + yell("Incorrect compression: partition: %d overflow length: %ld capacity: %ld", + p->which, p->length, p->capacity); + } + (*i) += node_count; + return error_count; } -/* Verify list metadata matches physical list contents. */ -static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count, - uint32_t head_count, uint32_t tail_count) { - int errors = 0; - - ql_info(ql); - if (len != ql->len) { - yell("quicklist length wrong: expected %d, got %lu", len, ql->len); - errors++; - } - - if (count != ql->count) { - yell("quicklist count wrong: expected %d, got %lu", count, ql->count); - errors++; - } - - int loopr = itrprintr(ql, 0); - if (loopr != (int)ql->count) { - yell("quicklist cached count not match actual count: expected %lu, got " - "%d", - ql->count, loopr); - errors++; - } +static int ql_p_verify_compress(struct quicklist_partition *p, long *i) +{ + int error_count = 0; + long node_count = 0; + + if (quicklist_p_is_overflow(p)) { + error_count++; + yell("Incorrect compression: partition: %d is overflow", p->which); + } + + struct quicklist_node *guard = p->guard; + struct quicklist_node *node = guard->next; + while (node != guard) { + if (node->raw) { + quicklist_n_compress_raw(node); + if (!node->raw) { + error_count++; + yell("Incorrect compression: node: %ld should be compressed", *i); + } + } + + node = node->next; + node_count++; + } + if (node_count != p->length) { + error_count++; + yell("Incorrect compression: partition: %d length: %ld actual: %ld", + p->which, p->length, node_count); + } + if (p->length > p->capacity) { + error_count++; + yell("Incorrect compression: partition: %d overflow length: %ld capacity: %ld", + p->which, p->length, p->capacity); + } + (*i) += node_count; + return error_count; +} - int rloopr = itrprintr_rev(ql, 0); - if (loopr != rloopr) { - yell("quicklist has different forward count than reverse count! " - "Forward count is %d, reverse count is %d.", - loopr, rloopr); - errors++; - } +static int ql_verify_compress(struct quicklist *quicklist) +{ + int error_count = 0; + long i = 0; + struct quicklist_partition *head = quicklist->head; + struct quicklist_partition *middle = head->next; + struct quicklist_partition *tail = middle->next; + error_count += ql_p_verify_not_compress(head, &i); + error_count += ql_p_verify_compress(middle, &i); + error_count += ql_p_verify_not_compress(tail, &i); + + if (middle->length == 0) { + if (quicklist_p_is_overflow(head)) { + error_count++; + yell("Incorrect compression: partition: head length: %ld is overflow", + head->length); + } + if (quicklist_p_is_overflow(tail)) { + error_count++; + yell("Incorrect compression: partition: tail length: %ld is overflow", + head->length); + } + } else { + if (!quicklist_p_is_full(head)) { + error_count++; + yell("Incorrect compression: partition: head length: %ld is not full", + head->length); + } + if (!quicklist_p_is_full(tail)) { + error_count++; + yell("Incorrect compression: partition: tail length: %ld is not full", + head->length); + } + } + return error_count; +} - if (ql->len == 0 && !errors) { - return errors; - } +static int ql_verify_merge(struct quicklist *quicklist) +{ + if (quicklist->count == 0) + return 0; + + int error_count = 0; + struct quicklist_fill *fill = quicklist->fill; + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(quicklist, &p, &node); + long i = 0; + while (1) { + struct quicklist_partition *next_p; + struct quicklist_node *next; + quicklist_next(p, node, &next_p, &next); + if (!next) + return error_count; + if (quicklist_n_allow_merge(fill, node, next)) { + error_count++; + yell("Incorrect merge: node: %ld is allowed to merge to next", i); + } + p = next_p; + node = next; + i++; + } +} - if (ql->head && head_count != ql->head->count && - head_count != lpLength(ql->head->entry)) { - yell("quicklist head count wrong: expected %d, " - "got cached %d vs. actual %lu", - head_count, ql->head->count, lpLength(ql->head->entry)); - errors++; - } +static int ql_verify_pack_limit(struct quicklist *quicklist) +{ + int error_count = 0; + struct quicklist_fill *fill = quicklist->fill; + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(quicklist, &p, &node); + long i = 0; + while (node) { + if (node->container == QUICKLIST_NODE_CONTAINER_PLAIN) { + if (!quicklist_is_large_element(fill, node->raw_sz)) { + error_count++; + yell("Incorrect limit: node: %ld is not a large element", i); + } + } else { + if (node->count > fill->pack_max_count) { + error_count++; + yell("Incorrect limit: node: %ld count: %d limit: %d", + i, node->count, fill->pack_max_count); + } + if (node->raw_sz > fill->pack_max_size) { + error_count++; + yell("Incorrect limit: node: %ld sz: %zu limit: %d", + i, node->raw_sz, fill->pack_max_size); + } + } + i++; + quicklist_next(p, node, &p, &node); + } + return error_count; +} - if (ql->tail && tail_count != ql->tail->count && - tail_count != lpLength(ql->tail->entry)) { - yell("quicklist tail count wrong: expected %d, " - "got cached %u vs. actual %lu", - tail_count, ql->tail->count, lpLength(ql->tail->entry)); - errors++; - } +static int ql_n_actual_count(struct quicklist_node *node) +{ + if (node->container == QUICKLIST_NODE_CONTAINER_PLAIN) + return 1; + return lpLength(node->carry); +} - errors += _ql_verify_compress(ql); - return errors; +/* Verify list metadata matches physical list contents. */ +static int _ql_verify(struct quicklist *ql, long len, long count, + int head_count, int tail_count) { + int errors = 0; + + ql_info(ql); + if (len != quicklist_node_count(ql)) { + yell("quicklist length wrong: expected %ld, got %ld", len, quicklist_node_count(ql)); + errors++; + } + + if (count != ql->count) { + yell("quicklist count wrong: expected %ld, got %ld", count, ql->count); + errors++; + } + + int loopr = itrprintr(ql, 0); + if (loopr != (int)ql->count) { + yell("quicklist cached count not match actual count: expected %ld, got " + "%d", ql->count, loopr); + errors++; + } + + int rloopr = itrprintr_rev(ql, 0); + if (loopr != rloopr) { + yell("quicklist has different forward count than reverse count! " + "Forward count is %d, reverse count is %d.", loopr, rloopr); + errors++; + } + + if (quicklist_node_count(ql) == 0 && !errors) { + return errors; + } + + struct quicklist_partition *head_p; + struct quicklist_node *head; + quicklist_first_node(ql, &head_p, &head); + if (head) { + int cache_count = head->count; + int actual_count = ql_n_actual_count(head); + if (head_count != cache_count || head_count != actual_count) { + yell("quicklist head count wrong: expected %d, " + "got cached %d vs. actual %d", + head_count, cache_count, actual_count); + errors++; + } + } + + struct quicklist_partition *tail_p; + struct quicklist_node *tail; + quicklist_last_node(ql, &tail_p, &tail); + if (tail) { + int cache_count = tail->count; + int actual_count = ql_n_actual_count(tail); + if (tail_count != cache_count || tail_count != actual_count) { + yell("quicklist tail count wrong: expected %d, " + "got cached %d vs. actual %d", + tail_count, cache_count, actual_count); + errors++; + } + } + + errors += ql_verify_compress(ql); + errors += ql_verify_merge(ql); + errors += ql_verify_pack_limit(ql); + return errors; } /* Release iterator and verify compress correctly. */ -static void ql_release_iterator(quicklistIter *iter) { - quicklist *ql = NULL; - if (iter) ql = iter->quicklist; - quicklistReleaseIterator(iter); - if (ql) assert(!_ql_verify_compress(ql)); +static void ql_release_iterator(struct quicklist_iter *iter) { + struct quicklist *ql = NULL; + if (iter) ql = iter->quicklist; + quicklist_iter_free(iter); + if (ql) assert(!ql_verify_compress(ql)); } /* Generate new string concatenating integer i against string 'prefix' */ static char *genstr(char *prefix, int i) { - static char result[64] = {0}; - snprintf(result, sizeof(result), "%s%d", prefix, i); - return result; + static char result[64] = {0}; + snprintf(result, sizeof(result), "%s%d", prefix, i); + return result; } static void randstring(unsigned char *target, size_t sz) { - size_t p = 0; - int minval, maxval; - switch(rand() % 3) { - case 0: - minval = 'a'; - maxval = 'z'; - break; - case 1: - minval = '0'; - maxval = '9'; - break; - case 2: - minval = 'A'; - maxval = 'Z'; - break; - default: - assert(NULL); - } + size_t p = 0; + int minval, maxval; + switch(rand() % 3) { + case 0: + minval = 'a'; + maxval = 'z'; + break; + case 1: + minval = '0'; + maxval = '9'; + break; + case 2: + minval = 'A'; + maxval = 'Z'; + break; + default: + assert(NULL); + } + + while(p < sz) + target[p++] = minval+rand()%(maxval-minval+1); +} + +/** quicklist_get_element - Get @ith element of @quicklist. + * @elem: The found element is stored in *@elem. + * + * Return: 1 if element is found, 0 if not. + */ +static int quicklist_get_element(struct quicklist *quicklist, long i, + struct quicklist_element *elem) +{ + struct quicklist_iter *iter = quicklist_iter_new(quicklist, i, 0); + if (iter == NULL) + return 0; - while(p < sz) - target[p++] = minval+rand()%(maxval-minval+1); + quicklist_iter_get_element(iter, elem); + quicklist_iter_free(iter); + return 1; } /* main test, but callable from other files */ int quicklistTest(int argc, char *argv[], int flags) { - UNUSED(argc); - UNUSED(argv); - - int accurate = (flags & REDIS_TEST_ACCURATE); - unsigned int err = 0; - int optimize_start = - -(int)(sizeof(optimization_level) / sizeof(*optimization_level)); - - printf("Starting optimization offset at: %d\n", optimize_start); - - int options[] = {0, 1, 2, 3, 4, 5, 6, 10}; - int fills[] = {-5, -4, -3, -2, -1, 0, - 1, 2, 32, 66, 128, 999}; - size_t option_count = sizeof(options) / sizeof(*options); - int fill_count = (int)(sizeof(fills) / sizeof(*fills)); - long long runtime[option_count]; - - for (int _i = 0; _i < (int)option_count; _i++) { - printf("Testing Compression option %d\n", options[_i]); - long long start = mstime(); - quicklistIter *iter; - - TEST("create list") { - quicklist *ql = quicklistNew(-2, options[_i]); - ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); - } - - TEST("add to tail of empty list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushTail(ql, "hello", 6); - /* 1 for head and 1 for tail because 1 node = head = tail */ - ql_verify(ql, 1, 1, 1, 1); - quicklistRelease(ql); - } + UNUSED(argc); + UNUSED(argv); + + int accurate = (flags & REDIS_TEST_ACCURATE); + unsigned int err = 0; + int optimize_start = + -(int)(sizeof(QUICKLIST_OPTIMIZED_LEVEL) / sizeof(*QUICKLIST_OPTIMIZED_LEVEL)); + + printf("Starting optimization offset at: %d\n", optimize_start); + + int options[] = {0, 1, 2, 3, 4, 5, 6, 10}; + int fills[] = {-5, -4, -3, -2, -1, 0, + 1, 2, 32, 66, 128, 999}; + size_t option_count = sizeof(options) / sizeof(*options); + int fill_count = (int)(sizeof(fills) / sizeof(*fills)); + long long runtime[option_count]; + + for (int _i = 0; _i < (int)option_count; _i++) { + printf("Testing Compression option %d\n", options[_i]); + long long start = mstime(); + struct quicklist_iter *iter; + + TEST("create list") { + struct quicklist *ql = quicklist_new(-2, options[_i]); + ql_verify(ql, 0, 0, 0, 0); + quicklist_free(ql); + } + + TEST("add to tail of empty list") { + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_tail(ql, "hello", 6); + /* 1 for head and 1 for tail because 1 node = head = tail */ + ql_verify(ql, 1, 1, 1, 1); + quicklist_free(ql); + } TEST("add to head of empty list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushHead(ql, "hello", 6); - /* 1 for head and 1 for tail because 1 node = head = tail */ - ql_verify(ql, 1, 1, 1, 1); - quicklistRelease(ql); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_head(ql, "hello", 6); + /* 1 for head and 1 for tail because 1 node = head = tail */ + ql_verify(ql, 1, 1, 1, 1); + quicklist_free(ql); } TEST_DESC("add to tail 5x at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 5; i++) - quicklistPushTail(ql, genstr("hello", i), 32); + quicklist_push_tail(ql, genstr("hello", i), 32); if (ql->count != 5) ERROR; if (fills[f] == 32) ql_verify(ql, 1, 5, 5, 5); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("add to head 5x at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 5; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); if (ql->count != 5) ERROR; if (fills[f] == 32) ql_verify(ql, 1, 5, 5, 5); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("add to tail 500x at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i), 64); + quicklist_push_tail(ql, genstr("hello", i), 64); if (ql->count != 500) ERROR; if (fills[f] == 32) ql_verify(ql, 16, 500, 32, 20); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("add to head 500x at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); if (ql->count != 500) ERROR; if (fills[f] == 32) ql_verify(ql, 16, 500, 20, 32); - quicklistRelease(ql); + quicklist_free(ql); } } - TEST("rotate empty") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistRotate(ql); - ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); - } - TEST("Comprassion Plain node") { char buf[256]; - quicklistisSetPackedThreshold(1); - quicklist *ql = quicklistNew(-2, 1); + struct quicklist *ql = quicklist_new(0, 1); for (int i = 0; i < 500; i++) { /* Set to 256 to allow the node to be triggered to compress, * if it is less than 48(nocompress), the test will be successful. */ snprintf(buf, sizeof(buf), "hello%d", i); - quicklistPushHead(ql, buf, 256); + quicklist_push_head(ql, buf, 256); } - quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL); - quicklistEntry entry; + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, -1, 0); + struct quicklist_element elem; int i = 0; - while (quicklistNext(iter, &entry)) { + while (quicklist_iter_next(iter)) { snprintf(buf, sizeof(buf), "hello%d", i); - if (strcmp((char *)entry.value, buf)) + quicklist_iter_get_element(iter, &elem); + if (strcmp((char *)elem.value, buf)) ERR("value [%s] didn't match [%s] at position %d", - entry.value, buf, i); + elem.value, buf, i); i++; } ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } TEST("NEXT plain node") { - packed_threshold = 3; - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(0, options[_i]); char *strings[] = {"hello1", "hello2", "h3", "h4", "hello5"}; for (int i = 0; i < 5; ++i) - quicklistPushHead(ql, strings[i], strlen(strings[i])); + quicklist_push_head(ql, strings[i], strlen(strings[i])); - quicklistEntry entry; - quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL); + struct quicklist_element elem; + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, -1, 0); int j = 0; - while(quicklistNext(iter, &entry) != 0) { - assert(strncmp(strings[j], (char *)entry.value, strlen(strings[j])) == 0); + while(quicklist_iter_next(iter) != 0) { + quicklist_iter_get_element(iter, &elem); + assert(strncmp(strings[j], (char *)elem.value, strlen(strings[j])) == 0); j++; } ql_release_iterator(iter); - quicklistRelease(ql); - } - - TEST("rotate plain node ") { - unsigned char *data = NULL; - size_t sz; - long long lv; - int i =0; - packed_threshold = 5; - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushHead(ql, "hello1", 6); - quicklistPushHead(ql, "hello4", 6); - quicklistPushHead(ql, "hello3", 6); - quicklistPushHead(ql, "hello2", 6); - quicklistRotate(ql); - - for(i = 1 ; i < 5; i++) { - quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); - int temp_char = data[5]; - zfree(data); - assert(temp_char == ('0' + i)); - } - - ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); - packed_threshold = (1 << 30); - } - - TEST("rotate one val once") { - for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); - quicklistPushHead(ql, "hello", 6); - quicklistRotate(ql); - /* Ignore compression verify because listpack is - * too small to compress. */ - ql_verify(ql, 1, 1, 1, 1); - quicklistRelease(ql); - } - } - - TEST_DESC("rotate 500 val 5000 times at compress %d", options[_i]) { - for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); - quicklistPushHead(ql, "900", 3); - quicklistPushHead(ql, "7000", 4); - quicklistPushHead(ql, "-1200", 5); - quicklistPushHead(ql, "42", 2); - for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 64); - ql_info(ql); - for (int i = 0; i < 5000; i++) { - ql_info(ql); - quicklistRotate(ql); - } - if (fills[f] == 1) - ql_verify(ql, 504, 504, 1, 1); - else if (fills[f] == 2) - ql_verify(ql, 252, 504, 2, 2); - else if (fills[f] == 32) - ql_verify(ql, 16, 504, 32, 24); - quicklistRelease(ql); - } - } - - TEST("pop empty") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPop(ql, QUICKLIST_HEAD, NULL, NULL, NULL); - ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + quicklist_free(ql); } TEST("pop 1 string from 1") { - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(-2, options[_i]); char *populate = genstr("hello", 331); - quicklistPushHead(ql, populate, 32); - unsigned char *data; - size_t sz; - long long lv; + quicklist_push_head(ql, populate, 32); ql_info(ql); - assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv)); - assert(data != NULL); - assert(sz == 32); - if (strcmp(populate, (char *)data)) { - int size = sz; + struct quicklist_iter *iter = quicklist_iter_new(ql, 0, 1); + assert(iter); + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + assert(elem.value); + assert(elem.sz == 32); + if (strcmp(populate, (char *)elem.value)) { + int size = elem.sz; ERR("Pop'd value (%.*s) didn't equal original value (%s)", size, - data, populate); + elem.value, populate); } - zfree(data); + quicklist_iter_del(iter); ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + ql_release_iterator(iter); + quicklist_free(ql); } TEST("pop head 1 number from 1") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushHead(ql, "55513", 5); - unsigned char *data; - size_t sz; - long long lv; - ql_info(ql); - assert(quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv)); - assert(data == NULL); - assert(lv == 55513); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_head(ql, "55513", 5); + struct quicklist_iter *iter = quicklist_iter_new(ql, 0, 1); + assert(iter); + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + assert(elem.value == NULL); + assert(elem.longval == 55513); + quicklist_iter_del(iter); ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + ql_release_iterator(iter); + quicklist_free(ql); } TEST("pop head 500 from 500") { - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(-2, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); ql_info(ql); for (int i = 0; i < 500; i++) { - unsigned char *data; - size_t sz; - long long lv; - int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); - assert(ret == 1); - assert(data != NULL); - assert(sz == 32); - if (strcmp(genstr("hello", 499 - i), (char *)data)) { - int size = sz; + struct quicklist_iter *iter = quicklist_iter_new(ql, 0, 1); + assert(iter); + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + assert(elem.value); + assert(elem.sz == 32); + if (strcmp(genstr("hello", 499 - i), (char *)elem.value)) { + int size = elem.sz; ERR("Pop'd value (%.*s) didn't equal original value (%s)", - size, data, genstr("hello", 499 - i)); + size, elem.value, genstr("hello", 499 - i)); } - zfree(data); + quicklist_iter_del(iter); + ql_release_iterator(iter); } ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + quicklist_free(ql); } TEST("pop head 5000 from 500") { - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(-2, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); for (int i = 0; i < 5000; i++) { - unsigned char *data; - size_t sz; - long long lv; - int ret = quicklistPop(ql, QUICKLIST_HEAD, &data, &sz, &lv); + struct quicklist_iter *iter = quicklist_iter_new(ql, 0, 1); if (i < 500) { - assert(ret == 1); - assert(data != NULL); - assert(sz == 32); - if (strcmp(genstr("hello", 499 - i), (char *)data)) { - int size = sz; + assert(iter); + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + assert(elem.value); + assert(elem.sz == 32); + if (strcmp(genstr("hello", 499 - i), (char *)elem.value)) { + int size = elem.sz; ERR("Pop'd value (%.*s) didn't equal original value " "(%s)", - size, data, genstr("hello", 499 - i)); + size, elem.value, genstr("hello", 499 - i)); } - zfree(data); + quicklist_iter_del(iter); + ql_release_iterator(iter); } else { - assert(ret == 0); + assert(iter == NULL); } } ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + quicklist_free(ql); } TEST("iterate forward over 500 list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); - quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD); - quicklistEntry entry; + quicklist_push_head(ql, genstr("hello", i), 32); + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, 0, 1); + struct quicklist_element elem; int i = 499, count = 0; - while (quicklistNext(iter, &entry)) { + while (quicklist_iter_next(iter)) { + quicklist_iter_get_element(iter, &elem); char *h = genstr("hello", i); - if (strcmp((char *)entry.value, h)) + if (strcmp((char *)elem.value, h)) ERR("value [%s] didn't match [%s] at position %d", - entry.value, h, i); + elem.value, h, i); i--; count++; } @@ -2281,135 +3239,122 @@ int quicklistTest(int argc, char *argv[], int flags) { ERR("Didn't iterate over exactly 500 elements (%d)", i); ql_verify(ql, 16, 500, 20, 32); ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } TEST("iterate reverse over 500 list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); - quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL); - quicklistEntry entry; + quicklist_push_head(ql, genstr("hello", i), 32); + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, -1, 0); + struct quicklist_element elem; int i = 0; - while (quicklistNext(iter, &entry)) { + while (quicklist_iter_next(iter)) { char *h = genstr("hello", i); - if (strcmp((char *)entry.value, h)) + quicklist_iter_get_element(iter, &elem); + if (strcmp((char *)elem.value, h)) ERR("value [%s] didn't match [%s] at position %d", - entry.value, h, i); + elem.value, h, i); i++; } if (i != 500) ERR("Didn't iterate over exactly 500 elements (%d)", i); ql_verify(ql, 16, 500, 20, 32); ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } TEST("insert after 1 element") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushHead(ql, "hello", 6); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - quicklistInsertAfter(iter, &entry, "abc", 4); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_head(ql, "hello", 6); + iter = quicklist_iter_new(ql, 0, 0); + quicklist_iter_add_after(iter, "abc", 4); ql_release_iterator(iter); ql_verify(ql, 1, 2, 2, 2); /* verify results */ - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - int sz = entry.sz; - if (strncmp((char *)entry.value, "hello", 5)) { - ERR("Value 0 didn't match, instead got: %.*s", sz, - entry.value); + struct quicklist_element elem; + quicklist_get_element(ql, 0, &elem); + int sz = elem.sz; + if (strncmp((char *)elem.value, "hello", 5)) { + ERR("Value 0 didn't match, instead got: %.*s", sz, elem.value); } - ql_release_iterator(iter); - iter = quicklistGetIteratorEntryAtIdx(ql, 1, &entry); - sz = entry.sz; - if (strncmp((char *)entry.value, "abc", 3)) { - ERR("Value 1 didn't match, instead got: %.*s", sz, - entry.value); + quicklist_get_element(ql, 1, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "abc", 3)) { + ERR("Value 1 didn't match, instead got: %.*s", sz, elem.value); } - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } TEST("insert before 1 element") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushHead(ql, "hello", 6); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - quicklistInsertBefore(iter, &entry, "abc", 4); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_head(ql, "hello", 6); + iter = quicklist_iter_new(ql, 0, 0); + quicklist_iter_add_before(iter, "abc", 4); ql_release_iterator(iter); ql_verify(ql, 1, 2, 2, 2); /* verify results */ - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - int sz = entry.sz; - if (strncmp((char *)entry.value, "abc", 3)) { - ERR("Value 0 didn't match, instead got: %.*s", sz, - entry.value); + struct quicklist_element elem; + quicklist_get_element(ql, 0, &elem); + int sz = elem.sz; + if (strncmp((char *)elem.value, "abc", 3)) { + ERR("Value 0 didn't match, instead got: %.*s", sz, elem.value); } - ql_release_iterator(iter); - iter = quicklistGetIteratorEntryAtIdx(ql, 1, &entry); - sz = entry.sz; - if (strncmp((char *)entry.value, "hello", 5)) { - ERR("Value 1 didn't match, instead got: %.*s", sz, - entry.value); + quicklist_get_element(ql, 1, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "hello", 5)) { + ERR("Value 1 didn't match, instead got: %.*s", sz, elem.value); } - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } TEST("insert head while head node is full") { - quicklist *ql = quicklistNew(4, options[_i]); - for (int i = 0; i < 10; i++) - quicklistPushTail(ql, genstr("hello", i), 6); - quicklistSetFill(ql, -1); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, -10, &entry); - char buf[4096] = {0}; - quicklistInsertBefore(iter, &entry, buf, 4096); - ql_release_iterator(iter); - ql_verify(ql, 4, 11, 1, 2); - quicklistRelease(ql); + struct quicklist *ql = quicklist_new(4, options[_i]); + for (int i = 0; i < 10; i++) + quicklist_push_tail(ql, genstr("hello", i), 6); + iter = quicklist_iter_new(ql, -10, 0); + char buf[4096] = {0}; + quicklist_iter_add_before(iter, buf, 4096); + ql_release_iterator(iter); + ql_verify(ql, 4, 11, 1, 2); + quicklist_free(ql); } TEST("insert tail while tail node is full") { - quicklist *ql = quicklistNew(4, options[_i]); - for (int i = 0; i < 10; i++) - quicklistPushHead(ql, genstr("hello", i), 6); - quicklistSetFill(ql, -1); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, -1, &entry); - char buf[4096] = {0}; - quicklistInsertAfter(iter, &entry, buf, 4096); - ql_release_iterator(iter); - ql_verify(ql, 4, 11, 2, 1); - quicklistRelease(ql); + struct quicklist *ql = quicklist_new(4, options[_i]); + for (int i = 0; i < 10; i++) + quicklist_push_head(ql, genstr("hello", i), 6); + iter = quicklist_iter_new(ql, -1, 0); + char buf[4096] = {0}; + quicklist_iter_add_after(iter, buf, 4096); + ql_release_iterator(iter); + ql_verify(ql, 4, 11, 2, 1); + quicklist_free(ql); } TEST_DESC("insert once in elements while iterating at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); - quicklistPushTail(ql, "abc", 3); - quicklistSetFill(ql, 1); - quicklistPushTail(ql, "def", 3); /* force to unique node */ - quicklistSetFill(ql, f); - quicklistPushTail(ql, "bob", 3); /* force to reset for +3 */ - quicklistPushTail(ql, "foo", 3); - quicklistPushTail(ql, "zoo", 3); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); + quicklist_push_tail(ql, "abc", 3); + quicklist_push_tail(ql, "def", 3); + quicklist_push_tail(ql, "bob", 3); + quicklist_push_tail(ql, "foo", 3); + quicklist_push_tail(ql, "zoo", 3); itrprintr(ql, 0); /* insert "bar" before "bob" while iterating over list. */ - quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD); - quicklistEntry entry; - while (quicklistNext(iter, &entry)) { - if (!strncmp((char *)entry.value, "bob", 3)) { + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, 0, 1); + struct quicklist_element elem; + while (quicklist_iter_next(iter)) { + quicklist_iter_get_element(iter, &elem); + if (!strncmp((char *)elem.value, "bob", 3)) { /* Insert as fill = 1 so it spills into new node. */ - quicklistInsertBefore(iter, &entry, "bar", 3); + quicklist_iter_add_before(iter, "bar", 3); break; /* didn't we fix insert-while-iterating? */ } } @@ -2417,75 +3362,71 @@ int quicklistTest(int argc, char *argv[], int flags) { itrprintr(ql, 0); /* verify results */ - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - int sz = entry.sz; - - if (strncmp((char *)entry.value, "abc", 3)) + quicklist_get_element(ql, 0, &elem); + int sz = elem.sz; + if (strncmp((char *)elem.value, "abc", 3)) ERR("Value 0 didn't match, instead got: %.*s", sz, - entry.value); - ql_release_iterator(iter); + elem.value); - iter = quicklistGetIteratorEntryAtIdx(ql, 1, &entry); - if (strncmp((char *)entry.value, "def", 3)) + quicklist_get_element(ql, 1, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "def", 3)) ERR("Value 1 didn't match, instead got: %.*s", sz, - entry.value); - ql_release_iterator(iter); + elem.value); - iter = quicklistGetIteratorEntryAtIdx(ql, 2, &entry); - if (strncmp((char *)entry.value, "bar", 3)) + quicklist_get_element(ql, 2, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "bar", 3)) ERR("Value 2 didn't match, instead got: %.*s", sz, - entry.value); - ql_release_iterator(iter); + elem.value); - iter = quicklistGetIteratorEntryAtIdx(ql, 3, &entry); - if (strncmp((char *)entry.value, "bob", 3)) + quicklist_get_element(ql, 3, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "bob", 3)) ERR("Value 3 didn't match, instead got: %.*s", sz, - entry.value); - ql_release_iterator(iter); + elem.value); - iter = quicklistGetIteratorEntryAtIdx(ql, 4, &entry); - if (strncmp((char *)entry.value, "foo", 3)) + quicklist_get_element(ql, 4, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "foo", 3)) ERR("Value 4 didn't match, instead got: %.*s", sz, - entry.value); - ql_release_iterator(iter); + elem.value); - iter = quicklistGetIteratorEntryAtIdx(ql, 5, &entry); - if (strncmp((char *)entry.value, "zoo", 3)) + quicklist_get_element(ql, 5, &elem); + sz = elem.sz; + if (strncmp((char *)elem.value, "zoo", 3)) ERR("Value 5 didn't match, instead got: %.*s", sz, - entry.value); - ql_release_iterator(iter); - quicklistRelease(ql); + elem.value); + quicklist_free(ql); } } TEST_DESC("insert [before] 250 new in middle of 500 elements at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i), 32); + quicklist_push_tail(ql, genstr("hello", i), 32); for (int i = 0; i < 250; i++) { - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 250, &entry); - quicklistInsertBefore(iter, &entry, genstr("abc", i), 32); + iter = quicklist_iter_new(ql, 250, 0); + quicklist_iter_add_before(iter, genstr("abc", i), 32); ql_release_iterator(iter); } if (fills[f] == 32) ql_verify(ql, 25, 750, 32, 20); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("insert [after] 250 new in middle of 500 elements at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); for (int i = 0; i < 250; i++) { - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 250, &entry); - quicklistInsertAfter(iter, &entry, genstr("abc", i), 32); + iter = quicklist_iter_new(ql, 250, 0); + quicklist_iter_add_after(iter, genstr("abc", i), 32); ql_release_iterator(iter); } @@ -2494,303 +3435,277 @@ int quicklistTest(int argc, char *argv[], int flags) { if (fills[f] == 32) ql_verify(ql, 26, 750, 20, 32); - quicklistRelease(ql); + quicklist_free(ql); } } TEST("duplicate empty list") { - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(-2, options[_i]); ql_verify(ql, 0, 0, 0, 0); - quicklist *copy = quicklistDup(ql); + struct quicklist *copy = quicklist_dup(ql); ql_verify(copy, 0, 0, 0, 0); - quicklistRelease(ql); - quicklistRelease(copy); + quicklist_free(ql); + quicklist_free(copy); } TEST("duplicate list of 1 element") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushHead(ql, genstr("hello", 3), 32); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_head(ql, genstr("hello", 3), 32); ql_verify(ql, 1, 1, 1, 1); - quicklist *copy = quicklistDup(ql); + struct quicklist *copy = quicklist_dup(ql); ql_verify(copy, 1, 1, 1, 1); - quicklistRelease(ql); - quicklistRelease(copy); + quicklist_free(ql); + quicklist_free(copy); } TEST("duplicate list of 500") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); ql_verify(ql, 16, 500, 20, 32); - quicklist *copy = quicklistDup(ql); + struct quicklist *copy = quicklist_dup(ql); ql_verify(copy, 16, 500, 20, 32); - quicklistRelease(ql); - quicklistRelease(copy); + quicklist_free(ql); + quicklist_free(copy); } for (int f = 0; f < fill_count; f++) { TEST_DESC("index 1,200 from 500 list at fill %d at compress %d", f, options[_i]) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 1, &entry); - if (strcmp((char *)entry.value, "hello2") != 0) - ERR("Value: %s", entry.value); - ql_release_iterator(iter); - - iter = quicklistGetIteratorEntryAtIdx(ql, 200, &entry); - if (strcmp((char *)entry.value, "hello201") != 0) - ERR("Value: %s", entry.value); - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); + struct quicklist_element elem; + quicklist_get_element(ql, 1, &elem); + if (strcmp((char *)elem.value, "hello2") != 0) + ERR("Value: %s", elem.value); + + quicklist_get_element(ql, 200, &elem); + if (strcmp((char *)elem.value, "hello201") != 0) + ERR("Value: %s", elem.value); + quicklist_free(ql); } TEST_DESC("index -1,-2 from 500 list at fill %d at compress %d", fills[f], options[_i]) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, -1, &entry); - if (strcmp((char *)entry.value, "hello500") != 0) - ERR("Value: %s", entry.value); - ql_release_iterator(iter); - - iter = quicklistGetIteratorEntryAtIdx(ql, -2, &entry); - if (strcmp((char *)entry.value, "hello499") != 0) - ERR("Value: %s", entry.value); - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); + struct quicklist_element elem; + quicklist_get_element(ql, -1, &elem); + if (strcmp((char *)elem.value, "hello500") != 0) + ERR("Value: %s", elem.value); + + quicklist_get_element(ql, -2, &elem); + if (strcmp((char *)elem.value, "hello499") != 0) + ERR("Value: %s", elem.value); + quicklist_free(ql); } TEST_DESC("index -100 from 500 list at fill %d at compress %d", fills[f], options[_i]) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, -100, &entry); - if (strcmp((char *)entry.value, "hello401") != 0) - ERR("Value: %s", entry.value); - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); + struct quicklist_element elem; + quicklist_get_element(ql, -100, &elem); + if (strcmp((char *)elem.value, "hello401") != 0) + ERR("Value: %s", elem.value); + quicklist_free(ql); } TEST_DESC("index too big +1 from 50 list at fill %d at compress %d", fills[f], options[_i]) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); for (int i = 0; i < 50; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); - quicklistEntry entry; - int sz = entry.sz; - iter = quicklistGetIteratorEntryAtIdx(ql, 50, &entry); - if (iter) - ERR("Index found at 50 with 50 list: %.*s", sz, - entry.value); - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); + struct quicklist_element elem; + iter = quicklist_iter_new(ql, 50, 0); + if (iter) { + quicklist_iter_get_element(iter, &elem); + int sz = elem.sz; + ERR("Index found at 50 with 50 list: %.*s", sz, + elem.value); + } + quicklist_free(ql); } } TEST("delete range empty list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistDelRange(ql, 5, 20); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_del(ql, 5, 20); ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete range of entire node in list of one node") { - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(-2, options[_i]); for (int i = 0; i < 32; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); ql_verify(ql, 1, 32, 32, 32); - quicklistDelRange(ql, 0, 32); + quicklist_del(ql, 0, 32); ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete range of entire node with overflow counts") { - quicklist *ql = quicklistNew(-2, options[_i]); + struct quicklist *ql = quicklist_new(-2, options[_i]); for (int i = 0; i < 32; i++) - quicklistPushHead(ql, genstr("hello", i), 32); + quicklist_push_head(ql, genstr("hello", i), 32); ql_verify(ql, 1, 32, 32, 32); - quicklistDelRange(ql, 0, 128); + quicklist_del(ql, 0, 128); ql_verify(ql, 0, 0, 0, 0); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete middle 100 of 500 list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); ql_verify(ql, 16, 500, 32, 20); - quicklistDelRange(ql, 200, 100); - ql_verify(ql, 14, 400, 32, 20); - quicklistRelease(ql); + quicklist_del(ql, 200, 100); + ql_verify(ql, 13, 400, 32, 20); + quicklist_free(ql); } TEST("delete less than fill but across nodes") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); ql_verify(ql, 16, 500, 32, 20); - quicklistDelRange(ql, 60, 10); + quicklist_del(ql, 60, 10); ql_verify(ql, 16, 490, 32, 20); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete negative 1 from 500 list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); ql_verify(ql, 16, 500, 32, 20); - quicklistDelRange(ql, -1, 1); + quicklist_del(ql, -1, 1); ql_verify(ql, 16, 499, 32, 19); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete negative 1 from 500 list with overflow counts") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); ql_verify(ql, 16, 500, 32, 20); - quicklistDelRange(ql, -1, 128); + quicklist_del(ql, -1, 128); ql_verify(ql, 16, 499, 32, 19); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete negative 100 from 500 list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 500; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); - quicklistDelRange(ql, -100, 100); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); + quicklist_del(ql, -100, 100); ql_verify(ql, 13, 400, 32, 16); - quicklistRelease(ql); + quicklist_free(ql); } TEST("delete -10 count 5 from 50 list") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); for (int i = 0; i < 50; i++) - quicklistPushTail(ql, genstr("hello", i + 1), 32); + quicklist_push_tail(ql, genstr("hello", i + 1), 32); ql_verify(ql, 2, 50, 32, 18); - quicklistDelRange(ql, -10, 5); + quicklist_del(ql, -10, 5); ql_verify(ql, 2, 45, 32, 13); - quicklistRelease(ql); + quicklist_free(ql); } TEST("numbers only list read") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushTail(ql, "1111", 4); - quicklistPushTail(ql, "2222", 4); - quicklistPushTail(ql, "3333", 4); - quicklistPushTail(ql, "4444", 4); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_tail(ql, "1111", 4); + quicklist_push_tail(ql, "2222", 4); + quicklist_push_tail(ql, "3333", 4); + quicklist_push_tail(ql, "4444", 4); ql_verify(ql, 1, 4, 4, 4); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - if (entry.longval != 1111) - ERR("Not 1111, %lld", entry.longval); - ql_release_iterator(iter); + struct quicklist_element elem; + quicklist_get_element(ql, 0, &elem); + if (elem.longval != 1111) + ERR("Not 1111, %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, 1, &entry); - if (entry.longval != 2222) - ERR("Not 2222, %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, 1, &elem); + if (elem.longval != 2222) + ERR("Not 2222, %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, 2, &entry); - if (entry.longval != 3333) - ERR("Not 3333, %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, 2, &elem); + if (elem.longval != 3333) + ERR("Not 3333, %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, 3, &entry); - if (entry.longval != 4444) - ERR("Not 4444, %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, 3, &elem); + if (elem.longval != 4444) + ERR("Not 4444, %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, 4, &entry); - if (iter) - ERR("Index past elements: %lld", entry.longval); - ql_release_iterator(iter); + if (quicklist_get_element(ql, 4, &elem)) + ERR("Index past elements: %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, -1, &entry); - if (entry.longval != 4444) - ERR("Not 4444 (reverse), %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, -1, &elem); + if (elem.longval != 4444) + ERR("Not 4444 (reverse), %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, -2, &entry); - if (entry.longval != 3333) - ERR("Not 3333 (reverse), %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, -2, &elem); + if (elem.longval != 3333) + ERR("Not 3333 (reverse), %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, -3, &entry); - if (entry.longval != 2222) - ERR("Not 2222 (reverse), %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, -3, &elem); + if (elem.longval != 2222) + ERR("Not 2222 (reverse), %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, -4, &entry); - if (entry.longval != 1111) - ERR("Not 1111 (reverse), %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, -4, &elem); + if (elem.longval != 1111) + ERR("Not 1111 (reverse), %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, -5, &entry); - if (iter) - ERR("Index past elements (reverse), %lld", entry.longval); - ql_release_iterator(iter); - quicklistRelease(ql); + if (quicklist_get_element(ql, -5, &elem)) + ERR("Index past elements (reverse), %lld", elem.longval); + quicklist_free(ql); } TEST("numbers larger list read") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistSetFill(ql, 32); + struct quicklist *ql = quicklist_new(32, options[_i]); char num[32]; long long nums[5000]; for (int i = 0; i < 5000; i++) { nums[i] = -5157318210846258176 + i; int sz = ll2string(num, sizeof(num), nums[i]); - quicklistPushTail(ql, num, sz); + quicklist_push_tail(ql, num, sz); } - quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20); - quicklistEntry entry; + quicklist_push_tail(ql, "xxxxxxxxxxxxxxxxxxxx", 20); + struct quicklist_element elem; for (int i = 0; i < 5000; i++) { - iter = quicklistGetIteratorEntryAtIdx(ql, i, &entry); - if (entry.longval != nums[i]) + quicklist_get_element(ql, i, &elem); + if (elem.longval != nums[i]) ERR("[%d] Not longval %lld but rather %lld", i, nums[i], - entry.longval); - entry.longval = 0xdeadbeef; - ql_release_iterator(iter); + elem.longval); + elem.longval = 0xdeadbeef; } - iter = quicklistGetIteratorEntryAtIdx(ql, 5000, &entry); - if (strncmp((char *)entry.value, "xxxxxxxxxxxxxxxxxxxx", 20)) - ERR("String val not match: %s", entry.value); + quicklist_get_element(ql, 5000, &elem); + if (strncmp((char *)elem.value, "xxxxxxxxxxxxxxxxxxxx", 20)) + ERR("String val not match: %s", elem.value); ql_verify(ql, 157, 5001, 32, 9); - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } TEST("numbers larger list read B") { - quicklist *ql = quicklistNew(-2, options[_i]); - quicklistPushTail(ql, "99", 2); - quicklistPushTail(ql, "98", 2); - quicklistPushTail(ql, "xxxxxxxxxxxxxxxxxxxx", 20); - quicklistPushTail(ql, "96", 2); - quicklistPushTail(ql, "95", 2); - quicklistReplaceAtIndex(ql, 1, "foo", 3); - quicklistReplaceAtIndex(ql, -1, "bar", 3); - quicklistRelease(ql); + struct quicklist *ql = quicklist_new(-2, options[_i]); + quicklist_push_tail(ql, "99", 2); + quicklist_push_tail(ql, "98", 2); + quicklist_push_tail(ql, "xxxxxxxxxxxxxxxxxxxx", 20); + quicklist_push_tail(ql, "96", 2); + quicklist_push_tail(ql, "95", 2); + quicklist_replace(ql, 1, "foo", 3); + quicklist_replace(ql, -1, "bar", 3); + quicklist_free(ql); } TEST_DESC("lrem test at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); char *words[] = {"abc", "foo", "bar", "foobar", "foobared", "zap", "bar", "test", "foo"}; char *result[] = {"abc", "foo", "foobar", "foobared", @@ -2798,44 +3713,45 @@ int quicklistTest(int argc, char *argv[], int flags) { char *resultB[] = {"abc", "foo", "foobar", "foobared", "zap", "test"}; for (int i = 0; i < 9; i++) - quicklistPushTail(ql, words[i], strlen(words[i])); + quicklist_push_tail(ql, words[i], strlen(words[i])); /* lrem 0 bar */ - quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD); - quicklistEntry entry; + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, 0, 1); + struct quicklist_element elem; int i = 0; - while (quicklistNext(iter, &entry)) { - if (quicklistCompare(&entry, (unsigned char *)"bar", 3)) { - quicklistDelEntry(iter, &entry); + while (quicklist_iter_next(iter)) { + if (quicklist_iter_equal(iter, (unsigned char *)"bar", 3)) { + quicklist_iter_del(iter); } i++; } ql_release_iterator(iter); /* check result of lrem 0 bar */ - iter = quicklistGetIterator(ql, AL_START_HEAD); + iter = quicklist_iter_new_ahead(ql, 0, 1); i = 0; - while (quicklistNext(iter, &entry)) { + while (quicklist_iter_next(iter)) { + quicklist_iter_get_element(iter, &elem); + int sz = elem.sz; /* Result must be: abc, foo, foobar, foobared, zap, test, * foo */ - int sz = entry.sz; - if (strncmp((char *)entry.value, result[i], entry.sz)) { + if (strncmp((char *)elem.value, result[i], sz)) { ERR("No match at position %d, got %.*s instead of %s", - i, sz, entry.value, result[i]); + i, sz, elem.value, result[i]); } i++; } ql_release_iterator(iter); - quicklistPushTail(ql, "foo", 3); + quicklist_push_tail(ql, "foo", 3); /* lrem -2 foo */ - iter = quicklistGetIterator(ql, AL_START_TAIL); + iter = quicklist_iter_new_ahead(ql, -1, 0); i = 0; int del = 2; - while (quicklistNext(iter, &entry)) { - if (quicklistCompare(&entry, (unsigned char *)"foo", 3)) { - quicklistDelEntry(iter, &entry); + while (quicklist_iter_next(iter)) { + if (quicklist_iter_equal(iter, (unsigned char *)"foo", 3)) { + quicklist_iter_del(iter); del--; } if (!del) @@ -2848,41 +3764,40 @@ int quicklistTest(int argc, char *argv[], int flags) { /* (we're ignoring the '2' part and still deleting all foo * because * we only have two foo) */ - iter = quicklistGetIterator(ql, AL_START_TAIL); + iter = quicklist_iter_new_ahead(ql, -1, 0); i = 0; size_t resB = sizeof(resultB) / sizeof(*resultB); - while (quicklistNext(iter, &entry)) { + while (quicklist_iter_next(iter)) { /* Result must be: abc, foo, foobar, foobared, zap, test, * foo */ - int sz = entry.sz; - if (strncmp((char *)entry.value, resultB[resB - 1 - i], - sz)) { + quicklist_iter_get_element(iter, &elem); + int sz = elem.sz; + if (strncmp((char *)elem.value, resultB[resB - 1 - i], sz)) { ERR("No match at position %d, got %.*s instead of %s", - i, sz, entry.value, resultB[resB - 1 - i]); + i, sz, elem.value, resultB[resB - 1 - i]); } i++; } ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("iterate reverse + delete at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); - quicklistPushTail(ql, "abc", 3); - quicklistPushTail(ql, "def", 3); - quicklistPushTail(ql, "hij", 3); - quicklistPushTail(ql, "jkl", 3); - quicklistPushTail(ql, "oop", 3); - - quicklistEntry entry; - quicklistIter *iter = quicklistGetIterator(ql, AL_START_TAIL); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); + quicklist_push_tail(ql, "abc", 3); + quicklist_push_tail(ql, "def", 3); + quicklist_push_tail(ql, "hij", 3); + quicklist_push_tail(ql, "jkl", 3); + quicklist_push_tail(ql, "oop", 3); + + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, -1, 0); int i = 0; - while (quicklistNext(iter, &entry)) { - if (quicklistCompare(&entry, (unsigned char *)"hij", 3)) { - quicklistDelEntry(iter, &entry); + while (quicklist_iter_next(iter)) { + if (quicklist_iter_equal(iter, (unsigned char *)"hij", 3)) { + quicklist_iter_del(iter); } i++; } @@ -2892,74 +3807,70 @@ int quicklistTest(int argc, char *argv[], int flags) { ERR("Didn't iterate 5 times, iterated %d times.", i); /* Check results after deletion of "hij" */ - iter = quicklistGetIterator(ql, AL_START_HEAD); + iter = quicklist_iter_new_ahead(ql, 0, 1); i = 0; char *vals[] = {"abc", "def", "jkl", "oop"}; - while (quicklistNext(iter, &entry)) { - if (!quicklistCompare(&entry, (unsigned char *)vals[i], - 3)) { + while (quicklist_iter_next(iter)) { + if (!quicklist_iter_equal(iter, (unsigned char *)vals[i], 3)) { ERR("Value at %d didn't match %s\n", i, vals[i]); } i++; } ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("iterator at index test at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); char num[32]; long long nums[5000]; for (int i = 0; i < 760; i++) { nums[i] = -5157318210846258176 + i; int sz = ll2string(num, sizeof(num), nums[i]); - quicklistPushTail(ql, num, sz); + quicklist_push_tail(ql, num, sz); } - quicklistEntry entry; - quicklistIter *iter = - quicklistGetIteratorAtIdx(ql, AL_START_HEAD, 437); + struct quicklist_element elem; + struct quicklist_iter *iter = quicklist_iter_new_ahead(ql, 437, 1); int i = 437; - while (quicklistNext(iter, &entry)) { - if (entry.longval != nums[i]) - ERR("Expected %lld, but got %lld", entry.longval, - nums[i]); + while (quicklist_iter_next(iter)) { + quicklist_iter_get_element(iter, &elem); + if (!elem.value && elem.longval != nums[i]) + ERR("Expected %lld, but got %lld", nums[i], elem.longval); i++; } ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("ltrim test A at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); char num[32]; long long nums[5000]; for (int i = 0; i < 32; i++) { nums[i] = -5157318210846258176 + i; int sz = ll2string(num, sizeof(num), nums[i]); - quicklistPushTail(ql, num, sz); + quicklist_push_tail(ql, num, sz); } if (fills[f] == 32) ql_verify(ql, 1, 32, 32, 32); /* ltrim 25 53 (keep [25,32] inclusive = 7 remaining) */ - quicklistDelRange(ql, 0, 25); - quicklistDelRange(ql, 0, 0); - quicklistEntry entry; + quicklist_del(ql, 0, 25); + struct quicklist_element elem; for (int i = 0; i < 7; i++) { - iter = quicklistGetIteratorEntryAtIdx(ql, i, &entry); - if (entry.longval != nums[25 + i]) + quicklist_get_element(ql, i, &elem); + if (!elem.value && elem.longval != nums[25 + i]) ERR("Deleted invalid range! Expected %lld but got " "%lld", - entry.longval, nums[25 + i]); - ql_release_iterator(iter); + elem.longval, nums[25 + i]); } if (fills[f] == 32) ql_verify(ql, 1, 7, 7, 7); - quicklistRelease(ql); + quicklist_free(ql); } } @@ -2967,97 +3878,91 @@ int quicklistTest(int argc, char *argv[], int flags) { for (int f = 0; f < fill_count; f++) { /* Force-disable compression because our 33 sequential * integers don't compress and the check always fails. */ - quicklist *ql = quicklistNew(fills[f], QUICKLIST_NOCOMPRESS); + struct quicklist *ql = quicklist_new(fills[f], 0); char num[32]; long long nums[5000]; for (int i = 0; i < 33; i++) { nums[i] = i; int sz = ll2string(num, sizeof(num), nums[i]); - quicklistPushTail(ql, num, sz); + quicklist_push_tail(ql, num, sz); } if (fills[f] == 32) ql_verify(ql, 2, 33, 32, 1); /* ltrim 5 16 (keep [5,16] inclusive = 12 remaining) */ - quicklistDelRange(ql, 0, 5); - quicklistDelRange(ql, -16, 16); + quicklist_del(ql, 0, 5); + quicklist_del(ql, -16, 16); if (fills[f] == 32) ql_verify(ql, 1, 12, 12, 12); - quicklistEntry entry; + struct quicklist_element elem; - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - if (entry.longval != 5) - ERR("A: longval not 5, but %lld", entry.longval); - ql_release_iterator(iter); + quicklist_get_element(ql, 0, &elem); + if (!elem.value && elem.longval != 5) + ERR("A: longval not 5, but %lld", elem.longval); - iter = quicklistGetIteratorEntryAtIdx(ql, -1, &entry); - if (entry.longval != 16) - ERR("B! got instead: %lld", entry.longval); - quicklistPushTail(ql, "bobobob", 7); - ql_release_iterator(iter); + quicklist_get_element(ql, -1, &elem); + if (!elem.value && elem.longval != 16) + ERR("B! got instead: %lld", elem.longval); + quicklist_push_tail(ql, "bobobob", 7); - iter = quicklistGetIteratorEntryAtIdx(ql, -1, &entry); - int sz = entry.sz; - if (strncmp((char *)entry.value, "bobobob", 7)) + quicklist_get_element(ql, -1, &elem); + int sz = elem.sz; + if (strncmp((char *)elem.value, "bobobob", 7)) ERR("Tail doesn't match bobobob, it's %.*s instead", - sz, entry.value); - ql_release_iterator(iter); + sz, elem.value); for (int i = 0; i < 12; i++) { - iter = quicklistGetIteratorEntryAtIdx(ql, i, &entry); - if (entry.longval != nums[5 + i]) + quicklist_get_element(ql, i, &elem); + if (!elem.value && elem.longval != nums[5 + i]) ERR("Deleted invalid range! Expected %lld but got " "%lld", - entry.longval, nums[5 + i]); - ql_release_iterator(iter); + elem.longval, nums[5 + i]); } - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("ltrim test C at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); char num[32]; long long nums[5000]; for (int i = 0; i < 33; i++) { nums[i] = -5157318210846258176 + i; int sz = ll2string(num, sizeof(num), nums[i]); - quicklistPushTail(ql, num, sz); + quicklist_push_tail(ql, num, sz); } if (fills[f] == 32) ql_verify(ql, 2, 33, 32, 1); /* ltrim 3 3 (keep [3,3] inclusive = 1 remaining) */ - quicklistDelRange(ql, 0, 3); - quicklistDelRange(ql, -29, - 4000); /* make sure not loop forever */ + quicklist_del(ql, 0, 3); + quicklist_del(ql, -29, 4000); /* make sure not loop forever */ if (fills[f] == 32) ql_verify(ql, 1, 1, 1, 1); - quicklistEntry entry; - iter = quicklistGetIteratorEntryAtIdx(ql, 0, &entry); - if (entry.longval != -5157318210846258173) + struct quicklist_element elem; + quicklist_get_element(ql, 0, &elem); + if (elem.longval != -5157318210846258173) ERROR; - ql_release_iterator(iter); - quicklistRelease(ql); + quicklist_free(ql); } } TEST_DESC("ltrim test D at compress %d", options[_i]) { for (int f = 0; f < fill_count; f++) { - quicklist *ql = quicklistNew(fills[f], options[_i]); + struct quicklist *ql = quicklist_new(fills[f], options[_i]); char num[32]; long long nums[5000]; for (int i = 0; i < 33; i++) { nums[i] = -5157318210846258176 + i; int sz = ll2string(num, sizeof(num), nums[i]); - quicklistPushTail(ql, num, sz); + quicklist_push_tail(ql, num, sz); } if (fills[f] == 32) ql_verify(ql, 2, 33, 32, 1); - quicklistDelRange(ql, -12, 3); + quicklist_del(ql, -12, 3); if (ql->count != 30) ERR("Didn't delete exactly three elements! Count is: %lu", ql->count); - quicklistRelease(ql); + quicklist_free(ql); } } @@ -3075,52 +3980,25 @@ int quicklistTest(int argc, char *argv[], int flags) { for (int f = 0; f < fill_count; f++) { for (int depth = 1; depth < 40; depth++) { /* skip over many redundant test cases */ - quicklist *ql = quicklistNew(fills[f], depth); + struct quicklist *ql = quicklist_new(fills[f], depth); for (int i = 0; i < list_sizes[list]; i++) { - quicklistPushTail(ql, genstr("hello TAIL", i + 1), 64); - quicklistPushHead(ql, genstr("hello HEAD", i + 1), 64); + quicklist_push_tail(ql, genstr("hello TAIL", i + 1), 64); + quicklist_push_head(ql, genstr("hello HEAD", i + 1), 64); } for (int step = 0; step < 2; step++) { /* test remove node */ if (step == 1) { for (int i = 0; i < list_sizes[list] / 2; i++) { - unsigned char *data; - assert(quicklistPop(ql, QUICKLIST_HEAD, &data, - NULL, NULL)); - zfree(data); - assert(quicklistPop(ql, QUICKLIST_TAIL, &data, - NULL, NULL)); - zfree(data); - } - } - quicklistNode *node = ql->head; - unsigned int low_raw = ql->compress; - unsigned int high_raw = ql->len - ql->compress; - - for (unsigned int at = 0; at < ql->len; - at++, node = node->next) { - if (at < low_raw || at >= high_raw) { - if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) { - ERR("Incorrect compression: node %d is " - "compressed at depth %d ((%u, %u); total " - "nodes: %lu; size: %zu)", - at, depth, low_raw, high_raw, ql->len, - node->sz); - } - } else { - if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) { - ERR("Incorrect non-compression: node %d is NOT " - "compressed at depth %d ((%u, %u); total " - "nodes: %lu; size: %zu; attempted: %d)", - at, depth, low_raw, high_raw, ql->len, - node->sz, node->attempted_compress); - } + assert(quicklist_del(ql, 0, 1)); + assert(quicklist_del(ql, -1, 1)); } } + + err += ql_verify_compress(ql); } - quicklistRelease(ql); + quicklist_free(ql); } } } @@ -3135,114 +4013,129 @@ int quicklistTest(int argc, char *argv[], int flags) { printf("\n"); TEST("bookmark get updated to next item") { - quicklist *ql = quicklistNew(1, 0); - quicklistPushTail(ql, "1", 1); - quicklistPushTail(ql, "2", 1); - quicklistPushTail(ql, "3", 1); - quicklistPushTail(ql, "4", 1); - quicklistPushTail(ql, "5", 1); - assert(ql->len==5); + struct quicklist *ql = quicklist_new(1, 0); + quicklist_push_tail(ql, "1", 1); + quicklist_push_tail(ql, "2", 1); + quicklist_push_tail(ql, "3", 1); + quicklist_push_tail(ql, "4", 1); + quicklist_push_tail(ql, "5", 1); + assert(quicklist_node_count(ql) == 5); + + struct quicklist_partition *head_p, *tail_p; + struct quicklist_node *head, *tail; + quicklist_first_node(ql, &head_p, &head); + quicklist_last_node(ql, &tail_p, &tail); + + struct quicklist_partition *head_next_p, *tail_prev_p; + struct quicklist_node *head_next, *tail_prev; + quicklist_next(head_p, head, &head_next_p, &head_next); + quicklist_prev(tail_p, tail, &tail_prev_p, &tail_prev); + assert(head_next); + assert(tail_prev); + /* add two bookmarks, one pointing to the node before the last. */ - assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next)); - assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev)); + assert(quicklist_bm_create(&ql, "_dummy", head_next)); + assert(quicklist_bm_create(&ql, "_test", tail_prev)); /* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */ - assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev); - assert(quicklistDelRange(ql, -2, 1)); - assert(quicklistBookmarkFind(ql, "_test") == ql->tail); + assert(quicklist_bm_find(ql, "_test") == tail_prev); + assert(quicklist_del(ql, -2, 1)); + quicklist_last_node(ql, &tail_p, &tail); + assert(quicklist_bm_find(ql, "_test") == tail); /* delete the last node, and see that the bookmark was deleted. */ - assert(quicklistDelRange(ql, -1, 1)); - assert(quicklistBookmarkFind(ql, "_test") == NULL); + assert(quicklist_del(ql, -1, 1)); + assert(quicklist_bm_find(ql, "_test") == NULL); /* test that other bookmarks aren't affected */ - assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next); - assert(quicklistBookmarkFind(ql, "_missing") == NULL); - assert(ql->len==3); - quicklistBookmarksClear(ql); /* for coverage */ - assert(quicklistBookmarkFind(ql, "_dummy") == NULL); - quicklistRelease(ql); + quicklist_first_node(ql, &head_p, &head); + quicklist_next(head_p, head, &head_next_p, &head_next); + assert(quicklist_bm_find(ql, "_dummy") == head_next); + assert(quicklist_bm_find(ql, "_missing") == NULL); + assert(quicklist_node_count(ql) == 3); + quicklist_bm_clear(ql); /* for coverage */ + assert(quicklist_bm_find(ql, "_dummy") == NULL); + quicklist_free(ql); } TEST("bookmark limit") { int i; - quicklist *ql = quicklistNew(1, 0); - quicklistPushHead(ql, "1", 1); - for (i=0; ihead)); + struct quicklist *ql = quicklist_new(1, 0); + quicklist_push_head(ql, "1", 1); + struct quicklist_partition *head_p; + struct quicklist_node *head; + quicklist_first_node(ql, &head_p, &head); + for (i=0; ihead)); + assert(!quicklist_bm_create(&ql, "_test", head)); /* delete one and see that we can now create another */ - assert(quicklistBookmarkDelete(ql, "0")); - assert(quicklistBookmarkCreate(&ql, "_test", ql->head)); + assert(quicklist_bm_delete(ql, "0")); + assert(quicklist_bm_create(&ql, "_test", head)); /* delete one and see that the rest survive */ - assert(quicklistBookmarkDelete(ql, "_test")); - for (i=1; ihead); + assert(quicklist_bm_delete(ql, "_test")); + quicklist_first_node(ql, &head_p, &head); + for (i=1; ientry = lpNew(0); - - /* Just to avoid triggering the assertion in __quicklistCompressNode(), - * it disables the passing of quicklist head or tail node. */ - node->prev = quicklistCreateNode(); - node->next = quicklistCreateNode(); + struct quicklist_node *node = zmalloc(sizeof(*node)); + node->carry = lpNew(0); + node->raw_sz = lpBytes(node->carry); + node->container = QUICKLIST_NODE_CONTAINER_PACKED; + node->count = 0; + node->raw = 1; - /* Create a rand string */ - size_t sz = (1 << 25); /* 32MB per one entry */ - unsigned char *s = zmalloc(sz); - randstring(s, sz); - - /* Keep filling the node, until it reaches 1GB */ - for (int i = 0; i < 32; i++) { - node->entry = lpAppend(node->entry, s, sz); - quicklistNodeUpdateSz(node); - - long long start = mstime(); - assert(__quicklistCompressNode(node)); - assert(__quicklistDecompressNode(node)); - printf("Compress and decompress: %zu MB in %.2f seconds.\n", - node->sz/1024/1024, (float)(mstime() - start) / 1000); - } - - zfree(s); - zfree(node->prev); - zfree(node->next); - zfree(node->entry); - zfree(node); + /* Create a rand string */ + size_t sz = (1 << 25); /* 32MB per one entry */ + unsigned char *s = zmalloc(sz); + randstring(s, sz); + + /* Keep filling the node, until it reaches 1GB */ + for (int i = 0; i < 32; i++) { + node->carry = lpAppend(node->carry, s, sz); + node->raw_sz = lpBytes(node->carry); + + long long start = mstime(); + quicklist_n_compress_raw(node); + assert(quicklist_n_decompress(node)); + printf("Compress and decompress: %zu MB in %.2f seconds.\n", + node->raw_sz/1024/1024, (float)(mstime() - start) / 1000); + } + + zfree(s); + quicklist_n_free(node); } #if ULONG_MAX >= 0xffffffffffffffff TEST("compress and decomress quicklist plain node large than UINT32_MAX") { - size_t sz = (1ull << 32); - unsigned char *s = zmalloc(sz); - randstring(s, sz); - memcpy(s, "helloworld", 10); - memcpy(s + sz - 10, "1234567890", 10); - - quicklistNode *node = __quicklistCreatePlainNode(s, sz); - - /* Just to avoid triggering the assertion in __quicklistCompressNode(), - * it disables the passing of quicklist head or tail node. */ - node->prev = quicklistCreateNode(); - node->next = quicklistCreateNode(); - - long long start = mstime(); - assert(__quicklistCompressNode(node)); - assert(__quicklistDecompressNode(node)); - printf("Compress and decompress: %zu MB in %.2f seconds.\n", - node->sz/1024/1024, (float)(mstime() - start) / 1000); - - assert(memcmp(node->entry, "helloworld", 10) == 0); - assert(memcmp(node->entry + sz - 10, "1234567890", 10) == 0); - zfree(node->prev); - zfree(node->next); - zfree(node->entry); - zfree(node); + size_t sz = (1ull << 32); + unsigned char *s = zmalloc(sz); + randstring(s, sz); + memcpy(s, "helloworld", 10); + memcpy(s + sz - 10, "1234567890", 10); + + struct quicklist_node *node = zmalloc(sizeof(*node)); + node->carry = zmalloc(sz); + memcpy(node->carry, s, sz); + node->raw_sz = sz; + node->container = QUICKLIST_NODE_CONTAINER_PLAIN; + node->count = 1; + node->raw = 1; + + long long start = mstime(); + quicklist_n_compress_raw(node); + assert(quicklist_n_decompress(node)); + printf("Compress and decompress: %zu MB in %.2f seconds.\n", + node->raw_sz/1024/1024, (float)(mstime() - start) / 1000); + + assert(memcmp(node->carry, "helloworld", 10) == 0); + assert(memcmp((char *)node->carry + sz - 10, "1234567890", 10) == 0); + zfree(s); + quicklist_n_free(node); } #endif } @@ -3254,4 +4147,4 @@ int quicklistTest(int argc, char *argv[], int flags) { return err; } -#endif +#endif \ No newline at end of file diff --git a/src/quicklist.h b/src/quicklist.h index f17834b9943ab..9c915036427de 100644 --- a/src/quicklist.h +++ b/src/quicklist.h @@ -29,186 +29,229 @@ */ #include // for UINTPTR_MAX +#include #ifndef __QUICKLIST_H__ #define __QUICKLIST_H__ -/* Node, quicklist, and Iterator are the only data structures used currently. */ - -/* 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 lp bytes is 65k, so max count actually < 32k). - * encoding: 2 bits, RAW=1, LZF=2. - * container: 2 bits, PLAIN=1 (a single item as char array), PACKED=2 (listpack with multiple items). - * 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 */ -typedef struct quicklistNode { - struct quicklistNode *prev; - struct quicklistNode *next; - unsigned char *entry; - size_t sz; /* entry size in bytes */ - unsigned int count : 16; /* count of items in listpack */ - unsigned int encoding : 2; /* RAW==1 or LZF==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 dont_compress : 1; /* prevent compression of entry that will be used later */ - unsigned int extra : 9; /* more bits to steal for future usage */ -} quicklistNode; - -/* quicklistLZF is a 8+N byte struct holding 'sz' followed by 'compressed'. - * 'sz' is byte length of 'compressed' field. - * 'compressed' is LZF data with total (compressed) length 'sz' - * NOTE: uncompressed length is stored in quicklistNode->sz. - * When quicklistNode->entry is compressed, node->entry points to a quicklistLZF */ -typedef struct quicklistLZF { - size_t sz; /* LZF size in bytes*/ - char compressed[]; -} quicklistLZF; - -/* Bookmarks are padded with realloc at the end of of the quicklist struct. - * They should only be used for very big lists if thousands of nodes were the - * excess memory usage is negligible, and there's a real need to iterate on them - * in portions. - * When not used, they don't add any memory overhead, but when used and then - * deleted, some overhead remains (to avoid resonance). - * The number of bookmarks used should be kept to minimum since it also adds - * overhead on node deletion (searching for a bookmark to update). */ -typedef struct quicklistBookmark { - quicklistNode *node; - char *name; -} quicklistBookmark; - #if UINTPTR_MAX == 0xffffffff /* 32-bit */ -# define QL_FILL_BITS 14 -# define QL_COMP_BITS 14 -# define QL_BM_BITS 4 +#define QUICKLIST_FILL_BITS 14 #elif UINTPTR_MAX == 0xffffffffffffffff /* 64-bit */ -# define QL_FILL_BITS 16 -# define QL_COMP_BITS 16 -# define QL_BM_BITS 4 /* we can encode more, but we rather limit the user - since they cause performance degradation. */ +#define QUICKLIST_FILL_BITS 16 #else -# error unknown arch bits count +#error unknown arch bits count #endif -/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist. - * 'count' is the number of total entries. - * 'len' is the number of quicklist nodes. - * 'compress' is: 0 if compression disabled, otherwise it's the number - * of quicklistNodes to leave uncompressed at ends of quicklist. - * 'fill' is the user-requested (or default) fill factor. - * 'bookmarks are an optional feature that is used by realloc this struct, - * so that they don't consume memory when not used. */ -typedef struct quicklist { - quicklistNode *head; - quicklistNode *tail; - unsigned long count; /* total count of all entries in all listpacks */ - unsigned long len; /* number of quicklistNodes */ - signed 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 */ - unsigned int bookmark_count: QL_BM_BITS; - quicklistBookmark bookmarks[]; -} quicklist; - -typedef struct quicklistIter { - quicklist *quicklist; - quicklistNode *current; - unsigned char *zi; /* points to the current element */ - long offset; /* offset in current listpack */ - int direction; -} quicklistIter; - -typedef struct quicklistEntry { - const quicklist *quicklist; - quicklistNode *node; - unsigned char *zi; - unsigned char *value; - long long longval; - size_t sz; - int offset; -} quicklistEntry; - -#define QUICKLIST_HEAD 0 -#define QUICKLIST_TAIL -1 - -/* quicklist node encodings */ -#define QUICKLIST_NODE_ENCODING_RAW 1 -#define QUICKLIST_NODE_ENCODING_LZF 2 - -/* quicklist compression disable */ -#define QUICKLIST_NOCOMPRESS 0 - -/* quicklist node container formats */ +#define QUICKLIST_NODE_CONTAINER_BITS 2 +#define QUICKLIST_NODE_RAW_BITS 1 +static_assert(QUICKLIST_NODE_CONTAINER_BITS + + QUICKLIST_NODE_RAW_BITS + + QUICKLIST_FILL_BITS <= 32); + #define QUICKLIST_NODE_CONTAINER_PLAIN 1 #define QUICKLIST_NODE_CONTAINER_PACKED 2 -#define QL_NODE_IS_PLAIN(node) ((node)->container == QUICKLIST_NODE_CONTAINER_PLAIN) - -#define quicklistNodeIsCompressed(node) \ - ((node)->encoding == QUICKLIST_NODE_ENCODING_LZF) - -/* Prototypes */ -quicklist *quicklistCreate(void); -quicklist *quicklistNew(int fill, int compress); -void quicklistSetCompressDepth(quicklist *quicklist, int depth); -void quicklistSetFill(quicklist *quicklist, int fill); -void quicklistSetOptions(quicklist *quicklist, int fill, int depth); -void quicklistRelease(quicklist *quicklist); -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 quicklistAppendListpack(quicklist *quicklist, unsigned char *zl); -void quicklistAppendPlainNode(quicklist *quicklist, unsigned char *data, size_t sz); -void quicklistInsertAfter(quicklistIter *iter, quicklistEntry *entry, - void *value, const size_t sz); -void quicklistInsertBefore(quicklistIter *iter, quicklistEntry *entry, - void *value, const size_t sz); -void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry); -void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry, - void *data, size_t sz); -int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data, - const size_t sz); -int quicklistDelRange(quicklist *quicklist, const long start, const long stop); -quicklistIter *quicklistGetIterator(quicklist *quicklist, int direction); -quicklistIter *quicklistGetIteratorAtIdx(quicklist *quicklist, - int direction, const long long idx); -quicklistIter *quicklistGetIteratorEntryAtIdx(quicklist *quicklist, const long long index, - quicklistEntry *entry); -int quicklistNext(quicklistIter *iter, quicklistEntry *entry); -void quicklistSetDirection(quicklistIter *iter, int direction); -void quicklistReleaseIterator(quicklistIter *iter); -quicklist *quicklistDup(quicklist *orig); -void quicklistRotate(quicklist *quicklist); -int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data, - size_t *sz, long long *sval, - void *(*saver)(unsigned char *data, size_t sz)); -int quicklistPop(quicklist *quicklist, int where, unsigned char **data, - size_t *sz, long long *slong); -unsigned long quicklistCount(const quicklist *ql); -int quicklistCompare(quicklistEntry *entry, unsigned char *p2, const size_t p2_len); -size_t quicklistGetLzf(const quicklistNode *node, void **data); -void quicklistNodeLimit(int fill, size_t *size, unsigned int *count); -int quicklistNodeExceedsLimit(int fill, size_t new_sz, unsigned int new_count); -void quicklistRepr(unsigned char *ql, int full); - -/* bookmarks */ -int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node); -int quicklistBookmarkDelete(quicklist *ql, const char *name); -quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name); -void quicklistBookmarksClear(quicklist *ql); -int quicklistisSetPackedThreshold(size_t sz); +/** + * struct quicklist_node - Circular doubly linked list structure. + * @carry: the data that the node holds. It can be plain data or listpack data + * or compressed plain data or compressed listpack data depends on @container + * and @raw. + * @raw_sz: the size in bytes of the data that the node holds before compressed. + * @container: the type of container that holds the data. + * @count: number of elements in the node. + * @raw: 1 for raw node, 0 for lzf compressed node. + */ +struct quicklist_node { + struct quicklist_node *prev; + struct quicklist_node *next; + void *carry; + size_t raw_sz; + unsigned int container : QUICKLIST_NODE_CONTAINER_BITS; + unsigned int count : QUICKLIST_FILL_BITS; + unsigned int raw : QUICKLIST_NODE_RAW_BITS; +}; + +#define QUICKLIST_P_HEAD 0 +#define QUICKLIST_P_MIDDLE 1 +#define QUICKLIST_P_TAIL 2 + +/** + * struct quicklist_partition - Circular doubly linked list structure. + * @which: which partition of the quicklist + * @capacity: the maximum number of nodes this partition can hold, it's + * related with quicklist's compress strategy. + * @guard: circular doubly linked quicklist_node. It self is not a valid + * quicklist_node, it's a guard node. + * @length: number of nodes in the partition. + */ +struct quicklist_partition { + int which; + long capacity; + struct quicklist_node *guard; + struct quicklist_partition *prev; + struct quicklist_partition *next; + + long length; +}; + + +#define QUICKLIST_PACK_SIZE_BITS 16 +static_assert(QUICKLIST_FILL_BITS+QUICKLIST_PACK_SIZE_BITS <= 32); + +/** + * struct quicklist_fill - The limitation of a packed node. + * @pack_max_count: the maximum number of elements that a packed node can hold + * @pack_max_size: the maximum size in bytes that a packed node can reach + */ +struct quicklist_fill { + unsigned int pack_max_count : QUICKLIST_FILL_BITS; + unsigned int pack_max_size : QUICKLIST_PACK_SIZE_BITS; +}; + + +struct quicklist_bookmark { + struct quicklist_node *node; + char *name; +}; + +/** + * struct quicklist + * @head: circular doubly linked quicklist_partition, has three partitions, + * start with head, next middle, and next tail. + * @fill: the limitation of a packed node. + * @count: the number of nodes in the quicklist. + * @bookmark_count: the number of bookmarks of the quicklist + * @bookmarks: the bookmarks for quick reach the target node. Note it that if + * the target node is deleted, it will update to the next node, if it is the + * last node, the bookmark will be deleted. + * + * Note: there are three strategies the quicklist should keep. + * + * compress strategy - every partition should not overflow, and the middle + * partition should keep empty until the head and tail partition is full. + * + * partition compress strategy - the node in head and tail partition should keep + * uncompressed. The node in middle partition should be compressed unless it can + * not compress small enough. + * + * merge strategy - any adjacent node in quicklist can not be merged. + */ +struct quicklist { + struct quicklist_partition *head; + struct quicklist_fill *fill; + long count; + int bookmark_count; + struct quicklist_bookmark bookmarks[]; +}; + +/** + * quicklist_lzf - A lzf compressed data structure. + * @sz: the size in bytes of @compressed + * @compressed: the compressed data + */ +struct quicklist_lzf { + size_t sz; + char compressed[]; +}; + +/** + * QUICKLIST_ITER_STATUS_AHEAD + * In this status, the iterator is invalid until quicklist_iter_next() is called, + * then the iterator will point to the target element. + */ +#define QUICKLIST_ITER_STATUS_AHEAD 0 + +/** + * QUICKLIST_ITER_STATUS_NORMAL + * In this status, the iterator is point to the target element. + */ +#define QUICKLIST_ITER_STATUS_NORMAL 1 + +/** + * QUICKLIST_ITER_STATUS_COMPLETE + * In this status, the iterator is finish its iteration, and is not available + * for further use. + */ +#define QUICKLIST_ITER_STATUS_COMPLETE 2 + +/** + * quicklist_iter - The quicklist element iterator structure. + * @status: see QUICKLIST_ITER_STATUS_AHEAD, QUICKLIST_ITER_STATUS_NORMAL + * and QUICKLIST_ITER_STATUS_COMPLETE. + * @forward: 1 for iterate towards to tail, 0 for iterate towards to head. + * @p: the partition that the iterator is current at + * @raw_node: the node that the iterator is current at, it should not be compressed. + * @offset: the index of the element in @node + * @lp_element: the element address in listpack, only valid if @raw_node is a + * packed node. It's used to quick iterate in listpack. + */ +struct quicklist_iter { + int status; + int forward; + struct quicklist *quicklist; + + struct quicklist_partition *p; + struct quicklist_node *raw_node; + int offset; + + unsigned char *lp_element; +}; + +/** + * quicklist_element - The quicklist element structure. + * @value: the string value of the element, NULL if the element is a integer + * value. Note that it is a reference to the element in quicklist. + * @sz: the size in bytes of the string value of the element. If the + * element is a integer value, it is undefined. + * @longval: the integer value of the element. If the + * element is a string value, it is undefined. + */ +struct quicklist_element { + unsigned char *value; + size_t sz; + long long longval; +}; + +struct quicklist *quicklist_new(int fill, int compress); +void quicklist_free(struct quicklist *quicklist); +void quicklist_push_head(struct quicklist *quicklist, void *value, size_t sz); +void quicklist_push_tail(struct quicklist *quicklist, void *value, size_t sz); +void quicklist_append_listpack(struct quicklist *quicklist, unsigned char *zl); +void quicklist_append_plain(struct quicklist *quicklist, void *value, size_t sz); +void quicklist_iter_add_after(struct quicklist_iter *iter, void *value, size_t sz); +void quicklist_iter_add_before(struct quicklist_iter *iter, void *value, size_t sz); +void quicklist_iter_del(struct quicklist_iter *iter); +void quicklist_iter_replace(struct quicklist_iter *iter, void *value, size_t sz); +int quicklist_replace(struct quicklist *quicklist, long i, void *value, size_t sz); +long quicklist_del(struct quicklist *quicklist, long from, long n); +void quicklist_first_node(struct quicklist *quicklist, + struct quicklist_partition **p, struct quicklist_node **node); +void quicklist_next(struct quicklist_partition *p, struct quicklist_node *node, + struct quicklist_partition **next_p, struct quicklist_node **next_node); +struct quicklist_node *quicklist_next_for_bookmark( + struct quicklist *quicklist, struct quicklist_node *node); +struct quicklist_iter *quicklist_iter_new(struct quicklist *quicklist, + long i, int forward); +struct quicklist_iter *quicklist_iter_new_ahead(struct quicklist *quicklist, + long i, int forward); +int quicklist_iter_next(struct quicklist_iter *iter); +void quicklist_iter_free(struct quicklist_iter *iter); +struct quicklist *quicklist_dup(struct quicklist *quicklist); +long quicklist_count(struct quicklist *quicklist); +long quicklist_node_count(struct quicklist *quicklist); +int quicklist_iter_equal(struct quicklist_iter *iter, void *value, size_t sz); +void quicklist_debug_print(struct quicklist *quicklist, int full); +void quicklist_iter_debug_print(struct quicklist_iter *iter); +int quicklist_bm_create(struct quicklist **ql_ref, char *name, struct quicklist_node *node); +int quicklist_bm_delete(struct quicklist *quicklist, char *name); +struct quicklist_node *quicklist_bm_find(struct quicklist *quicklist, char *name); +void quicklist_iter_get_element(struct quicklist_iter *iter, struct quicklist_element *elem); +struct quicklist_fill *quicklist_fill_new(int fill); #ifdef REDIS_TEST int quicklistTest(int argc, char *argv[], int flags); #endif -/* Directions for iterators */ -#define AL_START_HEAD 0 -#define AL_START_TAIL 1 - #endif /* __QUICKLIST_H__ */ diff --git a/src/rdb.c b/src/rdb.c index b50ea7867c4c1..92c2379899c1d 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -821,26 +821,29 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key, int dbid) { } else if (o->type == OBJ_LIST) { /* Save a list value */ if (o->encoding == OBJ_ENCODING_QUICKLIST) { - quicklist *ql = o->ptr; - quicklistNode *node = ql->head; + struct quicklist *ql = o->ptr; + struct quicklist_partition *p; + struct quicklist_node *node; + quicklist_first_node(ql, &p, &node); - if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1; + if ((n = rdbSaveLen(rdb,quicklist_node_count(ql))) == -1) return -1; nwritten += n; while(node) { if ((n = rdbSaveLen(rdb,node->container)) == -1) return -1; nwritten += n; - if (quicklistNodeIsCompressed(node)) { - void *data; - size_t compress_len = quicklistGetLzf(node, &data); - if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1; + if (!node->raw) { + struct quicklist_lzf *lzf = node->carry; + void *data = lzf->compressed; + size_t compress_len = lzf->sz; + if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->raw_sz)) == -1) return -1; nwritten += n; } else { - if ((n = rdbSaveRawString(rdb,node->entry,node->sz)) == -1) return -1; + if ((n = rdbSaveRawString(rdb,node->carry,node->raw_sz)) == -1) return -1; nwritten += n; } - node = node->next; + quicklist_next(p, node, &p, &node); } } else if (o->encoding == OBJ_ENCODING_LISTPACK) { unsigned char *lp = o->ptr; @@ -1758,7 +1761,7 @@ static int _listZiplistEntryConvertAndValidate(unsigned char *p, unsigned int he unsigned int slen; long long vll; char longstr[32] = {0}; - quicklist *ql = (quicklist*)userdata; + struct quicklist *ql = userdata; if (!ziplistGet(p, &str, &slen, &vll)) return 0; if (!str) { @@ -1766,7 +1769,7 @@ static int _listZiplistEntryConvertAndValidate(unsigned char *p, unsigned int he slen = ll2string(longstr, sizeof(longstr), vll); str = (unsigned char *)longstr; } - quicklistPushTail(ql, str, slen); + quicklist_push_tail(ql, str, slen); return 1; } @@ -1862,8 +1865,6 @@ 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_listpack_size, - server.list_compress_depth); /* Load every single element of the list */ while(len--) { @@ -1873,7 +1874,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { } dec = getDecodedObject(ele); size_t len = sdslen(dec->ptr); - quicklistPushTail(o->ptr, dec->ptr, len); + quicklist_push_tail(o->ptr, dec->ptr, len); decrRefCount(dec); decrRefCount(ele); } @@ -2174,8 +2175,6 @@ 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_listpack_size, - server.list_compress_depth); uint64_t container = QUICKLIST_NODE_CONTAINER_PACKED; while (len--) { unsigned char *lp; @@ -2203,7 +2202,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { } if (container == QUICKLIST_NODE_CONTAINER_PLAIN) { - quicklistAppendPlainNode(o->ptr, data, encoded_len); + quicklist_append_plain(o->ptr, data, encoded_len); continue; } @@ -2236,11 +2235,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { zfree(lp); continue; } else { - quicklistAppendListpack(o->ptr, lp); + quicklist_append_listpack(o->ptr, lp); } } - if (quicklistCount(o->ptr) == 0) { + if (quicklist_count(o->ptr) == 0) { decrRefCount(o); goto emptykey; } @@ -2325,7 +2324,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { break; case RDB_TYPE_LIST_ZIPLIST: { - quicklist *ql = quicklistNew(server.list_max_listpack_size, + struct quicklist *ql = quicklist_new(server.list_max_listpack_size, server.list_compress_depth); if (!ziplistValidateIntegrity(encoded, encoded_len, 1, @@ -2335,15 +2334,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) { zfree(encoded); o->ptr = NULL; decrRefCount(o); - quicklistRelease(ql); + quicklist_free(ql); return NULL; } - if (ql->len == 0) { + if (quicklist_node_count(ql) == 0) { zfree(encoded); o->ptr = NULL; decrRefCount(o); - quicklistRelease(ql); + quicklist_free(ql); goto emptykey; } diff --git a/src/server.h b/src/server.h index 77ebb0f5b9a15..d5b79b3146ddb 100644 --- a/src/server.h +++ b/src/server.h @@ -2415,15 +2415,9 @@ typedef struct { unsigned char direction; /* Iteration direction */ unsigned char *lpi; /* listpack iterator */ - quicklistIter *iter; /* quicklist iterator */ -} listTypeIterator; - -/* Structure for an entry while iterating over a list. */ -typedef struct { - listTypeIterator *li; unsigned char *lpe; /* Entry in listpack */ - quicklistEntry entry; /* Entry in quicklist */ -} listTypeEntry; + struct quicklist_iter *iter; /* quicklist iterator */ +} listTypeIterator; /* Structure to hold set iteration abstraction. */ typedef struct { @@ -2727,14 +2721,13 @@ robj *listTypePop(robj *subject, int where); unsigned long listTypeLength(const robj *subject); listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction); void listTypeReleaseIterator(listTypeIterator *li); -void listTypeSetIteratorDirection(listTypeIterator *li, listTypeEntry *entry, unsigned char direction); -int listTypeNext(listTypeIterator *li, listTypeEntry *entry); -robj *listTypeGet(listTypeEntry *entry); -unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *lval); -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); +int listTypeNext(listTypeIterator *entry); +robj *listTypeGet(listTypeIterator *entry); +unsigned char *listTypeGetValue(listTypeIterator *entry, size_t *vlen, long long *lval); +void listTypeInsert(listTypeIterator *entry, robj *value, int where); +void listTypeReplace(listTypeIterator *entry, robj *value); +int listTypeEqual(listTypeIterator *entry, robj *o); +void listTypeDelete(listTypeIterator *entry); robj *listTypeDup(robj *o); void listTypeDelRange(robj *o, long start, long stop); void popGenericCommand(client *c, int where); diff --git a/src/sort.c b/src/sort.c index 77f4cbbc4c601..2303c76c74ea8 100644 --- a/src/sort.c +++ b/src/sort.c @@ -369,13 +369,12 @@ void sortCommandGeneric(client *c, int readonly) { * way, just getting the required range, as an optimization. */ if (end >= start) { listTypeIterator *li; - listTypeEntry entry; li = listTypeInitIterator(sortval, desc ? (long)(listTypeLength(sortval) - start - 1) : start, desc ? LIST_HEAD : LIST_TAIL); - while(j < vectorlen && listTypeNext(li,&entry)) { - vector[j].obj = listTypeGet(&entry); + while(j < vectorlen && listTypeNext(li)) { + vector[j].obj = listTypeGet(li); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; @@ -387,9 +386,8 @@ void sortCommandGeneric(client *c, int readonly) { } } else if (sortval->type == OBJ_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,LIST_TAIL); - listTypeEntry entry; - while(listTypeNext(li,&entry)) { - vector[j].obj = listTypeGet(&entry); + while(listTypeNext(li)) { + vector[j].obj = listTypeGet(li); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; diff --git a/src/t_list.c b/src/t_list.c index 966199e0e9bfa..ad5d91d8d9bc9 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -33,6 +33,16 @@ * List API *----------------------------------------------------------------------------*/ +static int quicklistNodeExceedsLimit(size_t sz, unsigned int count) +{ + struct quicklist_fill *fill = quicklist_fill_new(server.list_max_listpack_size); + if (fill->pack_max_count == 0) + fill->pack_max_count = 1; + int exceed = sz > fill->pack_max_size || count > fill->pack_max_count; + zfree(fill); + return exceed; +} + /* Check the length and size of a number of objects that will be added to list to see * if we need to convert a listpack to a quicklist. Note that we only check string * encoded objects as their string length can be queried in constant time. @@ -56,18 +66,17 @@ static void listTypeTryConvertListpack(robj *o, robj **argv, int start, int end, add_length = end - start + 1; } - if (quicklistNodeExceedsLimit(server.list_max_listpack_size, - lpBytes(o->ptr) + add_bytes, lpLength(o->ptr) + add_length)) + if (quicklistNodeExceedsLimit( + lpBytes(o->ptr) + add_bytes, lpLength(o->ptr) + add_length)) { /* Invoke callback before conversion. */ if (fn) fn(data); - quicklist *ql = quicklistCreate(); - quicklistSetOptions(ql, server.list_max_listpack_size, server.list_compress_depth); + struct quicklist *ql = quicklist_new(server.list_max_listpack_size, server.list_compress_depth); /* Append listpack to quicklist if it's not empty, otherwise release it. */ if (lpLength(o->ptr)) - quicklistAppendListpack(ql, o->ptr); + quicklist_append_listpack(ql, o->ptr); else lpFree(o->ptr); o->ptr = ql; @@ -89,28 +98,35 @@ static void listTypeTryConvertQuicklist(robj *o, int shrinking, beforeConvertCB size_t sz_limit; unsigned int count_limit; - quicklist *ql = o->ptr; + struct quicklist *ql = o->ptr; /* A quicklist can be converted to listpack only if it has only one packed node. */ - if (ql->len != 1 || ql->head->container != QUICKLIST_NODE_CONTAINER_PACKED) + if (quicklist_node_count(ql) != 1) + return; + + struct quicklist_partition *head_p; + struct quicklist_node *head; + quicklist_first_node(ql, &head_p, &head); + if (head->container != QUICKLIST_NODE_CONTAINER_PACKED) return; /* Check the length or size of the quicklist is below the limit. */ - quicklistNodeLimit(server.list_max_listpack_size, &sz_limit, &count_limit); + sz_limit = ql->fill->pack_max_size; + count_limit = ql->fill->pack_max_count; if (shrinking) { sz_limit /= 2; count_limit /= 2; } - if (ql->head->sz > sz_limit || ql->count > count_limit) return; + if (head->raw_sz > sz_limit || ql->count > count_limit) return; /* Invoke callback before conversion. */ if (fn) fn(data); /* Extract the listpack from the unique quicklist node, * then reset it and release the quicklist. */ - o->ptr = ql->head->entry; - ql->head->entry = NULL; - quicklistRelease(ql); + o->ptr = head->carry; + head->carry = NULL; + quicklist_free(ql); o->encoding = OBJ_ENCODING_LISTPACK; } @@ -165,13 +181,18 @@ void listTypeTryConversionAppend(robj *o, robj **argv, int start, int end, * the function takes care of it if needed. */ void listTypePush(robj *subject, robj *value, int where) { if (subject->encoding == OBJ_ENCODING_QUICKLIST) { - int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL; if (value->encoding == OBJ_ENCODING_INT) { char buf[32]; ll2string(buf, 32, (long)value->ptr); - quicklistPush(subject->ptr, buf, strlen(buf), pos); + if (where == LIST_HEAD) + quicklist_push_head(subject->ptr, buf, strlen(buf)); + else + quicklist_push_tail(subject->ptr, buf, strlen(buf)); } else { - quicklistPush(subject->ptr, value->ptr, sdslen(value->ptr), pos); + if (where == LIST_HEAD) + quicklist_push_head(subject->ptr, value->ptr, sdslen(value->ptr)); + else + quicklist_push_tail(subject->ptr, value->ptr, sdslen(value->ptr)); } } else if (subject->encoding == OBJ_ENCODING_LISTPACK) { if (value->encoding == OBJ_ENCODING_INT) { @@ -192,14 +213,39 @@ void *listPopSaver(unsigned char *data, size_t sz) { return createStringObject((char*)data,sz); } +int quicklistPopCustom(struct quicklist *quicklist, int head, unsigned char **data, + long long *sval) { + + if (quicklist->count == 0) + return 0; + + struct quicklist_iter *iter; + if (head) + iter = quicklist_iter_new(quicklist, 0, 1); + else + iter = quicklist_iter_new(quicklist, -1, 1); + + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + if (elem.value) { + *data = listPopSaver(elem.value, elem.sz); + } else { + *data = NULL; + *sval = elem.longval; + } + quicklist_iter_del(iter); + quicklist_iter_free(iter); + return 1; +} + robj *listTypePop(robj *subject, int where) { robj *value = NULL; if (subject->encoding == OBJ_ENCODING_QUICKLIST) { long long vlong; - int ql_where = where == LIST_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL; - if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value, - NULL, &vlong, listPopSaver)) { + int head = where == LIST_HEAD ? 1 : 0; + if (quicklistPopCustom(subject->ptr, head, (unsigned char **)&value, + &vlong)) { if (!value) value = createStringObjectFromLongLong(vlong); } @@ -223,7 +269,7 @@ robj *listTypePop(robj *subject, int where) { unsigned long listTypeLength(const robj *subject) { if (subject->encoding == OBJ_ENCODING_QUICKLIST) { - return quicklistCount(subject->ptr); + return quicklist_count(subject->ptr); } else if (subject->encoding == OBJ_ENCODING_LISTPACK) { return lpLength(subject->ptr); } else { @@ -242,9 +288,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, /* LIST_HEAD means start at TAIL and move *towards* head. * LIST_TAIL means start at HEAD and move *towards* tail. */ if (li->encoding == OBJ_ENCODING_QUICKLIST) { - int iter_direction = direction == LIST_HEAD ? AL_START_TAIL : AL_START_HEAD; - li->iter = quicklistGetIteratorAtIdx(li->subject->ptr, - iter_direction, index); + li->iter = quicklist_iter_new_ahead(li->subject->ptr, index, direction == LIST_TAIL); } else if (li->encoding == OBJ_ENCODING_LISTPACK) { li->lpi = lpSeek(subject->ptr, index); } else { @@ -253,44 +297,26 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, return li; } -/* Sets the direction of an iterator. */ -void listTypeSetIteratorDirection(listTypeIterator *li, listTypeEntry *entry, unsigned char direction) { - if (li->direction == direction) return; - - li->direction = direction; - if (li->encoding == OBJ_ENCODING_QUICKLIST) { - int dir = direction == LIST_HEAD ? AL_START_TAIL : AL_START_HEAD; - quicklistSetDirection(li->iter, dir); - } else if (li->encoding == OBJ_ENCODING_LISTPACK) { - unsigned char *lp = li->subject->ptr; - /* Note that the iterator for listpack always points to the next of the current entry, - * so we need to update position of the iterator depending on the direction. */ - li->lpi = (direction == LIST_TAIL) ? lpNext(lp, entry->lpe) : lpPrev(lp, entry->lpe); - } else { - serverPanic("Unknown list encoding"); - } -} - /* Clean up the iterator. */ void listTypeReleaseIterator(listTypeIterator *li) { - if (li->encoding == OBJ_ENCODING_QUICKLIST) - quicklistReleaseIterator(li->iter); + if (li->encoding == OBJ_ENCODING_QUICKLIST && li->iter) + quicklist_iter_free(li->iter); zfree(li); } /* Stores pointer to current the entry in the provided entry structure * and advances the position of the iterator. Returns 1 when the current * entry is in fact an entry, 0 otherwise. */ -int listTypeNext(listTypeIterator *li, listTypeEntry *entry) { +int listTypeNext(listTypeIterator *li) { /* Protect from converting when iterating */ serverAssert(li->subject->encoding == li->encoding); - entry->li = li; if (li->encoding == OBJ_ENCODING_QUICKLIST) { - return quicklistNext(li->iter, &entry->entry); + if (li->iter) + return quicklist_iter_next(li->iter); } else if (li->encoding == OBJ_ENCODING_LISTPACK) { - entry->lpe = li->lpi; - if (entry->lpe != NULL) { + li->lpe = li->lpi; + if (li->lpe != NULL) { li->lpi = (li->direction == LIST_TAIL) ? lpNext(li->subject->ptr,li->lpi) : lpPrev(li->subject->ptr,li->lpi); return 1; @@ -305,18 +331,20 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) { * When the function returns NULL, it populates the integer value by * reference in 'lval'. Otherwise a pointer to the string is returned, * and 'vlen' is set to the length of the string. */ -unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *lval) { +unsigned char *listTypeGetValue(listTypeIterator *li, size_t *vlen, long long *lval) { unsigned char *vstr = NULL; - if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) { - if (entry->entry.value) { - vstr = entry->entry.value; - *vlen = entry->entry.sz; + if (li->encoding == OBJ_ENCODING_QUICKLIST) { + struct quicklist_element elem; + quicklist_iter_get_element(li->iter, &elem); + if (elem.value) { + vstr = elem.value; + *vlen = elem.sz; } else { - *lval = entry->entry.longval; + *lval = elem.longval; } - } else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) { + } else if (li->encoding == OBJ_ENCODING_LISTPACK) { unsigned int slen; - vstr = lpGetValue(entry->lpe, &slen, lval); + vstr = lpGetValue(li->lpe, &slen, lval); *vlen = slen; } else { serverPanic("Unknown list encoding"); @@ -325,34 +353,34 @@ unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *l } /* Return entry or NULL at the current position of the iterator. */ -robj *listTypeGet(listTypeEntry *entry) { +robj *listTypeGet(listTypeIterator *li) { unsigned char *vstr; size_t vlen; long long lval; - vstr = listTypeGetValue(entry, &vlen, &lval); + vstr = listTypeGetValue(li, &vlen, &lval); if (vstr) return createStringObject((char *)vstr, vlen); else return createStringObjectFromLongLong(lval); } -void listTypeInsert(listTypeEntry *entry, robj *value, int where) { - robj *subject = entry->li->subject; +void listTypeInsert(listTypeIterator *li, robj *value, int where) { + robj *subject = li->subject; value = getDecodedObject(value); sds str = value->ptr; size_t len = sdslen(str); - if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) { + if (li->encoding == OBJ_ENCODING_QUICKLIST) { if (where == LIST_TAIL) { - quicklistInsertAfter(entry->li->iter, &entry->entry, str, len); + quicklist_iter_add_after(li->iter, str, len); } else if (where == LIST_HEAD) { - quicklistInsertBefore(entry->li->iter, &entry->entry, str, len); + quicklist_iter_add_before(li->iter, str, len); } - } else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) { + } else if (li->encoding == OBJ_ENCODING_LISTPACK) { int lpw = (where == LIST_TAIL) ? LP_AFTER : LP_BEFORE; subject->ptr = lpInsertString(subject->ptr, (unsigned char *)str, - len, entry->lpe, lpw, &entry->lpe); + len, li->lpe, lpw, &li->lpe); } else { serverPanic("Unknown list encoding"); } @@ -360,16 +388,16 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) { } /* Replaces entry at the current position of the iterator. */ -void listTypeReplace(listTypeEntry *entry, robj *value) { - robj *subject = entry->li->subject; +void listTypeReplace(listTypeIterator *li, robj *value) { + robj *subject = li->subject; value = getDecodedObject(value); sds str = value->ptr; size_t len = sdslen(str); - if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) { - quicklistReplaceEntry(entry->li->iter, &entry->entry, str, len); - } else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) { - subject->ptr = lpReplace(subject->ptr, &entry->lpe, (unsigned char *)str, len); + if (li->encoding == OBJ_ENCODING_QUICKLIST) { + quicklist_iter_replace(li->iter, str, len); + } else if (li->encoding == OBJ_ENCODING_LISTPACK) { + subject->ptr = lpReplace(subject->ptr, &li->lpe, (unsigned char *)str, len); } else { serverPanic("Unknown list encoding"); } @@ -388,8 +416,8 @@ int listTypeReplaceAtIndex(robj *o, int index, robj *value) { int replaced = 0; if (o->encoding == OBJ_ENCODING_QUICKLIST) { - quicklist *ql = o->ptr; - replaced = quicklistReplaceAtIndex(ql, index, vstr, vlen); + struct quicklist *ql = o->ptr; + replaced = quicklist_replace(ql, index, vstr, vlen); } else if (o->encoding == OBJ_ENCODING_LISTPACK) { unsigned char *p = lpSeek(o->ptr,index); if (p) { @@ -405,23 +433,23 @@ int listTypeReplaceAtIndex(robj *o, int index, robj *value) { } /* Compare the given object with the entry at the current position. */ -int listTypeEqual(listTypeEntry *entry, robj *o) { +int listTypeEqual(listTypeIterator *li, robj *o) { serverAssertWithInfo(NULL,o,sdsEncodedObject(o)); - if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) { - return quicklistCompare(&entry->entry,o->ptr,sdslen(o->ptr)); - } else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) { - return lpCompare(entry->lpe,o->ptr,sdslen(o->ptr)); + if (li->encoding == OBJ_ENCODING_QUICKLIST) { + return quicklist_iter_equal(li->iter,o->ptr,sdslen(o->ptr)); + } else if (li->encoding == OBJ_ENCODING_LISTPACK) { + return lpCompare(li->lpe,o->ptr,sdslen(o->ptr)); } else { serverPanic("Unknown list encoding"); } } /* Delete the element pointed to. */ -void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) { - if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) { - quicklistDelEntry(iter->iter, &entry->entry); - } else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) { - unsigned char *p = entry->lpe; +void listTypeDelete(listTypeIterator *iter) { + if (iter->encoding == OBJ_ENCODING_QUICKLIST) { + quicklist_iter_del(iter->iter); + } else if (iter->encoding == OBJ_ENCODING_LISTPACK) { + unsigned char *p = iter->lpe; iter->subject->ptr = lpDelete(iter->subject->ptr,p,&p); /* Update position of the iterator depending on the direction */ @@ -456,7 +484,7 @@ robj *listTypeDup(robj *o) { lobj = createObject(OBJ_LIST, lpDup(o->ptr)); break; case OBJ_ENCODING_QUICKLIST: - lobj = createObject(OBJ_LIST, quicklistDup(o->ptr)); + lobj = createObject(OBJ_LIST, quicklist_dup(o->ptr)); break; default: serverPanic("Unknown list encoding"); @@ -469,7 +497,7 @@ robj *listTypeDup(robj *o) { /* Delete a range of elements from the list. */ void listTypeDelRange(robj *subject, long start, long count) { if (subject->encoding == OBJ_ENCODING_QUICKLIST) { - quicklistDelRange(subject->ptr, start, count); + quicklist_del(subject->ptr, start, count); } else if (subject->encoding == OBJ_ENCODING_LISTPACK) { subject->ptr = lpDeleteRange(subject->ptr, start, count); } else { @@ -536,7 +564,6 @@ void linsertCommand(client *c) { int where; robj *subject; listTypeIterator *iter; - listTypeEntry entry; int inserted = 0; if (strcasecmp(c->argv[2]->ptr,"after") == 0) { @@ -560,9 +587,9 @@ void linsertCommand(client *c) { /* Seek pivot from head to tail */ iter = listTypeInitIterator(subject,0,LIST_TAIL); - while (listTypeNext(iter,&entry)) { - if (listTypeEqual(&entry,c->argv[3])) { - listTypeInsert(&entry,c->argv[4],where); + while (listTypeNext(iter)) { + if (listTypeEqual(iter,c->argv[3])) { + listTypeInsert(iter,c->argv[4],where); inserted = 1; break; } @@ -600,13 +627,12 @@ void lindexCommand(client *c) { return; listTypeIterator *iter = listTypeInitIterator(o,index,LIST_TAIL); - listTypeEntry entry; unsigned char *vstr; size_t vlen; long long lval; - if (listTypeNext(iter,&entry)) { - vstr = listTypeGetValue(&entry,&vlen,&lval); + if (listTypeNext(iter)) { + vstr = listTypeGetValue(iter,&vlen,&lval); if (vstr) { addReplyBulkCBuffer(c, vstr, vlen); } else { @@ -680,18 +706,18 @@ void addListQuicklistRangeReply(client *c, robj *o, int from, int rangelen, int /* Return the result in form of a multi-bulk reply */ addReplyArrayLen(c,rangelen); - int direction = reverse ? AL_START_TAIL : AL_START_HEAD; - quicklistIter *iter = quicklistGetIteratorAtIdx(o->ptr, direction, from); + struct quicklist_iter *iter = quicklist_iter_new_ahead(o->ptr, from, !reverse); while(rangelen--) { - quicklistEntry qe; - serverAssert(quicklistNext(iter, &qe)); /* fail on corrupt data */ - if (qe.value) { - addReplyBulkCBuffer(c,qe.value,qe.sz); + serverAssert(quicklist_iter_next(iter)); /* fail on corrupt data */ + struct quicklist_element elem; + quicklist_iter_get_element(iter, &elem); + if (elem.value) { + addReplyBulkCBuffer(c, elem.value, elem.sz); } else { - addReplyBulkLongLong(c,qe.longval); + addReplyBulkLongLong(c, elem.longval); } } - quicklistReleaseIterator(iter); + quicklist_iter_free(iter); } /* Extracted from `addListRangeReply()` to reply with a listpack list. @@ -919,8 +945,10 @@ void ltrimCommand(client *c) { /* Remove list elements to perform the trim */ if (o->encoding == OBJ_ENCODING_QUICKLIST) { - quicklistDelRange(o->ptr,0,ltrim); - quicklistDelRange(o->ptr,-rtrim,rtrim); + if (ltrim > 0) + quicklist_del(o->ptr,0,ltrim); + if (rtrim > 0) + quicklist_del(o->ptr,-rtrim,rtrim); } else if (o->encoding == OBJ_ENCODING_LISTPACK) { o->ptr = lpDeleteRange(o->ptr,0,ltrim); o->ptr = lpDeleteRange(o->ptr,-rtrim,rtrim); @@ -1018,11 +1046,10 @@ void lposCommand(client *c) { /* Seek the element. */ listTypeIterator *li; li = listTypeInitIterator(o,direction == LIST_HEAD ? -1 : 0,direction); - listTypeEntry entry; long llen = listTypeLength(o); long index = 0, matches = 0, matchindex = -1, arraylen = 0; - while (listTypeNext(li,&entry) && (maxlen == 0 || index < maxlen)) { - if (listTypeEqual(&entry,ele)) { + while (listTypeNext(li) && (maxlen == 0 || index < maxlen)) { + if (listTypeEqual(li,ele)) { matches++; matchindex = (direction == LIST_TAIL) ? index : llen - index - 1; if (matches >= rank) { @@ -1073,10 +1100,9 @@ void lremCommand(client *c) { li = listTypeInitIterator(subject,0,LIST_TAIL); } - listTypeEntry entry; - while (listTypeNext(li,&entry)) { - if (listTypeEqual(&entry,obj)) { - listTypeDelete(li, &entry); + while (listTypeNext(li)) { + if (listTypeEqual(li,obj)) { + listTypeDelete(li); server.dirty++; removed++; if (toremove && removed == toremove) break; diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl old mode 100644 new mode 100755 index 586d3d30865ef..5df94b6734381 --- a/tests/unit/type/list.tcl +++ b/tests/unit/type/list.tcl @@ -1,2363 +1,2360 @@ -# check functionality compression of plain and zipped nodes -start_server [list overrides [list save ""] ] { - r config set list-compress-depth 2 - r config set list-max-ziplist-size 1 - - # 3 test to check compression with regular ziplist nodes - # 1. using push + insert - # 2. using push + insert + trim - # 3. using push + insert + set - - test {reg node check compression with insert and pop} { - r lpush list1 [string repeat a 500] - r lpush list1 [string repeat b 500] - r lpush list1 [string repeat c 500] - r lpush list1 [string repeat d 500] - r linsert list1 after [string repeat d 500] [string repeat e 500] - r linsert list1 after [string repeat d 500] [string repeat f 500] - r linsert list1 after [string repeat d 500] [string repeat g 500] - r linsert list1 after [string repeat d 500] [string repeat j 500] - assert_equal [r lpop list1] [string repeat d 500] - assert_equal [r lpop list1] [string repeat j 500] - assert_equal [r lpop list1] [string repeat g 500] - assert_equal [r lpop list1] [string repeat f 500] - assert_equal [r lpop list1] [string repeat e 500] - assert_equal [r lpop list1] [string repeat c 500] - assert_equal [r lpop list1] [string repeat b 500] - assert_equal [r lpop list1] [string repeat a 500] - }; - - test {reg node check compression combined with trim} { - r lpush list2 [string repeat a 500] - r linsert list2 after [string repeat a 500] [string repeat b 500] - r rpush list2 [string repeat c 500] - assert_equal [string repeat b 500] [r lindex list2 1] - r LTRIM list2 1 -1 - r llen list2 - } {2} - - test {reg node check compression with lset} { - r lpush list3 [string repeat a 500] - r LSET list3 0 [string repeat b 500] - assert_equal [string repeat b 500] [r lindex list3 0] - r lpush list3 [string repeat c 500] - r LSET list3 0 [string repeat d 500] - assert_equal [string repeat d 500] [r lindex list3 0] - } - - # repeating the 3 tests with plain nodes - # (by adjusting quicklist-packed-threshold) - - test {plain node check compression} { - r debug quicklist-packed-threshold 1b - r lpush list4 [string repeat a 500] - r lpush list4 [string repeat b 500] - r lpush list4 [string repeat c 500] - r lpush list4 [string repeat d 500] - r linsert list4 after [string repeat d 500] [string repeat e 500] - r linsert list4 after [string repeat d 500] [string repeat f 500] - r linsert list4 after [string repeat d 500] [string repeat g 500] - r linsert list4 after [string repeat d 500] [string repeat j 500] - assert_equal [r lpop list4] [string repeat d 500] - assert_equal [r lpop list4] [string repeat j 500] - assert_equal [r lpop list4] [string repeat g 500] - assert_equal [r lpop list4] [string repeat f 500] - assert_equal [r lpop list4] [string repeat e 500] - assert_equal [r lpop list4] [string repeat c 500] - assert_equal [r lpop list4] [string repeat b 500] - assert_equal [r lpop list4] [string repeat a 500] - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - test {plain node check compression with ltrim} { - r debug quicklist-packed-threshold 1b - r lpush list5 [string repeat a 500] - r linsert list5 after [string repeat a 500] [string repeat b 500] - r rpush list5 [string repeat c 500] - assert_equal [string repeat b 500] [r lindex list5 1] - r LTRIM list5 1 -1 - assert_equal [r llen list5] 2 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - test {plain node check compression using lset} { - r debug quicklist-packed-threshold 1b - r lpush list6 [string repeat a 500] - r LSET list6 0 [string repeat b 500] - assert_equal [string repeat b 500] [r lindex list6 0] - r lpush list6 [string repeat c 500] - r LSET list6 0 [string repeat d 500] - assert_equal [string repeat d 500] [r lindex list6 0] - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # revert config for external mode tests. - r config set list-compress-depth 0 -} - -# check functionality of plain nodes using low packed-threshold -start_server [list overrides [list save ""] ] { - # basic command check for plain nodes - "LPUSH & LPOP" - test {Test LPUSH and LPOP on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst 9 - r lpush lst xxxxxxxxxx - r lpush lst xxxxxxxxxx - set s0 [s used_memory] - assert {$s0 > 10} - assert {[r llen lst] == 3} - set s0 [r rpop lst] - set s1 [r rpop lst] - assert {$s0 eq "9"} - assert {[r llen lst] == 1} - r lpop lst - assert {[string length $s1] == 10} - # check rdb - r lpush lst xxxxxxxxxx - r lpush lst bb - r debug reload - assert_equal [r rpop lst] "xxxxxxxxxx" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LINDEX & LINSERT" - test {Test LINDEX and LINSERT on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst xxxxxxxxxxx - r lpush lst 9 - r lpush lst xxxxxxxxxxx - r linsert lst before "9" "8" - assert {[r lindex lst 1] eq "8"} - r linsert lst BEFORE "9" "7" - r linsert lst BEFORE "9" "xxxxxxxxxxx" - assert {[r lindex lst 3] eq "xxxxxxxxxxx"} - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LTRIM" - test {Test LTRIM on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst1 9 - r lpush lst1 xxxxxxxxxxx - r lpush lst1 9 - r LTRIM lst1 1 -1 - assert_equal [r llen lst1] 2 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LREM" - test {Test LREM on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r lpush lst one - r lpush lst xxxxxxxxxxx - set s0 [s used_memory] - assert {$s0 > 10} - r lpush lst 9 - r LREM lst -2 "one" - assert_equal [r llen lst] 2 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LPOS" - test {Test LPOS on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r RPUSH lst "aa" - r RPUSH lst "bb" - r RPUSH lst "cc" - r LSET lst 0 "xxxxxxxxxxx" - assert_equal [r LPOS lst "xxxxxxxxxxx"] 0 - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # basic command check for plain nodes - "LMOVE" - test {Test LMOVE on plain nodes} { - r flushdb - r debug quicklist-packed-threshold 1b - r RPUSH lst2{t} "aa" - r RPUSH lst2{t} "bb" - r LSET lst2{t} 0 xxxxxxxxxxx - r RPUSH lst2{t} "cc" - r RPUSH lst2{t} "dd" - r LMOVE lst2{t} lst{t} RIGHT LEFT - r LMOVE lst2{t} lst{t} LEFT RIGHT - assert_equal [r llen lst{t}] 2 - assert_equal [r llen lst2{t}] 2 - assert_equal [r lpop lst2{t}] "bb" - assert_equal [r lpop lst2{t}] "cc" - assert_equal [r lpop lst{t}] "dd" - assert_equal [r lpop lst{t}] "xxxxxxxxxxx" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # testing LSET with combinations of node types - # plain->packed , packed->plain, plain->plain, packed->packed - test {Test LSET with packed / plain combinations} { - r debug quicklist-packed-threshold 5b - r RPUSH lst "aa" - r RPUSH lst "bb" - r lset lst 0 [string repeat d 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat d 50001] - r RPUSH lst [string repeat f 50001] - r lset lst 0 [string repeat e 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat e 50001] - r RPUSH lst [string repeat m 50001] - r lset lst 0 "bb" - set s1 [r lpop lst] - assert_equal $s1 "bb" - r RPUSH lst "bb" - r lset lst 0 "cc" - set s1 [r lpop lst] - assert_equal $s1 "cc" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - # checking LSET in case ziplist needs to be split - test {Test LSET with packed is split in the middle} { - r flushdb - r debug quicklist-packed-threshold 5b - r RPUSH lst "aa" - r RPUSH lst "bb" - r RPUSH lst "cc" - r RPUSH lst "dd" - r RPUSH lst "ee" - r lset lst 2 [string repeat e 10] - assert_equal [r lpop lst] "aa" - assert_equal [r lpop lst] "bb" - assert_equal [r lpop lst] [string repeat e 10] - assert_equal [r lpop lst] "dd" - assert_equal [r lpop lst] "ee" - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} - - - # repeating "plain check LSET with combinations" - # but now with single item in each ziplist - test {Test LSET with packed consist only one item} { - r flushdb - set original_config [config_get_set list-max-ziplist-size 1] - r debug quicklist-packed-threshold 1b - r RPUSH lst "aa" - r RPUSH lst "bb" - r lset lst 0 [string repeat d 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat d 50001] - r RPUSH lst [string repeat f 50001] - r lset lst 0 [string repeat e 50001] - set s1 [r lpop lst] - assert_equal $s1 [string repeat e 50001] - r RPUSH lst [string repeat m 50001] - r lset lst 0 "bb" - set s1 [r lpop lst] - assert_equal $s1 "bb" - r RPUSH lst "bb" - r lset lst 0 "cc" - set s1 [r lpop lst] - assert_equal $s1 "cc" - r debug quicklist-packed-threshold 0 - r config set list-max-ziplist-size $original_config - } {OK} {needs:debug} - - test {Crash due to delete entry from a compress quicklist node} { - r flushdb - r debug quicklist-packed-threshold 100b - set original_config [config_get_set list-compress-depth 1] - - set small_ele [string repeat x 32] - set large_ele [string repeat x 100] - - # Push a large element - r RPUSH lst $large_ele - - # Insert two elements and keep them in the same node - r RPUSH lst $small_ele - r RPUSH lst $small_ele - - # When setting the position of -1 to a large element, we first insert - # a large element at the end and then delete its previous element. - r LSET lst -1 $large_ele - assert_equal "$large_ele $small_ele $large_ele" [r LRANGE lst 0 -1] - - r debug quicklist-packed-threshold 0 - r config set list-compress-depth $original_config - } {OK} {needs:debug} - - test {Crash due to split quicklist node wrongly} { - r flushdb - r debug quicklist-packed-threshold 10b - - r LPUSH lst "aa" - r LPUSH lst "bb" - r LSET lst -2 [string repeat x 10] - r RPOP lst - assert_equal [string repeat x 10] [r LRANGE lst 0 -1] - - r debug quicklist-packed-threshold 0 - } {OK} {needs:debug} -} - -run_solo {list-large-memory} { -start_server [list overrides [list save ""] ] { - -# test if the server supports such large configs (avoid 32 bit builds) -catch { - r config set proto-max-bulk-len 10000000000 ;#10gb - r config set client-query-buffer-limit 10000000000 ;#10gb -} -if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { - - set str_length 5000000000 - - # repeating all the plain nodes basic checks with 5gb values - test {Test LPUSH and LPOP on plain nodes over 4GB} { - r flushdb - r lpush lst 9 - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - set s0 [s used_memory] - assert {$s0 > $str_length} - assert {[r llen lst] == 3} - assert_equal [r rpop lst] "9" - assert_equal [read_big_bulk {r rpop lst}] $str_length - assert {[r llen lst] == 1} - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LINDEX and LINSERT on plain nodes over 4GB} { - r flushdb - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r lpush lst 9 - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r linsert lst before "9" "8" - assert_equal [r lindex lst 1] "8" - r LINSERT lst BEFORE "9" "7" - r write "*5\r\n\$7\r\nLINSERT\r\n\$3\r\nlst\r\n\$6\r\nBEFORE\r\n\$3\r\n\"9\"\r\n" - write_big_bulk 10; - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LTRIM on plain nodes over 4GB} { - r flushdb - r lpush lst 9 - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r lpush lst 9 - r LTRIM lst 1 -1 - assert_equal [r llen lst] 2 - assert_equal [r rpop lst] 9 - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LREM on plain nodes over 4GB} { - r flushdb - r lpush lst one - r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" - write_big_bulk $str_length; - r lpush lst 9 - r LREM lst -2 "one" - assert_equal [read_big_bulk {r rpop lst}] $str_length - r llen lst - } {1} {large-memory} - - test {Test LSET on plain nodes over 4GB} { - r flushdb - r RPUSH lst "aa" - r RPUSH lst "bb" - r RPUSH lst "cc" - r write "*4\r\n\$4\r\nLSET\r\n\$3\r\nlst\r\n\$1\r\n0\r\n" - write_big_bulk $str_length; - assert_equal [r rpop lst] "cc" - assert_equal [r rpop lst] "bb" - assert_equal [read_big_bulk {r rpop lst}] $str_length - } {} {large-memory} - - test {Test LMOVE on plain nodes over 4GB} { - r flushdb - r RPUSH lst2{t} "aa" - r RPUSH lst2{t} "bb" - r write "*4\r\n\$4\r\nLSET\r\n\$7\r\nlst2{t}\r\n\$1\r\n0\r\n" - write_big_bulk $str_length; - r RPUSH lst2{t} "cc" - r RPUSH lst2{t} "dd" - r LMOVE lst2{t} lst{t} RIGHT LEFT - assert_equal [read_big_bulk {r LMOVE lst2{t} lst{t} LEFT RIGHT}] $str_length - assert_equal [r llen lst{t}] 2 - assert_equal [r llen lst2{t}] 2 - assert_equal [r lpop lst2{t}] "bb" - assert_equal [r lpop lst2{t}] "cc" - assert_equal [r lpop lst{t}] "dd" - assert_equal [read_big_bulk {r rpop lst{t}}] $str_length - } {} {large-memory} - - # restore defaults - r config set proto-max-bulk-len 536870912 - r config set client-query-buffer-limit 1073741824 - -} ;# skip 32bit builds -} -} ;# run_solo - -start_server { - tags {"list"} - overrides { - "list-max-ziplist-size" -1 - } -} { - source "tests/unit/type/list-common.tcl" - - # A helper function to execute either B*POP or BLMPOP* with one input key. - proc bpop_command {rd pop key timeout} { - if {$pop == "BLMPOP_LEFT"} { - $rd blmpop $timeout 1 $key left count 1 - } elseif {$pop == "BLMPOP_RIGHT"} { - $rd blmpop $timeout 1 $key right count 1 - } else { - $rd $pop $key $timeout - } - } - - # A helper function to execute either B*POP or BLMPOP* with two input keys. - proc bpop_command_two_key {rd pop key key2 timeout} { - if {$pop == "BLMPOP_LEFT"} { - $rd blmpop $timeout 2 $key $key2 left count 1 - } elseif {$pop == "BLMPOP_RIGHT"} { - $rd blmpop $timeout 2 $key $key2 right count 1 - } else { - $rd $pop $key $key2 $timeout - } - } - - proc create_listpack {key entries} { - r del $key - foreach entry $entries { r rpush $key $entry } - assert_encoding listpack $key - } - - proc create_quicklist {key entries} { - r del $key - foreach entry $entries { r rpush $key $entry } - assert_encoding quicklist $key - } - -foreach {type large} [array get largevalue] { - test "LPOS basic usage - $type" { - r DEL mylist - r RPUSH mylist a b c $large 2 3 c c - assert {[r LPOS mylist a] == 0} - assert {[r LPOS mylist c] == 2} - } - - test {LPOS RANK (positive, negative and zero rank) option} { - assert {[r LPOS mylist c RANK 1] == 2} - assert {[r LPOS mylist c RANK 2] == 6} - assert {[r LPOS mylist c RANK 4] eq ""} - assert {[r LPOS mylist c RANK -1] == 7} - assert {[r LPOS mylist c RANK -2] == 6} - assert_error "*RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start*" {r LPOS mylist c RANK 0} - assert_error "*value is out of range*" {r LPOS mylist c RANK -9223372036854775808} - } - - test {LPOS COUNT option} { - assert {[r LPOS mylist c COUNT 0] == {2 6 7}} - assert {[r LPOS mylist c COUNT 1] == {2}} - assert {[r LPOS mylist c COUNT 2] == {2 6}} - assert {[r LPOS mylist c COUNT 100] == {2 6 7}} - } - - test {LPOS COUNT + RANK option} { - assert {[r LPOS mylist c COUNT 0 RANK 2] == {6 7}} - assert {[r LPOS mylist c COUNT 2 RANK -1] == {7 6}} - } - - test {LPOS non existing key} { - assert {[r LPOS mylistxxx c COUNT 0 RANK 2] eq {}} - } - - test {LPOS no match} { - assert {[r LPOS mylist x COUNT 2 RANK -1] eq {}} - assert {[r LPOS mylist x RANK -1] eq {}} - } - - test {LPOS MAXLEN} { - assert {[r LPOS mylist a COUNT 0 MAXLEN 1] == {0}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 1] == {}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 3] == {2}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 3 RANK -1] == {7 6}} - assert {[r LPOS mylist c COUNT 0 MAXLEN 7 RANK 2] == {6}} - } - - test {LPOS when RANK is greater than matches} { - r DEL mylist - r LPUSH mylist a - assert {[r LPOS mylist b COUNT 10 RANK 5] eq {}} - } - - test "LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - $type" { - # first lpush then rpush - r del mylist1 - assert_equal 1 [r lpush mylist1 $large] - assert_encoding $type mylist1 - assert_equal 2 [r rpush mylist1 b] - assert_equal 3 [r rpush mylist1 c] - assert_equal 3 [r llen mylist1] - assert_equal $large [r lindex mylist1 0] - assert_equal b [r lindex mylist1 1] - assert_equal c [r lindex mylist1 2] - assert_equal {} [r lindex mylist1 3] - assert_equal c [r rpop mylist1] - assert_equal $large [r lpop mylist1] - - # first rpush then lpush - r del mylist2 - assert_equal 1 [r rpush mylist2 $large] - assert_equal 2 [r lpush mylist2 b] - assert_equal 3 [r lpush mylist2 c] - assert_encoding $type mylist2 - assert_equal 3 [r llen mylist2] - assert_equal c [r lindex mylist2 0] - assert_equal b [r lindex mylist2 1] - assert_equal $large [r lindex mylist2 2] - assert_equal {} [r lindex mylist2 3] - assert_equal $large [r rpop mylist2] - assert_equal c [r lpop mylist2] - } - - test "LPOP/RPOP with wrong number of arguments" { - assert_error {*wrong number of arguments for 'lpop' command} {r lpop key 1 1} - assert_error {*wrong number of arguments for 'rpop' command} {r rpop key 2 2} - } - - test "RPOP/LPOP with the optional count argument - $type" { - assert_equal 7 [r lpush listcount aa $large cc dd ee ff gg] - assert_equal {gg} [r lpop listcount 1] - assert_equal {ff ee} [r lpop listcount 2] - assert_equal "aa $large" [r rpop listcount 2] - assert_equal {cc} [r rpop listcount 1] - assert_equal {dd} [r rpop listcount 123] - assert_error "*ERR*range*" {r lpop forbarqaz -123} - } -} - - proc verify_resp_response {resp response resp2_response resp3_response} { - if {$resp == 2} { - assert_equal $response $resp2_response - } elseif {$resp == 3} { - assert_equal $response $resp3_response - } - } - - foreach resp {3 2} { - if {[lsearch $::denytags "resp3"] >= 0} { - if {$resp == 3} {continue} - } elseif {$::force_resp3} { - if {$resp == 2} {continue} - } - r hello $resp - - # Make sure we can distinguish between an empty array and a null response - r readraw 1 - - test "LPOP/RPOP with the count 0 returns an empty array in RESP$resp" { - r lpush listcount zero - assert_equal {*0} [r lpop listcount 0] - assert_equal {*0} [r rpop listcount 0] - } - - test "LPOP/RPOP against non existing key in RESP$resp" { - r del non_existing_key - - verify_resp_response $resp [r lpop non_existing_key] {$-1} {_} - verify_resp_response $resp [r rpop non_existing_key] {$-1} {_} - } - - test "LPOP/RPOP with against non existing key in RESP$resp" { - r del non_existing_key - - verify_resp_response $resp [r lpop non_existing_key 0] {*-1} {_} - verify_resp_response $resp [r lpop non_existing_key 1] {*-1} {_} - - verify_resp_response $resp [r rpop non_existing_key 0] {*-1} {_} - verify_resp_response $resp [r rpop non_existing_key 1] {*-1} {_} - } - - r readraw 0 - r hello 2 - } - - test {Variadic RPUSH/LPUSH} { - r del mylist - assert_equal 4 [r lpush mylist a b c d] - assert_equal 8 [r rpush mylist 0 1 2 3] - assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1] - } - - test {DEL a list} { - assert_equal 1 [r del mylist2] - assert_equal 0 [r exists mylist2] - assert_equal 0 [r llen mylist2] - } - - foreach {type large} [array get largevalue] { - foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop: single existing list - $type" { - set rd [redis_deferring_client] - create_$type blist "a b $large c d" - - bpop_command $rd $pop blist 1 - assert_equal {blist a} [$rd read] - if {$pop == "BLPOP"} { - bpop_command $rd BRPOP blist 1 - } else { - bpop_command $rd BLMPOP_RIGHT blist 1 - } - assert_equal {blist d} [$rd read] - - bpop_command $rd $pop blist 1 - assert_equal {blist b} [$rd read] - if {$pop == "BLPOP"} { - bpop_command $rd BRPOP blist 1 - } else { - bpop_command $rd BLMPOP_RIGHT blist 1 - } - assert_equal {blist c} [$rd read] - - assert_equal 1 [r llen blist] - $rd close - } - - test "$pop: multiple existing lists - $type" { - set rd [redis_deferring_client] - create_$type blist1{t} "a $large c" - create_$type blist2{t} "d $large f" - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - assert_equal {blist1{t} a} [$rd read] - if {$pop == "BLPOP"} { - bpop_command_two_key $rd BRPOP blist1{t} blist2{t} 1 - } else { - bpop_command_two_key $rd BLMPOP_RIGHT blist1{t} blist2{t} 1 - } - assert_equal {blist1{t} c} [$rd read] - assert_equal 1 [r llen blist1{t}] - assert_equal 3 [r llen blist2{t}] - - bpop_command_two_key $rd $pop blist2{t} blist1{t} 1 - assert_equal {blist2{t} d} [$rd read] - if {$pop == "BLPOP"} { - bpop_command_two_key $rd BRPOP blist2{t} blist1{t} 1 - } else { - bpop_command_two_key $rd BLMPOP_RIGHT blist2{t} blist1{t} 1 - } - assert_equal {blist2{t} f} [$rd read] - assert_equal 1 [r llen blist1{t}] - assert_equal 1 [r llen blist2{t}] - $rd close - } - - test "$pop: second list has an entry - $type" { - set rd [redis_deferring_client] - r del blist1{t} - create_$type blist2{t} "d $large f" - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - assert_equal {blist2{t} d} [$rd read] - if {$pop == "BLPOP"} { - bpop_command_two_key $rd BRPOP blist1{t} blist2{t} 1 - } else { - bpop_command_two_key $rd BLMPOP_RIGHT blist1{t} blist2{t} 1 - } - assert_equal {blist2{t} f} [$rd read] - assert_equal 0 [r llen blist1{t}] - assert_equal 1 [r llen blist2{t}] - $rd close - } - } - - test "BRPOPLPUSH - $type" { - r del target{t} - r rpush target{t} bar - - set rd [redis_deferring_client] - create_$type blist{t} "a b $large c d" - - $rd brpoplpush blist{t} target{t} 1 - assert_equal d [$rd read] - - assert_equal d [r lpop target{t}] - assert_equal "a b $large c" [r lrange blist{t} 0 -1] - $rd close - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "BLMOVE $wherefrom $whereto - $type" { - r del target{t} - r rpush target{t} bar - - set rd [redis_deferring_client] - create_$type blist{t} "a b $large c d" - - $rd blmove blist{t} target{t} $wherefrom $whereto 1 - set poppedelement [$rd read] - - if {$wherefrom eq "right"} { - assert_equal d $poppedelement - assert_equal "a b $large c" [r lrange blist{t} 0 -1] - } else { - assert_equal a $poppedelement - assert_equal "b $large c d" [r lrange blist{t} 0 -1] - } - - if {$whereto eq "right"} { - assert_equal $poppedelement [r rpop target{t}] - } else { - assert_equal $poppedelement [r lpop target{t}] - } - $rd close - } - } - } - } - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop, LPUSH + DEL should not awake blocked client" { - set rd [redis_deferring_client] - r del list - - bpop_command $rd $pop list 0 - wait_for_blocked_client - - r multi - r lpush list a - r del list - r exec - r del list - r lpush list b - assert_equal {list b} [$rd read] - $rd close - } - - test "$pop, LPUSH + DEL + SET should not awake blocked client" { - set rd [redis_deferring_client] - r del list - - bpop_command $rd $pop list 0 - wait_for_blocked_client - - r multi - r lpush list a - r del list - r set list foo - r exec - r del list - r lpush list b - assert_equal {list b} [$rd read] - $rd close - } -} - - test "BLPOP with same key multiple times should work (issue #801)" { - set rd [redis_deferring_client] - r del list1{t} list2{t} - - # Data arriving after the BLPOP. - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - wait_for_blocked_client - r lpush list1{t} a - assert_equal [$rd read] {list1{t} a} - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - wait_for_blocked_client - r lpush list2{t} b - assert_equal [$rd read] {list2{t} b} - - # Data already there. - r lpush list1{t} a - r lpush list2{t} b - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - assert_equal [$rd read] {list1{t} a} - $rd blpop list1{t} list2{t} list2{t} list1{t} 0 - assert_equal [$rd read] {list2{t} b} - $rd close - } - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "MULTI/EXEC is isolated from the point of view of $pop" { - set rd [redis_deferring_client] - r del list - - bpop_command $rd $pop list 0 - wait_for_blocked_client - - r multi - r lpush list a - r lpush list b - r lpush list c - r exec - assert_equal {list c} [$rd read] - $rd close - } - - test "$pop with variadic LPUSH" { - set rd [redis_deferring_client] - r del blist - bpop_command $rd $pop blist 0 - wait_for_blocked_client - assert_equal 2 [r lpush blist foo bar] - assert_equal {blist bar} [$rd read] - assert_equal foo [lindex [r lrange blist 0 -1] 0] - $rd close - } -} - - test "BRPOPLPUSH with zero timeout should block indefinitely" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r rpush target{t} bar - $rd brpoplpush blist{t} target{t} 0 - wait_for_blocked_clients_count 1 - r rpush blist{t} foo - assert_equal foo [$rd read] - assert_equal {foo bar} [r lrange target{t} 0 -1] - $rd close - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "BLMOVE $wherefrom $whereto with zero timeout should block indefinitely" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r rpush target{t} bar - $rd blmove blist{t} target{t} $wherefrom $whereto 0 - wait_for_blocked_clients_count 1 - r rpush blist{t} foo - assert_equal foo [$rd read] - if {$whereto eq "right"} { - assert_equal {bar foo} [r lrange target{t} 0 -1] - } else { - assert_equal {foo bar} [r lrange target{t} 0 -1] - } - $rd close - } - } - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "BLMOVE ($wherefrom, $whereto) with a client BLPOPing the target list" { - set rd [redis_deferring_client] - set rd2 [redis_deferring_client] - r del blist{t} target{t} - $rd2 blpop target{t} 0 - wait_for_blocked_clients_count 1 - $rd blmove blist{t} target{t} $wherefrom $whereto 0 - wait_for_blocked_clients_count 2 - r rpush blist{t} foo - assert_equal foo [$rd read] - assert_equal {target{t} foo} [$rd2 read] - assert_equal 0 [r exists target{t}] - $rd close - $rd2 close - } - } - } - - test "BRPOPLPUSH with wrong source type" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r set blist{t} nolist - $rd brpoplpush blist{t} target{t} 1 - assert_error "WRONGTYPE*" {$rd read} - $rd close - } - - test "BRPOPLPUSH with wrong destination type" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r set target{t} nolist - r lpush blist{t} foo - $rd brpoplpush blist{t} target{t} 1 - assert_error "WRONGTYPE*" {$rd read} - $rd close - - set rd [redis_deferring_client] - r del blist{t} target{t} - r set target{t} nolist - $rd brpoplpush blist{t} target{t} 0 - wait_for_blocked_clients_count 1 - r rpush blist{t} foo - assert_error "WRONGTYPE*" {$rd read} - assert_equal {foo} [r lrange blist{t} 0 -1] - $rd close - } - - test "BRPOPLPUSH maintains order of elements after failure" { - set rd [redis_deferring_client] - r del blist{t} target{t} - r set target{t} nolist - $rd brpoplpush blist{t} target{t} 0 - wait_for_blocked_client - r rpush blist{t} a b c - assert_error "WRONGTYPE*" {$rd read} - $rd close - r lrange blist{t} 0 -1 - } {a b c} - - test "BRPOPLPUSH with multiple blocked clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - r del blist{t} target1{t} target2{t} - r set target1{t} nolist - $rd1 brpoplpush blist{t} target1{t} 0 - wait_for_blocked_clients_count 1 - $rd2 brpoplpush blist{t} target2{t} 0 - wait_for_blocked_clients_count 2 - r lpush blist{t} foo - - assert_error "WRONGTYPE*" {$rd1 read} - assert_equal {foo} [$rd2 read] - assert_equal {foo} [r lrange target2{t} 0 -1] - $rd1 close - $rd2 close - } - - test "BLMPOP with multiple blocked clients" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - set rd4 [redis_deferring_client] - r del blist{t} blist2{t} - - $rd1 blmpop 0 2 blist{t} blist2{t} left count 1 - wait_for_blocked_clients_count 1 - $rd2 blmpop 0 2 blist{t} blist2{t} right count 10 - wait_for_blocked_clients_count 2 - $rd3 blmpop 0 2 blist{t} blist2{t} left count 10 - wait_for_blocked_clients_count 3 - $rd4 blmpop 0 2 blist{t} blist2{t} right count 1 - wait_for_blocked_clients_count 4 - - r multi - r lpush blist{t} a b c d e - r lpush blist2{t} 1 2 3 4 5 - r exec - - assert_equal {blist{t} e} [$rd1 read] - assert_equal {blist{t} {a b c d}} [$rd2 read] - assert_equal {blist2{t} {5 4 3 2 1}} [$rd3 read] - - r lpush blist2{t} 1 2 3 - assert_equal {blist2{t} 1} [$rd4 read] - $rd1 close - $rd2 close - $rd3 close - $rd4 close - } - - test "Linked LMOVEs" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - r del list1{t} list2{t} list3{t} - - $rd1 blmove list1{t} list2{t} right left 0 - wait_for_blocked_clients_count 1 - $rd2 blmove list2{t} list3{t} left right 0 - wait_for_blocked_clients_count 2 - - r rpush list1{t} foo - - assert_equal {} [r lrange list1{t} 0 -1] - assert_equal {} [r lrange list2{t} 0 -1] - assert_equal {foo} [r lrange list3{t} 0 -1] - $rd1 close - $rd2 close - } - - test "Circular BRPOPLPUSH" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - r del list1{t} list2{t} - - $rd1 brpoplpush list1{t} list2{t} 0 - wait_for_blocked_clients_count 1 - $rd2 brpoplpush list2{t} list1{t} 0 - wait_for_blocked_clients_count 2 - - r rpush list1{t} foo - - assert_equal {foo} [r lrange list1{t} 0 -1] - assert_equal {} [r lrange list2{t} 0 -1] - $rd1 close - $rd2 close - } - - test "Self-referential BRPOPLPUSH" { - set rd [redis_deferring_client] - - r del blist{t} - - $rd brpoplpush blist{t} blist{t} 0 - wait_for_blocked_client - - r rpush blist{t} foo - - assert_equal {foo} [r lrange blist{t} 0 -1] - $rd close - } - - test "BRPOPLPUSH inside a transaction" { - r del xlist{t} target{t} - r lpush xlist{t} foo - r lpush xlist{t} bar - - r multi - r brpoplpush xlist{t} target{t} 0 - r brpoplpush xlist{t} target{t} 0 - r brpoplpush xlist{t} target{t} 0 - r lrange xlist{t} 0 -1 - r lrange target{t} 0 -1 - r exec - } {foo bar {} {} {bar foo}} - - test "PUSH resulting from BRPOPLPUSH affect WATCH" { - set blocked_client [redis_deferring_client] - set watching_client [redis_deferring_client] - r del srclist{t} dstlist{t} somekey{t} - r set somekey{t} somevalue - $blocked_client brpoplpush srclist{t} dstlist{t} 0 - wait_for_blocked_client - $watching_client watch dstlist{t} - $watching_client read - $watching_client multi - $watching_client read - $watching_client get somekey{t} - $watching_client read - r lpush srclist{t} element - $watching_client exec - set res [$watching_client read] - $blocked_client close - $watching_client close - set _ $res - } {} - - test "BRPOPLPUSH does not affect WATCH while still blocked" { - set blocked_client [redis_deferring_client] - set watching_client [redis_deferring_client] - r del srclist{t} dstlist{t} somekey{t} - r set somekey{t} somevalue - $blocked_client brpoplpush srclist{t} dstlist{t} 0 - wait_for_blocked_client - $watching_client watch dstlist{t} - $watching_client read - $watching_client multi - $watching_client read - $watching_client get somekey{t} - $watching_client read - $watching_client exec - # Blocked BLPOPLPUSH may create problems, unblock it. - r lpush srclist{t} element - set res [$watching_client read] - $blocked_client close - $watching_client close - set _ $res - } {somevalue} - - test {BRPOPLPUSH timeout} { - set rd [redis_deferring_client] - - $rd brpoplpush foo_list{t} bar_list{t} 1 - wait_for_blocked_clients_count 1 - wait_for_blocked_clients_count 0 500 10 - set res [$rd read] - $rd close - set _ $res - } {} - - test {SWAPDB awakes blocked client} { - r flushall - r select 1 - r rpush k hello - r select 9 - set rd [redis_deferring_client] - $rd brpop k 5 - wait_for_blocked_clients_count 1 - r swapdb 1 9 - $rd read - } {k hello} {singledb:skip} - - test {SWAPDB wants to wake blocked client, but the key already expired} { - set repl [attach_to_replication_stream] - r flushall - r debug set-active-expire 0 - r select 1 - r rpush k hello - r pexpire k 100 - set rd [redis_deferring_client] - $rd deferred 0 - $rd select 9 - set id [$rd client id] - $rd deferred 1 - $rd brpop k 1 - wait_for_blocked_clients_count 1 - after 101 - r swapdb 1 9 - # The SWAPDB command tries to awake the blocked client, but it remains - # blocked because the key is expired. Check that the deferred client is - # still blocked. Then unblock it. - assert_match "*flags=b*" [r client list id $id] - r client unblock $id - assert_equal {} [$rd read] - $rd deferred 0 - # We want to force key deletion to be propagated to the replica - # in order to verify it was expired on the replication stream. - $rd set somekey1 someval1 - $rd exists k - r set somekey2 someval2 - - assert_replication_stream $repl { - {select *} - {flushall} - {select 1} - {rpush k hello} - {pexpireat k *} - {swapdb 1 9} - {select 9} - {set somekey1 someval1} - {del k} - {select 1} - {set somekey2 someval2} - } - close_replication_stream $repl - r debug set-active-expire 1 - # Restore server and client state - r select 9 - } {OK} {singledb:skip needs:debug} - - test {MULTI + LPUSH + EXPIRE + DEBUG SLEEP on blocked client, key already expired} { - set repl [attach_to_replication_stream] - r flushall - r debug set-active-expire 0 - - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - $rd brpop k 0 - wait_for_blocked_clients_count 1 - - r multi - r rpush k hello - r pexpire k 100 - r debug sleep 0.2 - r exec - - # The EXEC command tries to awake the blocked client, but it remains - # blocked because the key is expired. Check that the deferred client is - # still blocked. Then unblock it. - assert_match "*flags=b*" [r client list id $id] - r client unblock $id - assert_equal {} [$rd read] - # We want to force key deletion to be propagated to the replica - # in order to verify it was expired on the replication stream. - $rd exists k - assert_equal {0} [$rd read] - assert_replication_stream $repl { - {select *} - {flushall} - {multi} - {rpush k hello} - {pexpireat k *} - {exec} - {del k} - } - close_replication_stream $repl - # Restore server and client state - r debug set-active-expire 1 - r select 9 - } {OK} {singledb:skip needs:debug} - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop when new key is moved into place" { - set rd [redis_deferring_client] - r del foo{t} - - bpop_command $rd $pop foo{t} 0 - wait_for_blocked_client - r lpush bob{t} abc def hij - r rename bob{t} foo{t} - set res [$rd read] - $rd close - set _ $res - } {foo{t} hij} - - test "$pop when result key is created by SORT..STORE" { - set rd [redis_deferring_client] - - # zero out list from previous test without explicit delete - r lpop foo{t} - r lpop foo{t} - r lpop foo{t} - - bpop_command $rd $pop foo{t} 5 - wait_for_blocked_client - r lpush notfoo{t} hello hola aguacate konichiwa zanzibar - r sort notfoo{t} ALPHA store foo{t} - set res [$rd read] - $rd close - set _ $res - } {foo{t} aguacate} -} - - test "BLPOP: timeout value out of range" { - # Timeout is parsed as float and multiplied by 1000, added mstime() - # and stored in long-long which might lead to out-of-range value. - # (Even though given timeout is smaller than LLONG_MAX, the result - # will be bigger) - assert_error "ERR *is out of range*" {r BLPOP blist1 0x7FFFFFFFFFFFFF} - } - - foreach {pop} {BLPOP BRPOP BLMPOP_LEFT BLMPOP_RIGHT} { - test "$pop: with single empty list argument" { - set rd [redis_deferring_client] - r del blist1 - bpop_command $rd $pop blist1 1 - wait_for_blocked_client - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - assert_equal 0 [r exists blist1] - $rd close - } - - test "$pop: with negative timeout" { - set rd [redis_deferring_client] - bpop_command $rd $pop blist1 -1 - assert_error "ERR *is negative*" {$rd read} - $rd close - } - - test "$pop: with non-integer timeout" { - set rd [redis_deferring_client] - r del blist1 - bpop_command $rd $pop blist1 0.1 - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - assert_equal 0 [r exists blist1] - $rd close - } - - test "$pop: with zero timeout should block indefinitely" { - # To test this, use a timeout of 0 and wait a second. - # The blocking pop should still be waiting for a push. - set rd [redis_deferring_client] - bpop_command $rd $pop blist1 0 - wait_for_blocked_client - r rpush blist1 foo - assert_equal {blist1 foo} [$rd read] - $rd close - } - - test "$pop: with 0.001 timeout should not block indefinitely" { - # Use a timeout of 0.001 and wait for the number of blocked clients to equal 0. - # Validate the empty read from the deferring client. - set rd [redis_deferring_client] - bpop_command $rd $pop blist1 0.001 - wait_for_blocked_clients_count 0 - assert_equal {} [$rd read] - $rd close - } - - test "$pop: second argument is not a list" { - set rd [redis_deferring_client] - r del blist1{t} blist2{t} - r set blist2{t} nolist{t} - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - assert_error "WRONGTYPE*" {$rd read} - $rd close - } - - test "$pop: timeout" { - set rd [redis_deferring_client] - r del blist1{t} blist2{t} - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - wait_for_blocked_client - assert_equal {} [$rd read] - $rd close - } - - test "$pop: arguments are empty" { - set rd [redis_deferring_client] - r del blist1{t} blist2{t} - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - wait_for_blocked_client - r rpush blist1{t} foo - assert_equal {blist1{t} foo} [$rd read] - assert_equal 0 [r exists blist1{t}] - assert_equal 0 [r exists blist2{t}] - - bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 - wait_for_blocked_client - r rpush blist2{t} foo - assert_equal {blist2{t} foo} [$rd read] - assert_equal 0 [r exists blist1{t}] - assert_equal 0 [r exists blist2{t}] - $rd close - } - } - -foreach {pop} {BLPOP BLMPOP_LEFT} { - test "$pop inside a transaction" { - r del xlist - r lpush xlist foo - r lpush xlist bar - r multi - - bpop_command r $pop xlist 0 - bpop_command r $pop xlist 0 - bpop_command r $pop xlist 0 - r exec - } {{xlist bar} {xlist foo} {}} -} - - test {BLMPOP propagate as pop with count command to replica} { - set rd [redis_deferring_client] - set repl [attach_to_replication_stream] - - # BLMPOP without being blocked. - r lpush mylist{t} a b c - r rpush mylist2{t} 1 2 3 - r blmpop 0 1 mylist{t} left count 1 - r blmpop 0 2 mylist{t} mylist2{t} right count 10 - r blmpop 0 2 mylist{t} mylist2{t} right count 10 - - # BLMPOP that gets blocked. - $rd blmpop 0 1 mylist{t} left count 1 - wait_for_blocked_client - r lpush mylist{t} a - $rd blmpop 0 2 mylist{t} mylist2{t} left count 5 - wait_for_blocked_client - r lpush mylist{t} a b c - $rd blmpop 0 2 mylist{t} mylist2{t} right count 10 - wait_for_blocked_client - r rpush mylist2{t} a b c - - # Released on timeout. - assert_equal {} [r blmpop 0.01 1 mylist{t} left count 10] - r set foo{t} bar ;# something else to propagate after, so we can make sure the above pop didn't. - - $rd close - - assert_replication_stream $repl { - {select *} - {lpush mylist{t} a b c} - {rpush mylist2{t} 1 2 3} - {lpop mylist{t} 1} - {rpop mylist{t} 2} - {rpop mylist2{t} 3} - {lpush mylist{t} a} - {lpop mylist{t} 1} - {lpush mylist{t} a b c} - {lpop mylist{t} 3} - {rpush mylist2{t} a b c} - {rpop mylist2{t} 3} - {set foo{t} bar} - } - close_replication_stream $repl - } {} {needs:repl} - - test {LPUSHX, RPUSHX - generic} { - r del xlist - assert_equal 0 [r lpushx xlist a] - assert_equal 0 [r llen xlist] - assert_equal 0 [r rpushx xlist a] - assert_equal 0 [r llen xlist] - } - - foreach {type large} [array get largevalue] { - test "LPUSHX, RPUSHX - $type" { - create_$type xlist "$large c" - assert_equal 3 [r rpushx xlist d] - assert_equal 4 [r lpushx xlist a] - assert_equal 6 [r rpushx xlist 42 x] - assert_equal 9 [r lpushx xlist y3 y2 y1] - assert_equal "y1 y2 y3 a $large c d 42 x" [r lrange xlist 0 -1] - } - - test "LINSERT - $type" { - create_$type xlist "a $large c d" - assert_equal 5 [r linsert xlist before c zz] "before c" - assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA" - assert_equal 6 [r linsert xlist after c yy] "after c" - assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB" - assert_equal 7 [r linsert xlist after d dd] "after d" - assert_equal -1 [r linsert xlist after bad ddd] "after bad" - assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC" - assert_equal 8 [r linsert xlist before a aa] "before a" - assert_equal -1 [r linsert xlist before bad aaa] "before bad" - assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD" - - # check inserting integer encoded value - assert_equal 9 [r linsert xlist before aa 42] "before aa" - assert_equal 42 [r lrange xlist 0 0] "lrangeE" - } - } - - test {LINSERT raise error on bad syntax} { - catch {[r linsert xlist aft3r aa 42]} e - set e - } {*ERR*syntax*error*} - - test {LINSERT against non-list value error} { - r set k1 v1 - assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r linsert k1 after 0 0} - } - - test {LINSERT against non existing key} { - assert_equal 0 [r linsert not-a-key before 0 0] - } - -foreach type {listpack quicklist} { - foreach {num} {250 500} { - if {$type == "quicklist"} { - set origin_config [config_get_set list-max-listpack-size 5] - } else { - set origin_config [config_get_set list-max-listpack-size -1] - } - - proc check_numbered_list_consistency {key} { - set len [r llen $key] - for {set i 0} {$i < $len} {incr i} { - assert_equal $i [r lindex $key $i] - assert_equal [expr $len-1-$i] [r lindex $key [expr (-$i)-1]] - } - } - - proc check_random_access_consistency {key} { - set len [r llen $key] - for {set i 0} {$i < $len} {incr i} { - set rint [expr int(rand()*$len)] - assert_equal $rint [r lindex $key $rint] - assert_equal [expr $len-1-$rint] [r lindex $key [expr (-$rint)-1]] - } - } - - test "LINDEX consistency test - $type" { - r del mylist - for {set i 0} {$i < $num} {incr i} { - r rpush mylist $i - } - assert_encoding $type mylist - check_numbered_list_consistency mylist - } - - test "LINDEX random access - $type" { - assert_encoding $type mylist - check_random_access_consistency mylist - } - - test "Check if list is still ok after a DEBUG RELOAD - $type" { - r debug reload - assert_encoding $type mylist - check_numbered_list_consistency mylist - check_random_access_consistency mylist - } {} {needs:debug} - - config_set list-max-listpack-size $origin_config - } -} - - test {LLEN against non-list value error} { - r del mylist - r set mylist foobar - assert_error WRONGTYPE* {r llen mylist} - } - - test {LLEN against non existing key} { - assert_equal 0 [r llen not-a-key] - } - - test {LINDEX against non-list value error} { - assert_error WRONGTYPE* {r lindex mylist 0} - } - - test {LINDEX against non existing key} { - assert_equal "" [r lindex not-a-key 10] - } - - test {LPUSH against non-list value error} { - assert_error WRONGTYPE* {r lpush mylist 0} - } - - test {RPUSH against non-list value error} { - assert_error WRONGTYPE* {r rpush mylist 0} - } - - foreach {type large} [array get largevalue] { - test "RPOPLPUSH base case - $type" { - r del mylist1{t} mylist2{t} - create_$type mylist1{t} "a $large c d" - assert_equal d [r rpoplpush mylist1{t} mylist2{t}] - assert_equal c [r rpoplpush mylist1{t} mylist2{t}] - assert_equal $large [r rpoplpush mylist1{t} mylist2{t}] - assert_equal "a" [r lrange mylist1{t} 0 -1] - assert_equal "$large c d" [r lrange mylist2{t} 0 -1] - assert_encoding listpack mylist1{t} ;# converted to listpack after shrinking - assert_encoding $type mylist2{t} - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "LMOVE $wherefrom $whereto base case - $type" { - r del mylist1{t} mylist2{t} - - if {$wherefrom eq "right"} { - create_$type mylist1{t} "c d $large a" - } else { - create_$type mylist1{t} "a $large c d" - } - assert_equal a [r lmove mylist1{t} mylist2{t} $wherefrom $whereto] - assert_equal $large [r lmove mylist1{t} mylist2{t} $wherefrom $whereto] - assert_equal "c d" [r lrange mylist1{t} 0 -1] - if {$whereto eq "right"} { - assert_equal "a $large" [r lrange mylist2{t} 0 -1] - } else { - assert_equal "$large a" [r lrange mylist2{t} 0 -1] - } - assert_encoding $type mylist2{t} - } - } - } - - test "RPOPLPUSH with the same list as src and dst - $type" { - create_$type mylist{t} "a $large c" - assert_equal "a $large c" [r lrange mylist{t} 0 -1] - assert_equal c [r rpoplpush mylist{t} mylist{t}] - assert_equal "c a $large" [r lrange mylist{t} 0 -1] - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "LMOVE $wherefrom $whereto with the same list as src and dst - $type" { - if {$wherefrom eq "right"} { - create_$type mylist{t} "a $large c" - assert_equal "a $large c" [r lrange mylist{t} 0 -1] - } else { - create_$type mylist{t} "c a $large" - assert_equal "c a $large" [r lrange mylist{t} 0 -1] - } - assert_equal c [r lmove mylist{t} mylist{t} $wherefrom $whereto] - if {$whereto eq "right"} { - assert_equal "a $large c" [r lrange mylist{t} 0 -1] - } else { - assert_equal "c a $large" [r lrange mylist{t} 0 -1] - } - } - } - } - - foreach {othertype otherlarge} [array get largevalue] { - test "RPOPLPUSH with $type source and existing target $othertype" { - create_$type srclist{t} "a b c $large" - create_$othertype dstlist{t} "$otherlarge" - assert_equal $large [r rpoplpush srclist{t} dstlist{t}] - assert_equal c [r rpoplpush srclist{t} dstlist{t}] - assert_equal "a b" [r lrange srclist{t} 0 -1] - assert_equal "c $large $otherlarge" [r lrange dstlist{t} 0 -1] - - # When we rpoplpush'ed a large value, dstlist should be - # converted to the same encoding as srclist. - if {$type eq "quicklist"} { - assert_encoding quicklist dstlist{t} - } - } - - foreach wherefrom {left right} { - foreach whereto {left right} { - test "LMOVE $wherefrom $whereto with $type source and existing target $othertype" { - create_$othertype dstlist{t} "$otherlarge" - - if {$wherefrom eq "right"} { - create_$type srclist{t} "a b c $large" - } else { - create_$type srclist{t} "$large c a b" - } - assert_equal $large [r lmove srclist{t} dstlist{t} $wherefrom $whereto] - assert_equal c [r lmove srclist{t} dstlist{t} $wherefrom $whereto] - assert_equal "a b" [r lrange srclist{t} 0 -1] - - if {$whereto eq "right"} { - assert_equal "$otherlarge $large c" [r lrange dstlist{t} 0 -1] - } else { - assert_equal "c $large $otherlarge" [r lrange dstlist{t} 0 -1] - } - - # When we lmoved a large value, dstlist should be - # converted to the same encoding as srclist. - if {$type eq "quicklist"} { - assert_encoding quicklist dstlist{t} - } - } - } - } - } - } - - test {RPOPLPUSH against non existing key} { - r del srclist{t} dstlist{t} - assert_equal {} [r rpoplpush srclist{t} dstlist{t}] - assert_equal 0 [r exists srclist{t}] - assert_equal 0 [r exists dstlist{t}] - } - - test {RPOPLPUSH against non list src key} { - r del srclist{t} dstlist{t} - r set srclist{t} x - assert_error WRONGTYPE* {r rpoplpush srclist{t} dstlist{t}} - assert_type string srclist{t} - assert_equal 0 [r exists newlist{t}] - } - -foreach {type large} [array get largevalue] { - test "RPOPLPUSH against non list dst key - $type" { - create_$type srclist{t} "a $large c d" - r set dstlist{t} x - assert_error WRONGTYPE* {r rpoplpush srclist{t} dstlist{t}} - assert_type string dstlist{t} - assert_equal "a $large c d" [r lrange srclist{t} 0 -1] - } -} - - test {RPOPLPUSH against non existing src key} { - r del srclist{t} dstlist{t} - assert_equal {} [r rpoplpush srclist{t} dstlist{t}] - } {} - - foreach {type large} [array get largevalue] { - test "Basic LPOP/RPOP/LMPOP - $type" { - create_$type mylist "$large 1 2" - assert_equal $large [r lpop mylist] - assert_equal 2 [r rpop mylist] - assert_equal 1 [r lpop mylist] - assert_equal 0 [r llen mylist] - - create_$type mylist "$large 1 2" - assert_equal "mylist $large" [r lmpop 1 mylist left count 1] - assert_equal {mylist {2 1}} [r lmpop 2 mylist mylist right count 2] - } - } - - test {LPOP/RPOP/LMPOP against empty list} { - r del non-existing-list{t} non-existing-list2{t} - - assert_equal {} [r lpop non-existing-list{t}] - assert_equal {} [r rpop non-existing-list2{t}] - - assert_equal {} [r lmpop 1 non-existing-list{t} left count 1] - assert_equal {} [r lmpop 1 non-existing-list{t} left count 10] - assert_equal {} [r lmpop 2 non-existing-list{t} non-existing-list2{t} right count 1] - assert_equal {} [r lmpop 2 non-existing-list{t} non-existing-list2{t} right count 10] - } - - test {LPOP/RPOP/LMPOP NON-BLOCK or BLOCK against non list value} { - r set notalist{t} foo - assert_error WRONGTYPE* {r lpop notalist{t}} - assert_error WRONGTYPE* {r blpop notalist{t} 0} - assert_error WRONGTYPE* {r rpop notalist{t}} - assert_error WRONGTYPE* {r brpop notalist{t} 0} - - r del notalist2{t} - assert_error "WRONGTYPE*" {r lmpop 2 notalist{t} notalist2{t} left count 1} - assert_error "WRONGTYPE*" {r blmpop 0 2 notalist{t} notalist2{t} left count 1} - - r del notalist{t} - r set notalist2{t} nolist - assert_error "WRONGTYPE*" {r lmpop 2 notalist{t} notalist2{t} right count 10} - assert_error "WRONGTYPE*" {r blmpop 0 2 notalist{t} notalist2{t} left count 1} - } - - foreach {num} {250 500} { - test "Mass RPOP/LPOP - $type" { - r del mylist - set sum1 0 - for {set i 0} {$i < $num} {incr i} { - if {$i == [expr $num/2]} { - r lpush mylist $large - } - r lpush mylist $i - incr sum1 $i - } - assert_encoding $type mylist - set sum2 0 - for {set i 0} {$i < [expr $num/2]} {incr i} { - incr sum2 [r lpop mylist] - incr sum2 [r rpop mylist] - } - assert_equal $sum1 $sum2 - } - } - - test {LMPOP with illegal argument} { - assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop} - assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop 1} - assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop 1 mylist{t}} - - assert_error "ERR numkeys*" {r lmpop 0 mylist{t} LEFT} - assert_error "ERR numkeys*" {r lmpop a mylist{t} LEFT} - assert_error "ERR numkeys*" {r lmpop -1 mylist{t} RIGHT} - - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} bad_where} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} LEFT bar_arg} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} RIGHT LEFT} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} COUNT} - assert_error "ERR syntax error*" {r lmpop 1 mylist{t} LEFT COUNT 1 COUNT 2} - assert_error "ERR syntax error*" {r lmpop 2 mylist{t} mylist2{t} bad_arg} - - assert_error "ERR count*" {r lmpop 1 mylist{t} LEFT COUNT 0} - assert_error "ERR count*" {r lmpop 1 mylist{t} RIGHT COUNT a} - assert_error "ERR count*" {r lmpop 1 mylist{t} LEFT COUNT -1} - assert_error "ERR count*" {r lmpop 2 mylist{t} mylist2{t} RIGHT COUNT -1} - } - -foreach {type large} [array get largevalue] { - test "LMPOP single existing list - $type" { - # Same key multiple times. - create_$type mylist{t} "a b $large d e f" - assert_equal {mylist{t} {a b}} [r lmpop 2 mylist{t} mylist{t} left count 2] - assert_equal {mylist{t} {f e}} [r lmpop 2 mylist{t} mylist{t} right count 2] - assert_equal 2 [r llen mylist{t}] - - # First one exists, second one does not exist. - create_$type mylist{t} "a b $large d e" - r del mylist2{t} - assert_equal {mylist{t} a} [r lmpop 2 mylist{t} mylist2{t} left count 1] - assert_equal 4 [r llen mylist{t}] - assert_equal "mylist{t} {e d $large b}" [r lmpop 2 mylist{t} mylist2{t} right count 10] - assert_equal {} [r lmpop 2 mylist{t} mylist2{t} right count 1] - - # First one does not exist, second one exists. - r del mylist{t} - create_$type mylist2{t} "1 2 $large 4 5" - assert_equal {mylist2{t} 5} [r lmpop 2 mylist{t} mylist2{t} right count 1] - assert_equal 4 [r llen mylist2{t}] - assert_equal "mylist2{t} {1 2 $large 4}" [r lmpop 2 mylist{t} mylist2{t} left count 10] - - assert_equal 0 [r exists mylist{t} mylist2{t}] - } - - test "LMPOP multiple existing lists - $type" { - create_$type mylist{t} "a b $large d e" - create_$type mylist2{t} "1 2 $large 4 5" - - # Pop up from the first key. - assert_equal {mylist{t} {a b}} [r lmpop 2 mylist{t} mylist2{t} left count 2] - assert_equal 3 [r llen mylist{t}] - assert_equal "mylist{t} {e d $large}" [r lmpop 2 mylist{t} mylist2{t} right count 3] - assert_equal 0 [r exists mylist{t}] - - # Pop up from the second key. - assert_equal "mylist2{t} {1 2 $large}" [r lmpop 2 mylist{t} mylist2{t} left count 3] - assert_equal 2 [r llen mylist2{t}] - assert_equal {mylist2{t} {5 4}} [r lmpop 2 mylist{t} mylist2{t} right count 2] - assert_equal 0 [r exists mylist{t}] - - # Pop up all elements. - create_$type mylist{t} "a $large c" - create_$type mylist2{t} "1 $large 3" - assert_equal "mylist{t} {a $large c}" [r lmpop 2 mylist{t} mylist2{t} left count 10] - assert_equal 0 [r llen mylist{t}] - assert_equal "mylist2{t} {3 $large 1}" [r lmpop 2 mylist{t} mylist2{t} right count 10] - assert_equal 0 [r llen mylist2{t}] - assert_equal 0 [r exists mylist{t} mylist2{t}] - } -} - - test {LMPOP propagate as pop with count command to replica} { - set repl [attach_to_replication_stream] - - # left/right propagate as lpop/rpop with count - r lpush mylist{t} a b c - - # Pop elements from one list. - r lmpop 1 mylist{t} left count 1 - r lmpop 1 mylist{t} right count 1 - - # Now the list have only one element - r lmpop 2 mylist{t} mylist2{t} left count 10 - - # No elements so we don't propagate. - r lmpop 2 mylist{t} mylist2{t} left count 10 - - # Pop elements from the second list. - r rpush mylist2{t} 1 2 3 - r lmpop 2 mylist{t} mylist2{t} left count 2 - r lmpop 2 mylist{t} mylist2{t} right count 1 - - # Pop all elements. - r rpush mylist{t} a b c - r rpush mylist2{t} 1 2 3 - r lmpop 2 mylist{t} mylist2{t} left count 10 - r lmpop 2 mylist{t} mylist2{t} right count 10 - - assert_replication_stream $repl { - {select *} - {lpush mylist{t} a b c} - {lpop mylist{t} 1} - {rpop mylist{t} 1} - {lpop mylist{t} 1} - {rpush mylist2{t} 1 2 3} - {lpop mylist2{t} 2} - {rpop mylist2{t} 1} - {rpush mylist{t} a b c} - {rpush mylist2{t} 1 2 3} - {lpop mylist{t} 3} - {rpop mylist2{t} 3} - } - close_replication_stream $repl - } {} {needs:repl} - - foreach {type large} [array get largevalue] { - test "LRANGE basics - $type" { - create_$type mylist "$large 1 2 3 4 5 6 7 8 9" - assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2] - assert_equal {7 8 9} [r lrange mylist -3 -1] - assert_equal {4} [r lrange mylist 4 4] - } - - test "LRANGE inverted indexes - $type" { - create_$type mylist "$large 1 2 3 4 5 6 7 8 9" - assert_equal {} [r lrange mylist 6 2] - } - - test "LRANGE out of range indexes including the full list - $type" { - create_$type mylist "$large 1 2 3" - assert_equal "$large 1 2 3" [r lrange mylist -1000 1000] - } - - test "LRANGE out of range negative end index - $type" { - create_$type mylist "$large 1 2 3" - assert_equal $large [r lrange mylist 0 -4] - assert_equal {} [r lrange mylist 0 -5] - } - } - - test {LRANGE against non existing key} { - assert_equal {} [r lrange nosuchkey 0 1] - } - - test {LRANGE with start > end yields an empty array for backward compatibility} { - create_$type mylist "1 $large 3" - assert_equal {} [r lrange mylist 1 0] - assert_equal {} [r lrange mylist -1 -2] - } - - foreach {type large} [array get largevalue] { - proc trim_list {type min max} { - upvar 1 large large - r del mylist - create_$type mylist "1 2 3 4 $large" - r ltrim mylist $min $max - r lrange mylist 0 -1 - } - - test "LTRIM basics - $type" { - assert_equal "1" [trim_list $type 0 0] - assert_equal "1 2" [trim_list $type 0 1] - assert_equal "1 2 3" [trim_list $type 0 2] - assert_equal "2 3" [trim_list $type 1 2] - assert_equal "2 3 4 $large" [trim_list $type 1 -1] - assert_equal "2 3 4" [trim_list $type 1 -2] - assert_equal "4 $large" [trim_list $type -2 -1] - assert_equal "$large" [trim_list $type -1 -1] - assert_equal "1 2 3 4 $large" [trim_list $type -5 -1] - assert_equal "1 2 3 4 $large" [trim_list $type -10 10] - assert_equal "1 2 3 4 $large" [trim_list $type 0 5] - assert_equal "1 2 3 4 $large" [trim_list $type 0 10] - } - - test "LTRIM out of range negative end index - $type" { - assert_equal {1} [trim_list $type 0 -5] - assert_equal {} [trim_list $type 0 -6] - } - - test "LSET - $type" { - create_$type mylist "99 98 $large 96 95" - r lset mylist 1 foo - r lset mylist -1 bar - assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1] - } - - test "LSET out of range index - $type" { - assert_error ERR*range* {r lset mylist 10 foo} - } - } - - test {LSET against non existing key} { - assert_error ERR*key* {r lset nosuchkey 10 foo} - } - - test {LSET against non list value} { - r set nolist foobar - assert_error WRONGTYPE* {r lset nolist 0 foo} - } - - foreach {type e} [array get largevalue] { - test "LREM remove all the occurrences - $type" { - create_$type mylist "$e foo bar foobar foobared zap bar test foo" - assert_equal 2 [r lrem mylist 0 bar] - assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1] - } - - test "LREM remove the first occurrence - $type" { - assert_equal 1 [r lrem mylist 1 foo] - assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1] - } - - test "LREM remove non existing element - $type" { - assert_equal 0 [r lrem mylist 1 nosuchelement] - assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1] - } - - test "LREM starting from tail with negative count - $type" { - create_$type mylist "$e foo bar foobar foobared zap bar test foo foo" - assert_equal 1 [r lrem mylist -1 bar] - assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1] - } - - test "LREM starting from tail with negative count (2) - $type" { - assert_equal 2 [r lrem mylist -2 foo] - assert_equal "$e foo bar foobar foobared zap test" [r lrange mylist 0 -1] - } - - test "LREM deleting objects that may be int encoded - $type" { - create_$type myotherlist "$e 1 2 3" - assert_equal 1 [r lrem myotherlist 1 2] - assert_equal 3 [r llen myotherlist] - } - } - - test "Regression for bug 593 - chaining BRPOPLPUSH with other blocking cmds" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - $rd1 brpoplpush a{t} b{t} 0 - $rd1 brpoplpush a{t} b{t} 0 - wait_for_blocked_clients_count 1 - $rd2 brpoplpush b{t} c{t} 0 - wait_for_blocked_clients_count 2 - r lpush a{t} data - $rd1 close - $rd2 close - r ping - } {PONG} - - test "BLPOP/BLMOVE should increase dirty" { - r del lst{t} lst1{t} - set rd [redis_deferring_client] - - set dirty [s rdb_changes_since_last_save] - $rd blpop lst{t} 0 - wait_for_blocked_client - r lpush lst{t} a - assert_equal {lst{t} a} [$rd read] - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 2} - - set dirty [s rdb_changes_since_last_save] - $rd blmove lst{t} lst1{t} left left 0 - wait_for_blocked_client - r lpush lst{t} a - assert_equal {a} [$rd read] - set dirty2 [s rdb_changes_since_last_save] - assert {$dirty2 == $dirty + 2} - - $rd close - } - -foreach {pop} {BLPOP BLMPOP_RIGHT} { - test "client unblock tests" { - r del l - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - - # test default args - bpop_command $rd $pop l 0 - wait_for_blocked_client - r client unblock $id - assert_equal {} [$rd read] - - # test with timeout - bpop_command $rd $pop l 0 - wait_for_blocked_client - r client unblock $id TIMEOUT - assert_equal {} [$rd read] - - # test with error - bpop_command $rd $pop l 0 - wait_for_blocked_client - r client unblock $id ERROR - catch {[$rd read]} e - assert_equal $e "UNBLOCKED client unblocked via CLIENT UNBLOCK" - - # test with invalid client id - catch {[r client unblock asd]} e - assert_equal $e "ERR value is not an integer or out of range" - - # test with non blocked client - set myid [r client id] - catch {[r client unblock $myid]} e - assert_equal $e {invalid command name "0"} - - # finally, see the this client and list are still functional - bpop_command $rd $pop l 0 - wait_for_blocked_client - r lpush l foo - assert_equal {l foo} [$rd read] - $rd close - } -} - - foreach {max_lp_size large} "3 $largevalue(listpack) -1 $largevalue(quicklist)" { - test "List listpack -> quicklist encoding conversion" { - set origin_conf [config_get_set list-max-listpack-size $max_lp_size] - - # RPUSH - create_listpack lst "a b c" - r RPUSH lst $large - assert_encoding quicklist lst - - # LINSERT - create_listpack lst "a b c" - r LINSERT lst after b $large - assert_encoding quicklist lst - - # LSET - create_listpack lst "a b c" - r LSET lst 0 $large - assert_encoding quicklist lst - - # LMOVE - create_quicklist lsrc{t} "a b c $large" - create_listpack ldes{t} "d e f" - r LMOVE lsrc{t} ldes{t} right right - assert_encoding quicklist ldes{t} - - r config set list-max-listpack-size $origin_conf - } - } - - test "List quicklist -> listpack encoding conversion" { - set origin_conf [config_get_set list-max-listpack-size 3] - - # RPOP - create_quicklist lst "a b c d" - r RPOP lst 3 - assert_encoding listpack lst - - # LREM - create_quicklist lst "a a a d" - r LREM lst 3 a - assert_encoding listpack lst - - # LTRIM - create_quicklist lst "a b c d" - r LTRIM lst 1 1 - assert_encoding listpack lst - - r config set list-max-listpack-size -1 - - # RPOP - create_quicklist lst "a b c $largevalue(quicklist)" - r RPOP lst 1 - assert_encoding listpack lst - - # LREM - create_quicklist lst "a $largevalue(quicklist)" - r LREM lst 1 $largevalue(quicklist) - assert_encoding listpack lst - - # LTRIM - create_quicklist lst "a b $largevalue(quicklist)" - r LTRIM lst 0 1 - assert_encoding listpack lst - - # LSET - create_quicklist lst "$largevalue(quicklist) a b" - r RPOP lst 2 - assert_encoding quicklist lst - r LSET lst -1 c - assert_encoding listpack lst - - r config set list-max-listpack-size $origin_conf - } - - test "List encoding conversion when RDB loading" { - set origin_conf [config_get_set list-max-listpack-size 3] - create_listpack lst "a b c" - - # list is still a listpack after DEBUG RELOAD - r DEBUG RELOAD - assert_encoding listpack lst - - # list is still a quicklist after DEBUG RELOAD - r RPUSH lst d - r DEBUG RELOAD - assert_encoding quicklist lst - - # when a quicklist has only one packed node, it will be - # converted to listpack during rdb loading - r RPOP lst - assert_encoding quicklist lst - r DEBUG RELOAD - assert_encoding listpack lst - - r config set list-max-listpack-size $origin_conf - } {OK} {needs:debug} - - test "List invalid list-max-listpack-size config" { - # ​When list-max-listpack-size is 0 we treat it as 1 and it'll - # still be listpack if there's a single element in the list. - r config set list-max-listpack-size 0 - r DEL lst - r RPUSH lst a - assert_encoding listpack lst - r RPUSH lst b - assert_encoding quicklist lst - - # When list-max-listpack-size < -5 we treat it as -5. - r config set list-max-listpack-size -6 - r DEL lst - r RPUSH lst [string repeat "x" 60000] - assert_encoding listpack lst - # Converted to quicklist when the size of listpack exceed 65536 - r RPUSH lst [string repeat "x" 5536] - assert_encoding quicklist lst - } - - test "List of various encodings" { - r del k - r lpush k 127 ;# ZIP_INT_8B - r lpush k 32767 ;# ZIP_INT_16B - r lpush k 2147483647 ;# ZIP_INT_32B - r lpush k 9223372036854775808 ;# ZIP_INT_64B - r lpush k 0 ;# ZIP_INT_IMM_MIN - r lpush k 12 ;# ZIP_INT_IMM_MAX - r lpush k [string repeat x 31] ;# ZIP_STR_06B - r lpush k [string repeat x 8191] ;# ZIP_STR_14B - r lpush k [string repeat x 65535] ;# ZIP_STR_32B - assert_encoding quicklist k ;# exceeds the size limit of quicklist node - set k [r lrange k 0 -1] - set dump [r dump k] - - # coverage for objectComputeSize - assert_morethan [memory_usage k] 0 - - config_set sanitize-dump-payload no mayfail - r restore kk 0 $dump replace - assert_encoding quicklist kk - set kk [r lrange kk 0 -1] - - # try some forward and backward searches to make sure all encodings - # can be traversed - assert_equal [r lindex kk 5] {9223372036854775808} - assert_equal [r lindex kk -5] {0} - assert_equal [r lpos kk foo rank 1] {} - assert_equal [r lpos kk foo rank -1] {} - - # make sure the values are right - assert_equal $k $kk - assert_equal [lpop k] [string repeat x 65535] - assert_equal [lpop k] [string repeat x 8191] - assert_equal [lpop k] [string repeat x 31] - set _ $k - } {12 0 9223372036854775808 2147483647 32767 127} - - test "List of various encodings - sanitize dump" { - config_set sanitize-dump-payload yes mayfail - r restore kk 0 $dump replace - assert_encoding quicklist kk - set k [r lrange k 0 -1] - set kk [r lrange kk 0 -1] - - # make sure the values are right - assert_equal $k $kk - assert_equal [lpop k] [string repeat x 65535] - assert_equal [lpop k] [string repeat x 8191] - assert_equal [lpop k] [string repeat x 31] - set _ $k - } {12 0 9223372036854775808 2147483647 32767 127} - - test "Unblock fairness is kept while pipelining" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - - # delete the list in case already exists - r del mylist - - # block a client on the list - $rd1 BLPOP mylist 0 - wait_for_blocked_clients_count 1 - - # pipeline on other client a list push and a blocking pop - # we should expect the fairness to be kept and have $rd1 - # being unblocked - set buf "" - append buf "LPUSH mylist 1\r\n" - append buf "BLPOP mylist 0\r\n" - $rd2 write $buf - $rd2 flush - - # we check that we still have 1 blocked client - # and that the first blocked client has been served - assert_equal [$rd1 read] {mylist 1} - assert_equal [$rd2 read] {1} - wait_for_blocked_clients_count 1 - - # We no unblock the last client and verify it was served last - r LPUSH mylist 2 - wait_for_blocked_clients_count 0 - assert_equal [$rd2 read] {mylist 2} - - $rd1 close - $rd2 close - } - - test "Unblock fairness is kept during nested unblock" { - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - - # delete the list in case already exists - r del l1{t} l2{t} l3{t} - - # block a client on the list - $rd1 BRPOPLPUSH l1{t} l3{t} 0 - wait_for_blocked_clients_count 1 - - $rd2 BLPOP l2{t} 0 - wait_for_blocked_clients_count 2 - - $rd3 BLMPOP 0 2 l2{t} l3{t} LEFT COUNT 1 - wait_for_blocked_clients_count 3 - - r multi - r lpush l1{t} 1 - r lpush l2{t} 2 - r exec - - wait_for_blocked_clients_count 0 - - assert_equal [$rd1 read] {1} - assert_equal [$rd2 read] {l2{t} 2} - assert_equal [$rd3 read] {l3{t} 1} - - $rd1 close - $rd2 close - $rd3 close - } - - test "Blocking command accounted only once in commandstats" { - # cleanup first - r del mylist - - # create a test client - set rd [redis_deferring_client] - - # reset the server stats - r config resetstat - - # block a client on the list - $rd BLPOP mylist 0 - wait_for_blocked_clients_count 1 - - # unblock the list - r LPUSH mylist 1 - wait_for_blocked_clients_count 0 - - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdrstat blpop r] - - $rd close - } - - test "Blocking command accounted only once in commandstats after timeout" { - # cleanup first - r del mylist - - # create a test client - set rd [redis_deferring_client] - $rd client id - set id [$rd read] - - # reset the server stats - r config resetstat - - # block a client on the list - $rd BLPOP mylist 0 - wait_for_blocked_clients_count 1 - - # unblock the client on timeout - r client unblock $id timeout - - assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdrstat blpop r] - - $rd close - } - - test {Command being unblocked cause another command to get unblocked execution order test} { - r del src{t} dst{t} key1{t} key2{t} key3{t} - set repl [attach_to_replication_stream] - - set rd1 [redis_deferring_client] - set rd2 [redis_deferring_client] - set rd3 [redis_deferring_client] - - $rd1 blmove src{t} dst{t} left right 0 - wait_for_blocked_clients_count 1 - - $rd2 blmove dst{t} src{t} right left 0 - wait_for_blocked_clients_count 2 - - # Create a pipeline of commands that will be processed in one socket read. - # Insert two set commands before and after lpush to observe the execution order. - set buf "" - append buf "set key1{t} value1\r\n" - append buf "lpush src{t} dummy\r\n" - append buf "set key2{t} value2\r\n" - $rd3 write $buf - $rd3 flush - - wait_for_blocked_clients_count 0 - - r set key3{t} value3 - - # If a command being unblocked causes another command to get unblocked, like a BLMOVE would do, - # then the new unblocked command will get processed right away rather than wait for later. - # If the set command occurs between two lmove commands, the results are not as expected. - assert_replication_stream $repl { - {select *} - {set key1{t} value1} - {lpush src{t} dummy} - {lmove src{t} dst{t} left right} - {lmove dst{t} src{t} right left} - {set key2{t} value2} - {set key3{t} value3} - } - - $rd1 close - $rd2 close - $rd3 close - - close_replication_stream $repl - } {} {needs:repl} - -} ;# stop servers +# check functionality compression of plain and zipped nodes +start_server [list overrides [list save ""] ] { + r config set list-compress-depth 2 + r config set list-max-ziplist-size 1 + + # 3 test to check compression with regular ziplist nodes + # 1. using push + insert + # 2. using push + insert + trim + # 3. using push + insert + set + + test {reg node check compression with insert and pop} { + r lpush list1 [string repeat a 500] + r lpush list1 [string repeat b 500] + r lpush list1 [string repeat c 500] + r lpush list1 [string repeat d 500] + r linsert list1 after [string repeat d 500] [string repeat e 500] + r linsert list1 after [string repeat d 500] [string repeat f 500] + r linsert list1 after [string repeat d 500] [string repeat g 500] + r linsert list1 after [string repeat d 500] [string repeat j 500] + assert_equal [r lpop list1] [string repeat d 500] + assert_equal [r lpop list1] [string repeat j 500] + assert_equal [r lpop list1] [string repeat g 500] + assert_equal [r lpop list1] [string repeat f 500] + assert_equal [r lpop list1] [string repeat e 500] + assert_equal [r lpop list1] [string repeat c 500] + assert_equal [r lpop list1] [string repeat b 500] + assert_equal [r lpop list1] [string repeat a 500] + }; + + test {reg node check compression combined with trim} { + r lpush list2 [string repeat a 500] + r linsert list2 after [string repeat a 500] [string repeat b 500] + r rpush list2 [string repeat c 500] + assert_equal [string repeat b 500] [r lindex list2 1] + r LTRIM list2 1 -1 + r llen list2 + } {2} + + test {reg node check compression with lset} { + r lpush list3 [string repeat a 500] + r LSET list3 0 [string repeat b 500] + assert_equal [string repeat b 500] [r lindex list3 0] + r lpush list3 [string repeat c 500] + r LSET list3 0 [string repeat d 500] + assert_equal [string repeat d 500] [r lindex list3 0] + } + + # repeating the 3 tests with plain nodes + # (by adjusting list-max-listpack-size) + test {plain node check compression} { + set origin_config [config_get_set list-max-listpack-size 0] + r lpush list4 [string repeat a 500] + r lpush list4 [string repeat b 500] + r lpush list4 [string repeat c 500] + r lpush list4 [string repeat d 500] + r linsert list4 after [string repeat d 500] [string repeat e 500] + r linsert list4 after [string repeat d 500] [string repeat f 500] + r linsert list4 after [string repeat d 500] [string repeat g 500] + r linsert list4 after [string repeat d 500] [string repeat j 500] + assert_equal [r lpop list4] [string repeat d 500] + assert_equal [r lpop list4] [string repeat j 500] + assert_equal [r lpop list4] [string repeat g 500] + assert_equal [r lpop list4] [string repeat f 500] + assert_equal [r lpop list4] [string repeat e 500] + assert_equal [r lpop list4] [string repeat c 500] + assert_equal [r lpop list4] [string repeat b 500] + assert_equal [r lpop list4] [string repeat a 500] + config_set list-max-listpack-size $origin_config + } + + test {plain node check compression with ltrim} { + set origin_config [config_get_set list-max-listpack-size 0] + r lpush list5 [string repeat a 500] + r linsert list5 after [string repeat a 500] [string repeat b 500] + r rpush list5 [string repeat c 500] + assert_equal [string repeat b 500] [r lindex list5 1] + r LTRIM list5 1 -1 + assert_equal [r llen list5] 2 + config_set list-max-listpack-size $origin_config + } + + test {plain node check compression using lset} { + set origin_config [config_get_set list-max-listpack-size 0] + r lpush list6 [string repeat a 500] + r LSET list6 0 [string repeat b 500] + assert_equal [string repeat b 500] [r lindex list6 0] + r lpush list6 [string repeat c 500] + r LSET list6 0 [string repeat d 500] + assert_equal [string repeat d 500] [r lindex list6 0] + config_set list-max-listpack-size $origin_config + } + + # revert config for external mode tests. + r config set list-compress-depth 0 +} + +# check functionality of plain nodes using low packed-threshold +start_server [list overrides [list save ""] ] { + # basic command check for plain nodes - "LPUSH & LPOP" + test {Test LPUSH and LPOP on plain nodes} { + r flushdb + set origin_config [config_get_set list-max-listpack-size 0] + r lpush lst 9 + r lpush lst xxxxxxxxxx + r lpush lst xxxxxxxxxx + set s0 [s used_memory] + assert {$s0 > 10} + assert {[r llen lst] == 3} + set s0 [r rpop lst] + set s1 [r rpop lst] + assert {$s0 eq "9"} + assert {[r llen lst] == 1} + r lpop lst + assert {[string length $s1] == 10} + # check rdb + r lpush lst xxxxxxxxxx + r lpush lst bb + r debug reload + assert_equal [r rpop lst] "xxxxxxxxxx" + config_set list-max-listpack-size $origin_config + } + + # basic command check for plain nodes - "LINDEX & LINSERT" + test {Test LINDEX and LINSERT on plain nodes} { + r flushdb + set origin_config [config_get_set list-max-listpack-size 0] + r lpush lst xxxxxxxxxxx + r lpush lst 9 + r lpush lst xxxxxxxxxxx + r linsert lst before "9" "8" + assert {[r lindex lst 1] eq "8"} + r linsert lst BEFORE "9" "7" + r linsert lst BEFORE "9" "xxxxxxxxxxx" + assert {[r lindex lst 3] eq "xxxxxxxxxxx"} + config_set list-max-listpack-size $origin_config + } + + # basic command check for plain nodes - "LTRIM" + test {Test LTRIM on plain nodes} { + r flushdb + set origin_config [config_get_set list-max-listpack-size 0] + r lpush lst1 9 + r lpush lst1 xxxxxxxxxxx + r lpush lst1 9 + r LTRIM lst1 1 -1 + assert_equal [r llen lst1] 2 + config_set list-max-listpack-size $origin_config + } + + # basic command check for plain nodes - "LREM" + test {Test LREM on plain nodes} { + r flushdb + set origin_config [config_get_set list-max-listpack-size 0] + r lpush lst one + r lpush lst xxxxxxxxxxx + set s0 [s used_memory] + assert {$s0 > 10} + r lpush lst 9 + r LREM lst -2 "one" + assert_equal [r llen lst] 2 + config_set list-max-listpack-size $origin_config + } + + # basic command check for plain nodes - "LPOS" + test {Test LPOS on plain nodes} { + r flushdb + set origin_config [config_get_set list-max-listpack-size 0] + r RPUSH lst "aa" + r RPUSH lst "bb" + r RPUSH lst "cc" + r LSET lst 0 "xxxxxxxxxxx" + assert_equal [r LPOS lst "xxxxxxxxxxx"] 0 + config_set list-max-listpack-size $origin_config + } + + # basic command check for plain nodes - "LMOVE" + test {Test LMOVE on plain nodes} { + r flushdb + set origin_config [config_get_set list-max-listpack-size 0] + r RPUSH lst2{t} "aa" + r RPUSH lst2{t} "bb" + r LSET lst2{t} 0 xxxxxxxxxxx + r RPUSH lst2{t} "cc" + r RPUSH lst2{t} "dd" + r LMOVE lst2{t} lst{t} RIGHT LEFT + r LMOVE lst2{t} lst{t} LEFT RIGHT + assert_equal [r llen lst{t}] 2 + assert_equal [r llen lst2{t}] 2 + assert_equal [r lpop lst2{t}] "bb" + assert_equal [r lpop lst2{t}] "cc" + assert_equal [r lpop lst{t}] "dd" + assert_equal [r lpop lst{t}] "xxxxxxxxxxx" + config_set list-max-listpack-size $origin_config + } + + # testing LSET with combinations of node types + # plain->packed , packed->plain, plain->plain, packed->packed + test {Test LSET with packed / plain combinations} { + set origin_config [config_get_set list-max-listpack-size -2] + r RPUSH lst "aa" + r RPUSH lst "bb" + r lset lst 0 [string repeat d 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat d 50001] + r RPUSH lst [string repeat f 50001] + r lset lst 0 [string repeat e 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat e 50001] + r RPUSH lst [string repeat m 50001] + r lset lst 0 "bb" + set s1 [r lpop lst] + assert_equal $s1 "bb" + r RPUSH lst "bb" + r lset lst 0 "cc" + set s1 [r lpop lst] + assert_equal $s1 "cc" + config_set list-max-listpack-size $origin_config + } + + # checking LSET in case ziplist needs to be split + test {Test LSET with packed is split in the middle} { + r flushdb + set origin_config [config_get_set list-max-listpack-size -2] + r RPUSH lst "aa" + r RPUSH lst "bb" + r RPUSH lst "cc" + r RPUSH lst "dd" + r RPUSH lst "ee" + r lset lst 2 [string repeat e 50001] + assert_equal [r lpop lst] "aa" + assert_equal [r lpop lst] "bb" + assert_equal [r lpop lst] [string repeat e 50001] + assert_equal [r lpop lst] "dd" + assert_equal [r lpop lst] "ee" + config_set list-max-listpack-size $origin_config + } + + + # repeating "plain check LSET with combinations" + # but now with single item in each ziplist + test {Test LSET with packed consist only one item} { + r flushdb + set original_config [config_get_set list-max-ziplist-size 0] + r RPUSH lst "aa" + r RPUSH lst "bb" + r lset lst 0 [string repeat d 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat d 50001] + r RPUSH lst [string repeat f 50001] + r lset lst 0 [string repeat e 50001] + set s1 [r lpop lst] + assert_equal $s1 [string repeat e 50001] + r RPUSH lst [string repeat m 50001] + r lset lst 0 "bb" + set s1 [r lpop lst] + assert_equal $s1 "bb" + r RPUSH lst "bb" + r lset lst 0 "cc" + set s1 [r lpop lst] + assert_equal $s1 "cc" + r config set list-max-ziplist-size $original_config + } + + test {Crash due to delete entry from a compress quicklist node} { + r flushdb + set original_config_fill [config_get_set list-max-ziplist-size -2] + set original_config [config_get_set list-compress-depth 1] + + set small_ele [string repeat x 32] + set large_ele [string repeat x 50001] + + # Push a large element + r RPUSH lst $large_ele + + # Insert two elements and keep them in the same node + r RPUSH lst $small_ele + r RPUSH lst $small_ele + + # When setting the position of -1 to a large element, we first insert + # a large element at the end and then delete its previous element. + r LSET lst -1 $large_ele + assert_equal "$large_ele $small_ele $large_ele" [r LRANGE lst 0 -1] + + r config set list-max-ziplist-size $original_config_fill + r config set list-compress-depth $original_config + } + + test {Crash due to split quicklist node wrongly} { + r flushdb + set original_config [config_get_set list-max-ziplist-size -2] + + r LPUSH lst "aa" + r LPUSH lst "bb" + r LSET lst -2 [string repeat x 50001] + r RPOP lst + assert_equal [string repeat x 50001] [r LRANGE lst 0 -1] + + r config set list-max-ziplist-size $original_config + } +} + +run_solo {list-large-memory} { +start_server [list overrides [list save ""] ] { + +# test if the server supports such large configs (avoid 32 bit builds) +catch { + r config set proto-max-bulk-len 10000000000 ;#10gb + r config set client-query-buffer-limit 10000000000 ;#10gb +} +if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { + + set str_length 5000000000 + + # repeating all the plain nodes basic checks with 5gb values + test {Test LPUSH and LPOP on plain nodes over 4GB} { + r flushdb + r lpush lst 9 + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + set s0 [s used_memory] + assert {$s0 > $str_length} + assert {[r llen lst] == 3} + assert_equal [r rpop lst] "9" + assert_equal [read_big_bulk {r rpop lst}] $str_length + assert {[r llen lst] == 1} + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LINDEX and LINSERT on plain nodes over 4GB} { + r flushdb + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r lpush lst 9 + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r linsert lst before "9" "8" + assert_equal [r lindex lst 1] "8" + r LINSERT lst BEFORE "9" "7" + r write "*5\r\n\$7\r\nLINSERT\r\n\$3\r\nlst\r\n\$6\r\nBEFORE\r\n\$3\r\n\"9\"\r\n" + write_big_bulk 10; + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LTRIM on plain nodes over 4GB} { + r flushdb + r lpush lst 9 + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r lpush lst 9 + r LTRIM lst 1 -1 + assert_equal [r llen lst] 2 + assert_equal [r rpop lst] 9 + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LREM on plain nodes over 4GB} { + r flushdb + r lpush lst one + r write "*3\r\n\$5\r\nLPUSH\r\n\$3\r\nlst\r\n" + write_big_bulk $str_length; + r lpush lst 9 + r LREM lst -2 "one" + assert_equal [read_big_bulk {r rpop lst}] $str_length + r llen lst + } {1} {large-memory} + + test {Test LSET on plain nodes over 4GB} { + r flushdb + r RPUSH lst "aa" + r RPUSH lst "bb" + r RPUSH lst "cc" + r write "*4\r\n\$4\r\nLSET\r\n\$3\r\nlst\r\n\$1\r\n0\r\n" + write_big_bulk $str_length; + assert_equal [r rpop lst] "cc" + assert_equal [r rpop lst] "bb" + assert_equal [read_big_bulk {r rpop lst}] $str_length + } {} {large-memory} + + test {Test LMOVE on plain nodes over 4GB} { + r flushdb + r RPUSH lst2{t} "aa" + r RPUSH lst2{t} "bb" + r write "*4\r\n\$4\r\nLSET\r\n\$7\r\nlst2{t}\r\n\$1\r\n0\r\n" + write_big_bulk $str_length; + r RPUSH lst2{t} "cc" + r RPUSH lst2{t} "dd" + r LMOVE lst2{t} lst{t} RIGHT LEFT + assert_equal [read_big_bulk {r LMOVE lst2{t} lst{t} LEFT RIGHT}] $str_length + assert_equal [r llen lst{t}] 2 + assert_equal [r llen lst2{t}] 2 + assert_equal [r lpop lst2{t}] "bb" + assert_equal [r lpop lst2{t}] "cc" + assert_equal [r lpop lst{t}] "dd" + assert_equal [read_big_bulk {r rpop lst{t}}] $str_length + } {} {large-memory} + + # restore defaults + r config set proto-max-bulk-len 536870912 + r config set client-query-buffer-limit 1073741824 + +} ;# skip 32bit builds +} +} ;# run_solo + +start_server { + tags {"list"} + overrides { + "list-max-ziplist-size" -1 + } +} { + source "tests/unit/type/list-common.tcl" + + # A helper function to execute either B*POP or BLMPOP* with one input key. + proc bpop_command {rd pop key timeout} { + if {$pop == "BLMPOP_LEFT"} { + $rd blmpop $timeout 1 $key left count 1 + } elseif {$pop == "BLMPOP_RIGHT"} { + $rd blmpop $timeout 1 $key right count 1 + } else { + $rd $pop $key $timeout + } + } + + # A helper function to execute either B*POP or BLMPOP* with two input keys. + proc bpop_command_two_key {rd pop key key2 timeout} { + if {$pop == "BLMPOP_LEFT"} { + $rd blmpop $timeout 2 $key $key2 left count 1 + } elseif {$pop == "BLMPOP_RIGHT"} { + $rd blmpop $timeout 2 $key $key2 right count 1 + } else { + $rd $pop $key $key2 $timeout + } + } + + proc create_listpack {key entries} { + r del $key + foreach entry $entries { r rpush $key $entry } + assert_encoding listpack $key + } + + proc create_quicklist {key entries} { + r del $key + foreach entry $entries { r rpush $key $entry } + assert_encoding quicklist $key + } + +foreach {type large} [array get largevalue] { + test "LPOS basic usage - $type" { + r DEL mylist + r RPUSH mylist a b c $large 2 3 c c + assert {[r LPOS mylist a] == 0} + assert {[r LPOS mylist c] == 2} + } + + test {LPOS RANK (positive, negative and zero rank) option} { + assert {[r LPOS mylist c RANK 1] == 2} + assert {[r LPOS mylist c RANK 2] == 6} + assert {[r LPOS mylist c RANK 4] eq ""} + assert {[r LPOS mylist c RANK -1] == 7} + assert {[r LPOS mylist c RANK -2] == 6} + assert_error "*RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start*" {r LPOS mylist c RANK 0} + assert_error "*value is out of range*" {r LPOS mylist c RANK -9223372036854775808} + } + + test {LPOS COUNT option} { + assert {[r LPOS mylist c COUNT 0] == {2 6 7}} + assert {[r LPOS mylist c COUNT 1] == {2}} + assert {[r LPOS mylist c COUNT 2] == {2 6}} + assert {[r LPOS mylist c COUNT 100] == {2 6 7}} + } + + test {LPOS COUNT + RANK option} { + assert {[r LPOS mylist c COUNT 0 RANK 2] == {6 7}} + assert {[r LPOS mylist c COUNT 2 RANK -1] == {7 6}} + } + + test {LPOS non existing key} { + assert {[r LPOS mylistxxx c COUNT 0 RANK 2] eq {}} + } + + test {LPOS no match} { + assert {[r LPOS mylist x COUNT 2 RANK -1] eq {}} + assert {[r LPOS mylist x RANK -1] eq {}} + } + + test {LPOS MAXLEN} { + assert {[r LPOS mylist a COUNT 0 MAXLEN 1] == {0}} + assert {[r LPOS mylist c COUNT 0 MAXLEN 1] == {}} + assert {[r LPOS mylist c COUNT 0 MAXLEN 3] == {2}} + assert {[r LPOS mylist c COUNT 0 MAXLEN 3 RANK -1] == {7 6}} + assert {[r LPOS mylist c COUNT 0 MAXLEN 7 RANK 2] == {6}} + } + + test {LPOS when RANK is greater than matches} { + r DEL mylist + r LPUSH mylist a + assert {[r LPOS mylist b COUNT 10 RANK 5] eq {}} + } + + test "LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - $type" { + # first lpush then rpush + r del mylist1 + assert_equal 1 [r lpush mylist1 $large] + assert_encoding $type mylist1 + assert_equal 2 [r rpush mylist1 b] + assert_equal 3 [r rpush mylist1 c] + assert_equal 3 [r llen mylist1] + assert_equal $large [r lindex mylist1 0] + assert_equal b [r lindex mylist1 1] + assert_equal c [r lindex mylist1 2] + assert_equal {} [r lindex mylist1 3] + assert_equal c [r rpop mylist1] + assert_equal $large [r lpop mylist1] + + # first rpush then lpush + r del mylist2 + assert_equal 1 [r rpush mylist2 $large] + assert_equal 2 [r lpush mylist2 b] + assert_equal 3 [r lpush mylist2 c] + assert_encoding $type mylist2 + assert_equal 3 [r llen mylist2] + assert_equal c [r lindex mylist2 0] + assert_equal b [r lindex mylist2 1] + assert_equal $large [r lindex mylist2 2] + assert_equal {} [r lindex mylist2 3] + assert_equal $large [r rpop mylist2] + assert_equal c [r lpop mylist2] + } + + test "LPOP/RPOP with wrong number of arguments" { + assert_error {*wrong number of arguments for 'lpop' command} {r lpop key 1 1} + assert_error {*wrong number of arguments for 'rpop' command} {r rpop key 2 2} + } + + test "RPOP/LPOP with the optional count argument - $type" { + assert_equal 7 [r lpush listcount aa $large cc dd ee ff gg] + assert_equal {gg} [r lpop listcount 1] + assert_equal {ff ee} [r lpop listcount 2] + assert_equal "aa $large" [r rpop listcount 2] + assert_equal {cc} [r rpop listcount 1] + assert_equal {dd} [r rpop listcount 123] + assert_error "*ERR*range*" {r lpop forbarqaz -123} + } +} + + proc verify_resp_response {resp response resp2_response resp3_response} { + if {$resp == 2} { + assert_equal $response $resp2_response + } elseif {$resp == 3} { + assert_equal $response $resp3_response + } + } + + foreach resp {3 2} { + if {[lsearch $::denytags "resp3"] >= 0} { + if {$resp == 3} {continue} + } elseif {$::force_resp3} { + if {$resp == 2} {continue} + } + r hello $resp + + # Make sure we can distinguish between an empty array and a null response + r readraw 1 + + test "LPOP/RPOP with the count 0 returns an empty array in RESP$resp" { + r lpush listcount zero + assert_equal {*0} [r lpop listcount 0] + assert_equal {*0} [r rpop listcount 0] + } + + test "LPOP/RPOP against non existing key in RESP$resp" { + r del non_existing_key + + verify_resp_response $resp [r lpop non_existing_key] {$-1} {_} + verify_resp_response $resp [r rpop non_existing_key] {$-1} {_} + } + + test "LPOP/RPOP with against non existing key in RESP$resp" { + r del non_existing_key + + verify_resp_response $resp [r lpop non_existing_key 0] {*-1} {_} + verify_resp_response $resp [r lpop non_existing_key 1] {*-1} {_} + + verify_resp_response $resp [r rpop non_existing_key 0] {*-1} {_} + verify_resp_response $resp [r rpop non_existing_key 1] {*-1} {_} + } + + r readraw 0 + r hello 2 + } + + test {Variadic RPUSH/LPUSH} { + r del mylist + assert_equal 4 [r lpush mylist a b c d] + assert_equal 8 [r rpush mylist 0 1 2 3] + assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1] + } + + test {DEL a list} { + assert_equal 1 [r del mylist2] + assert_equal 0 [r exists mylist2] + assert_equal 0 [r llen mylist2] + } + + foreach {type large} [array get largevalue] { + foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop: single existing list - $type" { + set rd [redis_deferring_client] + create_$type blist "a b $large c d" + + bpop_command $rd $pop blist 1 + assert_equal {blist a} [$rd read] + if {$pop == "BLPOP"} { + bpop_command $rd BRPOP blist 1 + } else { + bpop_command $rd BLMPOP_RIGHT blist 1 + } + assert_equal {blist d} [$rd read] + + bpop_command $rd $pop blist 1 + assert_equal {blist b} [$rd read] + if {$pop == "BLPOP"} { + bpop_command $rd BRPOP blist 1 + } else { + bpop_command $rd BLMPOP_RIGHT blist 1 + } + assert_equal {blist c} [$rd read] + + assert_equal 1 [r llen blist] + $rd close + } + + test "$pop: multiple existing lists - $type" { + set rd [redis_deferring_client] + create_$type blist1{t} "a $large c" + create_$type blist2{t} "d $large f" + + bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 + assert_equal {blist1{t} a} [$rd read] + if {$pop == "BLPOP"} { + bpop_command_two_key $rd BRPOP blist1{t} blist2{t} 1 + } else { + bpop_command_two_key $rd BLMPOP_RIGHT blist1{t} blist2{t} 1 + } + assert_equal {blist1{t} c} [$rd read] + assert_equal 1 [r llen blist1{t}] + assert_equal 3 [r llen blist2{t}] + + bpop_command_two_key $rd $pop blist2{t} blist1{t} 1 + assert_equal {blist2{t} d} [$rd read] + if {$pop == "BLPOP"} { + bpop_command_two_key $rd BRPOP blist2{t} blist1{t} 1 + } else { + bpop_command_two_key $rd BLMPOP_RIGHT blist2{t} blist1{t} 1 + } + assert_equal {blist2{t} f} [$rd read] + assert_equal 1 [r llen blist1{t}] + assert_equal 1 [r llen blist2{t}] + $rd close + } + + test "$pop: second list has an entry - $type" { + set rd [redis_deferring_client] + r del blist1{t} + create_$type blist2{t} "d $large f" + + bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 + assert_equal {blist2{t} d} [$rd read] + if {$pop == "BLPOP"} { + bpop_command_two_key $rd BRPOP blist1{t} blist2{t} 1 + } else { + bpop_command_two_key $rd BLMPOP_RIGHT blist1{t} blist2{t} 1 + } + assert_equal {blist2{t} f} [$rd read] + assert_equal 0 [r llen blist1{t}] + assert_equal 1 [r llen blist2{t}] + $rd close + } + } + + test "BRPOPLPUSH - $type" { + r del target{t} + r rpush target{t} bar + + set rd [redis_deferring_client] + create_$type blist{t} "a b $large c d" + + $rd brpoplpush blist{t} target{t} 1 + assert_equal d [$rd read] + + assert_equal d [r lpop target{t}] + assert_equal "a b $large c" [r lrange blist{t} 0 -1] + $rd close + } + + foreach wherefrom {left right} { + foreach whereto {left right} { + test "BLMOVE $wherefrom $whereto - $type" { + r del target{t} + r rpush target{t} bar + + set rd [redis_deferring_client] + create_$type blist{t} "a b $large c d" + + $rd blmove blist{t} target{t} $wherefrom $whereto 1 + set poppedelement [$rd read] + + if {$wherefrom eq "right"} { + assert_equal d $poppedelement + assert_equal "a b $large c" [r lrange blist{t} 0 -1] + } else { + assert_equal a $poppedelement + assert_equal "b $large c d" [r lrange blist{t} 0 -1] + } + + if {$whereto eq "right"} { + assert_equal $poppedelement [r rpop target{t}] + } else { + assert_equal $poppedelement [r lpop target{t}] + } + $rd close + } + } + } + } + +foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop, LPUSH + DEL should not awake blocked client" { + set rd [redis_deferring_client] + r del list + + bpop_command $rd $pop list 0 + wait_for_blocked_client + + r multi + r lpush list a + r del list + r exec + r del list + r lpush list b + assert_equal {list b} [$rd read] + $rd close + } + + test "$pop, LPUSH + DEL + SET should not awake blocked client" { + set rd [redis_deferring_client] + r del list + + bpop_command $rd $pop list 0 + wait_for_blocked_client + + r multi + r lpush list a + r del list + r set list foo + r exec + r del list + r lpush list b + assert_equal {list b} [$rd read] + $rd close + } +} + + test "BLPOP with same key multiple times should work (issue #801)" { + set rd [redis_deferring_client] + r del list1{t} list2{t} + + # Data arriving after the BLPOP. + $rd blpop list1{t} list2{t} list2{t} list1{t} 0 + wait_for_blocked_client + r lpush list1{t} a + assert_equal [$rd read] {list1{t} a} + $rd blpop list1{t} list2{t} list2{t} list1{t} 0 + wait_for_blocked_client + r lpush list2{t} b + assert_equal [$rd read] {list2{t} b} + + # Data already there. + r lpush list1{t} a + r lpush list2{t} b + $rd blpop list1{t} list2{t} list2{t} list1{t} 0 + assert_equal [$rd read] {list1{t} a} + $rd blpop list1{t} list2{t} list2{t} list1{t} 0 + assert_equal [$rd read] {list2{t} b} + $rd close + } + +foreach {pop} {BLPOP BLMPOP_LEFT} { + test "MULTI/EXEC is isolated from the point of view of $pop" { + set rd [redis_deferring_client] + r del list + + bpop_command $rd $pop list 0 + wait_for_blocked_client + + r multi + r lpush list a + r lpush list b + r lpush list c + r exec + assert_equal {list c} [$rd read] + $rd close + } + + test "$pop with variadic LPUSH" { + set rd [redis_deferring_client] + r del blist + bpop_command $rd $pop blist 0 + wait_for_blocked_client + assert_equal 2 [r lpush blist foo bar] + assert_equal {blist bar} [$rd read] + assert_equal foo [lindex [r lrange blist 0 -1] 0] + $rd close + } +} + + test "BRPOPLPUSH with zero timeout should block indefinitely" { + set rd [redis_deferring_client] + r del blist{t} target{t} + r rpush target{t} bar + $rd brpoplpush blist{t} target{t} 0 + wait_for_blocked_clients_count 1 + r rpush blist{t} foo + assert_equal foo [$rd read] + assert_equal {foo bar} [r lrange target{t} 0 -1] + $rd close + } + + foreach wherefrom {left right} { + foreach whereto {left right} { + test "BLMOVE $wherefrom $whereto with zero timeout should block indefinitely" { + set rd [redis_deferring_client] + r del blist{t} target{t} + r rpush target{t} bar + $rd blmove blist{t} target{t} $wherefrom $whereto 0 + wait_for_blocked_clients_count 1 + r rpush blist{t} foo + assert_equal foo [$rd read] + if {$whereto eq "right"} { + assert_equal {bar foo} [r lrange target{t} 0 -1] + } else { + assert_equal {foo bar} [r lrange target{t} 0 -1] + } + $rd close + } + } + } + + foreach wherefrom {left right} { + foreach whereto {left right} { + test "BLMOVE ($wherefrom, $whereto) with a client BLPOPing the target list" { + set rd [redis_deferring_client] + set rd2 [redis_deferring_client] + r del blist{t} target{t} + $rd2 blpop target{t} 0 + wait_for_blocked_clients_count 1 + $rd blmove blist{t} target{t} $wherefrom $whereto 0 + wait_for_blocked_clients_count 2 + r rpush blist{t} foo + assert_equal foo [$rd read] + assert_equal {target{t} foo} [$rd2 read] + assert_equal 0 [r exists target{t}] + $rd close + $rd2 close + } + } + } + + test "BRPOPLPUSH with wrong source type" { + set rd [redis_deferring_client] + r del blist{t} target{t} + r set blist{t} nolist + $rd brpoplpush blist{t} target{t} 1 + assert_error "WRONGTYPE*" {$rd read} + $rd close + } + + test "BRPOPLPUSH with wrong destination type" { + set rd [redis_deferring_client] + r del blist{t} target{t} + r set target{t} nolist + r lpush blist{t} foo + $rd brpoplpush blist{t} target{t} 1 + assert_error "WRONGTYPE*" {$rd read} + $rd close + + set rd [redis_deferring_client] + r del blist{t} target{t} + r set target{t} nolist + $rd brpoplpush blist{t} target{t} 0 + wait_for_blocked_clients_count 1 + r rpush blist{t} foo + assert_error "WRONGTYPE*" {$rd read} + assert_equal {foo} [r lrange blist{t} 0 -1] + $rd close + } + + test "BRPOPLPUSH maintains order of elements after failure" { + set rd [redis_deferring_client] + r del blist{t} target{t} + r set target{t} nolist + $rd brpoplpush blist{t} target{t} 0 + wait_for_blocked_client + r rpush blist{t} a b c + assert_error "WRONGTYPE*" {$rd read} + $rd close + r lrange blist{t} 0 -1 + } {a b c} + + test "BRPOPLPUSH with multiple blocked clients" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + r del blist{t} target1{t} target2{t} + r set target1{t} nolist + $rd1 brpoplpush blist{t} target1{t} 0 + wait_for_blocked_clients_count 1 + $rd2 brpoplpush blist{t} target2{t} 0 + wait_for_blocked_clients_count 2 + r lpush blist{t} foo + + assert_error "WRONGTYPE*" {$rd1 read} + assert_equal {foo} [$rd2 read] + assert_equal {foo} [r lrange target2{t} 0 -1] + $rd1 close + $rd2 close + } + + test "BLMPOP with multiple blocked clients" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + set rd3 [redis_deferring_client] + set rd4 [redis_deferring_client] + r del blist{t} blist2{t} + + $rd1 blmpop 0 2 blist{t} blist2{t} left count 1 + wait_for_blocked_clients_count 1 + $rd2 blmpop 0 2 blist{t} blist2{t} right count 10 + wait_for_blocked_clients_count 2 + $rd3 blmpop 0 2 blist{t} blist2{t} left count 10 + wait_for_blocked_clients_count 3 + $rd4 blmpop 0 2 blist{t} blist2{t} right count 1 + wait_for_blocked_clients_count 4 + + r multi + r lpush blist{t} a b c d e + r lpush blist2{t} 1 2 3 4 5 + r exec + + assert_equal {blist{t} e} [$rd1 read] + assert_equal {blist{t} {a b c d}} [$rd2 read] + assert_equal {blist2{t} {5 4 3 2 1}} [$rd3 read] + + r lpush blist2{t} 1 2 3 + assert_equal {blist2{t} 1} [$rd4 read] + $rd1 close + $rd2 close + $rd3 close + $rd4 close + } + + test "Linked LMOVEs" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + r del list1{t} list2{t} list3{t} + + $rd1 blmove list1{t} list2{t} right left 0 + wait_for_blocked_clients_count 1 + $rd2 blmove list2{t} list3{t} left right 0 + wait_for_blocked_clients_count 2 + + r rpush list1{t} foo + + assert_equal {} [r lrange list1{t} 0 -1] + assert_equal {} [r lrange list2{t} 0 -1] + assert_equal {foo} [r lrange list3{t} 0 -1] + $rd1 close + $rd2 close + } + + test "Circular BRPOPLPUSH" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + r del list1{t} list2{t} + + $rd1 brpoplpush list1{t} list2{t} 0 + wait_for_blocked_clients_count 1 + $rd2 brpoplpush list2{t} list1{t} 0 + wait_for_blocked_clients_count 2 + + r rpush list1{t} foo + + assert_equal {foo} [r lrange list1{t} 0 -1] + assert_equal {} [r lrange list2{t} 0 -1] + $rd1 close + $rd2 close + } + + test "Self-referential BRPOPLPUSH" { + set rd [redis_deferring_client] + + r del blist{t} + + $rd brpoplpush blist{t} blist{t} 0 + wait_for_blocked_client + + r rpush blist{t} foo + + assert_equal {foo} [r lrange blist{t} 0 -1] + $rd close + } + + test "BRPOPLPUSH inside a transaction" { + r del xlist{t} target{t} + r lpush xlist{t} foo + r lpush xlist{t} bar + + r multi + r brpoplpush xlist{t} target{t} 0 + r brpoplpush xlist{t} target{t} 0 + r brpoplpush xlist{t} target{t} 0 + r lrange xlist{t} 0 -1 + r lrange target{t} 0 -1 + r exec + } {foo bar {} {} {bar foo}} + + test "PUSH resulting from BRPOPLPUSH affect WATCH" { + set blocked_client [redis_deferring_client] + set watching_client [redis_deferring_client] + r del srclist{t} dstlist{t} somekey{t} + r set somekey{t} somevalue + $blocked_client brpoplpush srclist{t} dstlist{t} 0 + wait_for_blocked_client + $watching_client watch dstlist{t} + $watching_client read + $watching_client multi + $watching_client read + $watching_client get somekey{t} + $watching_client read + r lpush srclist{t} element + $watching_client exec + set res [$watching_client read] + $blocked_client close + $watching_client close + set _ $res + } {} + + test "BRPOPLPUSH does not affect WATCH while still blocked" { + set blocked_client [redis_deferring_client] + set watching_client [redis_deferring_client] + r del srclist{t} dstlist{t} somekey{t} + r set somekey{t} somevalue + $blocked_client brpoplpush srclist{t} dstlist{t} 0 + wait_for_blocked_client + $watching_client watch dstlist{t} + $watching_client read + $watching_client multi + $watching_client read + $watching_client get somekey{t} + $watching_client read + $watching_client exec + # Blocked BLPOPLPUSH may create problems, unblock it. + r lpush srclist{t} element + set res [$watching_client read] + $blocked_client close + $watching_client close + set _ $res + } {somevalue} + + test {BRPOPLPUSH timeout} { + set rd [redis_deferring_client] + + $rd brpoplpush foo_list{t} bar_list{t} 1 + wait_for_blocked_clients_count 1 + wait_for_blocked_clients_count 0 500 10 + set res [$rd read] + $rd close + set _ $res + } {} + + test {SWAPDB awakes blocked client} { + r flushall + r select 1 + r rpush k hello + r select 9 + set rd [redis_deferring_client] + $rd brpop k 5 + wait_for_blocked_clients_count 1 + r swapdb 1 9 + $rd read + } {k hello} {singledb:skip} + + test {SWAPDB wants to wake blocked client, but the key already expired} { + set repl [attach_to_replication_stream] + r flushall + r debug set-active-expire 0 + r select 1 + r rpush k hello + r pexpire k 100 + set rd [redis_deferring_client] + $rd deferred 0 + $rd select 9 + set id [$rd client id] + $rd deferred 1 + $rd brpop k 1 + wait_for_blocked_clients_count 1 + after 101 + r swapdb 1 9 + # The SWAPDB command tries to awake the blocked client, but it remains + # blocked because the key is expired. Check that the deferred client is + # still blocked. Then unblock it. + assert_match "*flags=b*" [r client list id $id] + r client unblock $id + assert_equal {} [$rd read] + $rd deferred 0 + # We want to force key deletion to be propagated to the replica + # in order to verify it was expired on the replication stream. + $rd set somekey1 someval1 + $rd exists k + r set somekey2 someval2 + + assert_replication_stream $repl { + {select *} + {flushall} + {select 1} + {rpush k hello} + {pexpireat k *} + {swapdb 1 9} + {select 9} + {set somekey1 someval1} + {del k} + {select 1} + {set somekey2 someval2} + } + close_replication_stream $repl + r debug set-active-expire 1 + # Restore server and client state + r select 9 + } {OK} {singledb:skip needs:debug} + + test {MULTI + LPUSH + EXPIRE + DEBUG SLEEP on blocked client, key already expired} { + set repl [attach_to_replication_stream] + r flushall + r debug set-active-expire 0 + + set rd [redis_deferring_client] + $rd client id + set id [$rd read] + $rd brpop k 0 + wait_for_blocked_clients_count 1 + + r multi + r rpush k hello + r pexpire k 100 + r debug sleep 0.2 + r exec + + # The EXEC command tries to awake the blocked client, but it remains + # blocked because the key is expired. Check that the deferred client is + # still blocked. Then unblock it. + assert_match "*flags=b*" [r client list id $id] + r client unblock $id + assert_equal {} [$rd read] + # We want to force key deletion to be propagated to the replica + # in order to verify it was expired on the replication stream. + $rd exists k + assert_equal {0} [$rd read] + assert_replication_stream $repl { + {select *} + {flushall} + {multi} + {rpush k hello} + {pexpireat k *} + {exec} + {del k} + } + close_replication_stream $repl + # Restore server and client state + r debug set-active-expire 1 + r select 9 + } {OK} {singledb:skip needs:debug} + +foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop when new key is moved into place" { + set rd [redis_deferring_client] + r del foo{t} + + bpop_command $rd $pop foo{t} 0 + wait_for_blocked_client + r lpush bob{t} abc def hij + r rename bob{t} foo{t} + set res [$rd read] + $rd close + set _ $res + } {foo{t} hij} + + test "$pop when result key is created by SORT..STORE" { + set rd [redis_deferring_client] + + # zero out list from previous test without explicit delete + r lpop foo{t} + r lpop foo{t} + r lpop foo{t} + + bpop_command $rd $pop foo{t} 5 + wait_for_blocked_client + r lpush notfoo{t} hello hola aguacate konichiwa zanzibar + r sort notfoo{t} ALPHA store foo{t} + set res [$rd read] + $rd close + set _ $res + } {foo{t} aguacate} +} + + test "BLPOP: timeout value out of range" { + # Timeout is parsed as float and multiplied by 1000, added mstime() + # and stored in long-long which might lead to out-of-range value. + # (Even though given timeout is smaller than LLONG_MAX, the result + # will be bigger) + assert_error "ERR *is out of range*" {r BLPOP blist1 0x7FFFFFFFFFFFFF} + } + + foreach {pop} {BLPOP BRPOP BLMPOP_LEFT BLMPOP_RIGHT} { + test "$pop: with single empty list argument" { + set rd [redis_deferring_client] + r del blist1 + bpop_command $rd $pop blist1 1 + wait_for_blocked_client + r rpush blist1 foo + assert_equal {blist1 foo} [$rd read] + assert_equal 0 [r exists blist1] + $rd close + } + + test "$pop: with negative timeout" { + set rd [redis_deferring_client] + bpop_command $rd $pop blist1 -1 + assert_error "ERR *is negative*" {$rd read} + $rd close + } + + test "$pop: with non-integer timeout" { + set rd [redis_deferring_client] + r del blist1 + bpop_command $rd $pop blist1 0.1 + r rpush blist1 foo + assert_equal {blist1 foo} [$rd read] + assert_equal 0 [r exists blist1] + $rd close + } + + test "$pop: with zero timeout should block indefinitely" { + # To test this, use a timeout of 0 and wait a second. + # The blocking pop should still be waiting for a push. + set rd [redis_deferring_client] + bpop_command $rd $pop blist1 0 + wait_for_blocked_client + r rpush blist1 foo + assert_equal {blist1 foo} [$rd read] + $rd close + } + + test "$pop: with 0.001 timeout should not block indefinitely" { + # Use a timeout of 0.001 and wait for the number of blocked clients to equal 0. + # Validate the empty read from the deferring client. + set rd [redis_deferring_client] + bpop_command $rd $pop blist1 0.001 + wait_for_blocked_clients_count 0 + assert_equal {} [$rd read] + $rd close + } + + test "$pop: second argument is not a list" { + set rd [redis_deferring_client] + r del blist1{t} blist2{t} + r set blist2{t} nolist{t} + bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 + assert_error "WRONGTYPE*" {$rd read} + $rd close + } + + test "$pop: timeout" { + set rd [redis_deferring_client] + r del blist1{t} blist2{t} + bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 + wait_for_blocked_client + assert_equal {} [$rd read] + $rd close + } + + test "$pop: arguments are empty" { + set rd [redis_deferring_client] + r del blist1{t} blist2{t} + + bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 + wait_for_blocked_client + r rpush blist1{t} foo + assert_equal {blist1{t} foo} [$rd read] + assert_equal 0 [r exists blist1{t}] + assert_equal 0 [r exists blist2{t}] + + bpop_command_two_key $rd $pop blist1{t} blist2{t} 1 + wait_for_blocked_client + r rpush blist2{t} foo + assert_equal {blist2{t} foo} [$rd read] + assert_equal 0 [r exists blist1{t}] + assert_equal 0 [r exists blist2{t}] + $rd close + } + } + +foreach {pop} {BLPOP BLMPOP_LEFT} { + test "$pop inside a transaction" { + r del xlist + r lpush xlist foo + r lpush xlist bar + r multi + + bpop_command r $pop xlist 0 + bpop_command r $pop xlist 0 + bpop_command r $pop xlist 0 + r exec + } {{xlist bar} {xlist foo} {}} +} + + test {BLMPOP propagate as pop with count command to replica} { + set rd [redis_deferring_client] + set repl [attach_to_replication_stream] + + # BLMPOP without being blocked. + r lpush mylist{t} a b c + r rpush mylist2{t} 1 2 3 + r blmpop 0 1 mylist{t} left count 1 + r blmpop 0 2 mylist{t} mylist2{t} right count 10 + r blmpop 0 2 mylist{t} mylist2{t} right count 10 + + # BLMPOP that gets blocked. + $rd blmpop 0 1 mylist{t} left count 1 + wait_for_blocked_client + r lpush mylist{t} a + $rd blmpop 0 2 mylist{t} mylist2{t} left count 5 + wait_for_blocked_client + r lpush mylist{t} a b c + $rd blmpop 0 2 mylist{t} mylist2{t} right count 10 + wait_for_blocked_client + r rpush mylist2{t} a b c + + # Released on timeout. + assert_equal {} [r blmpop 0.01 1 mylist{t} left count 10] + r set foo{t} bar ;# something else to propagate after, so we can make sure the above pop didn't. + + $rd close + + assert_replication_stream $repl { + {select *} + {lpush mylist{t} a b c} + {rpush mylist2{t} 1 2 3} + {lpop mylist{t} 1} + {rpop mylist{t} 2} + {rpop mylist2{t} 3} + {lpush mylist{t} a} + {lpop mylist{t} 1} + {lpush mylist{t} a b c} + {lpop mylist{t} 3} + {rpush mylist2{t} a b c} + {rpop mylist2{t} 3} + {set foo{t} bar} + } + close_replication_stream $repl + } {} {needs:repl} + + test {LPUSHX, RPUSHX - generic} { + r del xlist + assert_equal 0 [r lpushx xlist a] + assert_equal 0 [r llen xlist] + assert_equal 0 [r rpushx xlist a] + assert_equal 0 [r llen xlist] + } + + foreach {type large} [array get largevalue] { + test "LPUSHX, RPUSHX - $type" { + create_$type xlist "$large c" + assert_equal 3 [r rpushx xlist d] + assert_equal 4 [r lpushx xlist a] + assert_equal 6 [r rpushx xlist 42 x] + assert_equal 9 [r lpushx xlist y3 y2 y1] + assert_equal "y1 y2 y3 a $large c d 42 x" [r lrange xlist 0 -1] + } + + test "LINSERT - $type" { + create_$type xlist "a $large c d" + assert_equal 5 [r linsert xlist before c zz] "before c" + assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA" + assert_equal 6 [r linsert xlist after c yy] "after c" + assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB" + assert_equal 7 [r linsert xlist after d dd] "after d" + assert_equal -1 [r linsert xlist after bad ddd] "after bad" + assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC" + assert_equal 8 [r linsert xlist before a aa] "before a" + assert_equal -1 [r linsert xlist before bad aaa] "before bad" + assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD" + + # check inserting integer encoded value + assert_equal 9 [r linsert xlist before aa 42] "before aa" + assert_equal 42 [r lrange xlist 0 0] "lrangeE" + } + } + + test {LINSERT raise error on bad syntax} { + catch {[r linsert xlist aft3r aa 42]} e + set e + } {*ERR*syntax*error*} + + test {LINSERT against non-list value error} { + r set k1 v1 + assert_error {WRONGTYPE Operation against a key holding the wrong kind of value*} {r linsert k1 after 0 0} + } + + test {LINSERT against non existing key} { + assert_equal 0 [r linsert not-a-key before 0 0] + } + +foreach type {listpack quicklist} { + foreach {num} {250 500} { + if {$type == "quicklist"} { + set origin_config [config_get_set list-max-listpack-size 5] + } else { + set origin_config [config_get_set list-max-listpack-size -1] + } + + proc check_numbered_list_consistency {key} { + set len [r llen $key] + for {set i 0} {$i < $len} {incr i} { + assert_equal $i [r lindex $key $i] + assert_equal [expr $len-1-$i] [r lindex $key [expr (-$i)-1]] + } + } + + proc check_random_access_consistency {key} { + set len [r llen $key] + for {set i 0} {$i < $len} {incr i} { + set rint [expr int(rand()*$len)] + assert_equal $rint [r lindex $key $rint] + assert_equal [expr $len-1-$rint] [r lindex $key [expr (-$rint)-1]] + } + } + + test "LINDEX consistency test - $type" { + r del mylist + for {set i 0} {$i < $num} {incr i} { + r rpush mylist $i + } + assert_encoding $type mylist + check_numbered_list_consistency mylist + } + + test "LINDEX random access - $type" { + assert_encoding $type mylist + check_random_access_consistency mylist + } + + test "Check if list is still ok after a DEBUG RELOAD - $type" { + r debug reload + assert_encoding $type mylist + check_numbered_list_consistency mylist + check_random_access_consistency mylist + } {} {needs:debug} + + config_set list-max-listpack-size $origin_config + } +} + + test {LLEN against non-list value error} { + r del mylist + r set mylist foobar + assert_error WRONGTYPE* {r llen mylist} + } + + test {LLEN against non existing key} { + assert_equal 0 [r llen not-a-key] + } + + test {LINDEX against non-list value error} { + assert_error WRONGTYPE* {r lindex mylist 0} + } + + test {LINDEX against non existing key} { + assert_equal "" [r lindex not-a-key 10] + } + + test {LPUSH against non-list value error} { + assert_error WRONGTYPE* {r lpush mylist 0} + } + + test {RPUSH against non-list value error} { + assert_error WRONGTYPE* {r rpush mylist 0} + } + + foreach {type large} [array get largevalue] { + test "RPOPLPUSH base case - $type" { + r del mylist1{t} mylist2{t} + create_$type mylist1{t} "a $large c d" + assert_equal d [r rpoplpush mylist1{t} mylist2{t}] + assert_equal c [r rpoplpush mylist1{t} mylist2{t}] + assert_equal $large [r rpoplpush mylist1{t} mylist2{t}] + assert_equal "a" [r lrange mylist1{t} 0 -1] + assert_equal "$large c d" [r lrange mylist2{t} 0 -1] + assert_encoding listpack mylist1{t} ;# converted to listpack after shrinking + assert_encoding $type mylist2{t} + } + + foreach wherefrom {left right} { + foreach whereto {left right} { + test "LMOVE $wherefrom $whereto base case - $type" { + r del mylist1{t} mylist2{t} + + if {$wherefrom eq "right"} { + create_$type mylist1{t} "c d $large a" + } else { + create_$type mylist1{t} "a $large c d" + } + assert_equal a [r lmove mylist1{t} mylist2{t} $wherefrom $whereto] + assert_equal $large [r lmove mylist1{t} mylist2{t} $wherefrom $whereto] + assert_equal "c d" [r lrange mylist1{t} 0 -1] + if {$whereto eq "right"} { + assert_equal "a $large" [r lrange mylist2{t} 0 -1] + } else { + assert_equal "$large a" [r lrange mylist2{t} 0 -1] + } + assert_encoding $type mylist2{t} + } + } + } + + test "RPOPLPUSH with the same list as src and dst - $type" { + create_$type mylist{t} "a $large c" + assert_equal "a $large c" [r lrange mylist{t} 0 -1] + assert_equal c [r rpoplpush mylist{t} mylist{t}] + assert_equal "c a $large" [r lrange mylist{t} 0 -1] + } + + foreach wherefrom {left right} { + foreach whereto {left right} { + test "LMOVE $wherefrom $whereto with the same list as src and dst - $type" { + if {$wherefrom eq "right"} { + create_$type mylist{t} "a $large c" + assert_equal "a $large c" [r lrange mylist{t} 0 -1] + } else { + create_$type mylist{t} "c a $large" + assert_equal "c a $large" [r lrange mylist{t} 0 -1] + } + assert_equal c [r lmove mylist{t} mylist{t} $wherefrom $whereto] + if {$whereto eq "right"} { + assert_equal "a $large c" [r lrange mylist{t} 0 -1] + } else { + assert_equal "c a $large" [r lrange mylist{t} 0 -1] + } + } + } + } + + foreach {othertype otherlarge} [array get largevalue] { + test "RPOPLPUSH with $type source and existing target $othertype" { + create_$type srclist{t} "a b c $large" + create_$othertype dstlist{t} "$otherlarge" + assert_equal $large [r rpoplpush srclist{t} dstlist{t}] + assert_equal c [r rpoplpush srclist{t} dstlist{t}] + assert_equal "a b" [r lrange srclist{t} 0 -1] + assert_equal "c $large $otherlarge" [r lrange dstlist{t} 0 -1] + + # When we rpoplpush'ed a large value, dstlist should be + # converted to the same encoding as srclist. + if {$type eq "quicklist"} { + assert_encoding quicklist dstlist{t} + } + } + + foreach wherefrom {left right} { + foreach whereto {left right} { + test "LMOVE $wherefrom $whereto with $type source and existing target $othertype" { + create_$othertype dstlist{t} "$otherlarge" + + if {$wherefrom eq "right"} { + create_$type srclist{t} "a b c $large" + } else { + create_$type srclist{t} "$large c a b" + } + assert_equal $large [r lmove srclist{t} dstlist{t} $wherefrom $whereto] + assert_equal c [r lmove srclist{t} dstlist{t} $wherefrom $whereto] + assert_equal "a b" [r lrange srclist{t} 0 -1] + + if {$whereto eq "right"} { + assert_equal "$otherlarge $large c" [r lrange dstlist{t} 0 -1] + } else { + assert_equal "c $large $otherlarge" [r lrange dstlist{t} 0 -1] + } + + # When we lmoved a large value, dstlist should be + # converted to the same encoding as srclist. + if {$type eq "quicklist"} { + assert_encoding quicklist dstlist{t} + } + } + } + } + } + } + + test {RPOPLPUSH against non existing key} { + r del srclist{t} dstlist{t} + assert_equal {} [r rpoplpush srclist{t} dstlist{t}] + assert_equal 0 [r exists srclist{t}] + assert_equal 0 [r exists dstlist{t}] + } + + test {RPOPLPUSH against non list src key} { + r del srclist{t} dstlist{t} + r set srclist{t} x + assert_error WRONGTYPE* {r rpoplpush srclist{t} dstlist{t}} + assert_type string srclist{t} + assert_equal 0 [r exists newlist{t}] + } + +foreach {type large} [array get largevalue] { + test "RPOPLPUSH against non list dst key - $type" { + create_$type srclist{t} "a $large c d" + r set dstlist{t} x + assert_error WRONGTYPE* {r rpoplpush srclist{t} dstlist{t}} + assert_type string dstlist{t} + assert_equal "a $large c d" [r lrange srclist{t} 0 -1] + } +} + + test {RPOPLPUSH against non existing src key} { + r del srclist{t} dstlist{t} + assert_equal {} [r rpoplpush srclist{t} dstlist{t}] + } {} + + foreach {type large} [array get largevalue] { + test "Basic LPOP/RPOP/LMPOP - $type" { + create_$type mylist "$large 1 2" + assert_equal $large [r lpop mylist] + assert_equal 2 [r rpop mylist] + assert_equal 1 [r lpop mylist] + assert_equal 0 [r llen mylist] + + create_$type mylist "$large 1 2" + assert_equal "mylist $large" [r lmpop 1 mylist left count 1] + assert_equal {mylist {2 1}} [r lmpop 2 mylist mylist right count 2] + } + } + + test {LPOP/RPOP/LMPOP against empty list} { + r del non-existing-list{t} non-existing-list2{t} + + assert_equal {} [r lpop non-existing-list{t}] + assert_equal {} [r rpop non-existing-list2{t}] + + assert_equal {} [r lmpop 1 non-existing-list{t} left count 1] + assert_equal {} [r lmpop 1 non-existing-list{t} left count 10] + assert_equal {} [r lmpop 2 non-existing-list{t} non-existing-list2{t} right count 1] + assert_equal {} [r lmpop 2 non-existing-list{t} non-existing-list2{t} right count 10] + } + + test {LPOP/RPOP/LMPOP NON-BLOCK or BLOCK against non list value} { + r set notalist{t} foo + assert_error WRONGTYPE* {r lpop notalist{t}} + assert_error WRONGTYPE* {r blpop notalist{t} 0} + assert_error WRONGTYPE* {r rpop notalist{t}} + assert_error WRONGTYPE* {r brpop notalist{t} 0} + + r del notalist2{t} + assert_error "WRONGTYPE*" {r lmpop 2 notalist{t} notalist2{t} left count 1} + assert_error "WRONGTYPE*" {r blmpop 0 2 notalist{t} notalist2{t} left count 1} + + r del notalist{t} + r set notalist2{t} nolist + assert_error "WRONGTYPE*" {r lmpop 2 notalist{t} notalist2{t} right count 10} + assert_error "WRONGTYPE*" {r blmpop 0 2 notalist{t} notalist2{t} left count 1} + } + + foreach {num} {250 500} { + test "Mass RPOP/LPOP - $type" { + r del mylist + set sum1 0 + for {set i 0} {$i < $num} {incr i} { + if {$i == [expr $num/2]} { + r lpush mylist $large + } + r lpush mylist $i + incr sum1 $i + } + assert_encoding $type mylist + set sum2 0 + for {set i 0} {$i < [expr $num/2]} {incr i} { + incr sum2 [r lpop mylist] + incr sum2 [r rpop mylist] + } + assert_equal $sum1 $sum2 + } + } + + test {LMPOP with illegal argument} { + assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop} + assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop 1} + assert_error "ERR wrong number of arguments for 'lmpop' command" {r lmpop 1 mylist{t}} + + assert_error "ERR numkeys*" {r lmpop 0 mylist{t} LEFT} + assert_error "ERR numkeys*" {r lmpop a mylist{t} LEFT} + assert_error "ERR numkeys*" {r lmpop -1 mylist{t} RIGHT} + + assert_error "ERR syntax error*" {r lmpop 1 mylist{t} bad_where} + assert_error "ERR syntax error*" {r lmpop 1 mylist{t} LEFT bar_arg} + assert_error "ERR syntax error*" {r lmpop 1 mylist{t} RIGHT LEFT} + assert_error "ERR syntax error*" {r lmpop 1 mylist{t} COUNT} + assert_error "ERR syntax error*" {r lmpop 1 mylist{t} LEFT COUNT 1 COUNT 2} + assert_error "ERR syntax error*" {r lmpop 2 mylist{t} mylist2{t} bad_arg} + + assert_error "ERR count*" {r lmpop 1 mylist{t} LEFT COUNT 0} + assert_error "ERR count*" {r lmpop 1 mylist{t} RIGHT COUNT a} + assert_error "ERR count*" {r lmpop 1 mylist{t} LEFT COUNT -1} + assert_error "ERR count*" {r lmpop 2 mylist{t} mylist2{t} RIGHT COUNT -1} + } + +foreach {type large} [array get largevalue] { + test "LMPOP single existing list - $type" { + # Same key multiple times. + create_$type mylist{t} "a b $large d e f" + assert_equal {mylist{t} {a b}} [r lmpop 2 mylist{t} mylist{t} left count 2] + assert_equal {mylist{t} {f e}} [r lmpop 2 mylist{t} mylist{t} right count 2] + assert_equal 2 [r llen mylist{t}] + + # First one exists, second one does not exist. + create_$type mylist{t} "a b $large d e" + r del mylist2{t} + assert_equal {mylist{t} a} [r lmpop 2 mylist{t} mylist2{t} left count 1] + assert_equal 4 [r llen mylist{t}] + assert_equal "mylist{t} {e d $large b}" [r lmpop 2 mylist{t} mylist2{t} right count 10] + assert_equal {} [r lmpop 2 mylist{t} mylist2{t} right count 1] + + # First one does not exist, second one exists. + r del mylist{t} + create_$type mylist2{t} "1 2 $large 4 5" + assert_equal {mylist2{t} 5} [r lmpop 2 mylist{t} mylist2{t} right count 1] + assert_equal 4 [r llen mylist2{t}] + assert_equal "mylist2{t} {1 2 $large 4}" [r lmpop 2 mylist{t} mylist2{t} left count 10] + + assert_equal 0 [r exists mylist{t} mylist2{t}] + } + + test "LMPOP multiple existing lists - $type" { + create_$type mylist{t} "a b $large d e" + create_$type mylist2{t} "1 2 $large 4 5" + + # Pop up from the first key. + assert_equal {mylist{t} {a b}} [r lmpop 2 mylist{t} mylist2{t} left count 2] + assert_equal 3 [r llen mylist{t}] + assert_equal "mylist{t} {e d $large}" [r lmpop 2 mylist{t} mylist2{t} right count 3] + assert_equal 0 [r exists mylist{t}] + + # Pop up from the second key. + assert_equal "mylist2{t} {1 2 $large}" [r lmpop 2 mylist{t} mylist2{t} left count 3] + assert_equal 2 [r llen mylist2{t}] + assert_equal {mylist2{t} {5 4}} [r lmpop 2 mylist{t} mylist2{t} right count 2] + assert_equal 0 [r exists mylist{t}] + + # Pop up all elements. + create_$type mylist{t} "a $large c" + create_$type mylist2{t} "1 $large 3" + assert_equal "mylist{t} {a $large c}" [r lmpop 2 mylist{t} mylist2{t} left count 10] + assert_equal 0 [r llen mylist{t}] + assert_equal "mylist2{t} {3 $large 1}" [r lmpop 2 mylist{t} mylist2{t} right count 10] + assert_equal 0 [r llen mylist2{t}] + assert_equal 0 [r exists mylist{t} mylist2{t}] + } +} + + test {LMPOP propagate as pop with count command to replica} { + set repl [attach_to_replication_stream] + + # left/right propagate as lpop/rpop with count + r lpush mylist{t} a b c + + # Pop elements from one list. + r lmpop 1 mylist{t} left count 1 + r lmpop 1 mylist{t} right count 1 + + # Now the list have only one element + r lmpop 2 mylist{t} mylist2{t} left count 10 + + # No elements so we don't propagate. + r lmpop 2 mylist{t} mylist2{t} left count 10 + + # Pop elements from the second list. + r rpush mylist2{t} 1 2 3 + r lmpop 2 mylist{t} mylist2{t} left count 2 + r lmpop 2 mylist{t} mylist2{t} right count 1 + + # Pop all elements. + r rpush mylist{t} a b c + r rpush mylist2{t} 1 2 3 + r lmpop 2 mylist{t} mylist2{t} left count 10 + r lmpop 2 mylist{t} mylist2{t} right count 10 + + assert_replication_stream $repl { + {select *} + {lpush mylist{t} a b c} + {lpop mylist{t} 1} + {rpop mylist{t} 1} + {lpop mylist{t} 1} + {rpush mylist2{t} 1 2 3} + {lpop mylist2{t} 2} + {rpop mylist2{t} 1} + {rpush mylist{t} a b c} + {rpush mylist2{t} 1 2 3} + {lpop mylist{t} 3} + {rpop mylist2{t} 3} + } + close_replication_stream $repl + } {} {needs:repl} + + foreach {type large} [array get largevalue] { + test "LRANGE basics - $type" { + create_$type mylist "$large 1 2 3 4 5 6 7 8 9" + assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2] + assert_equal {7 8 9} [r lrange mylist -3 -1] + assert_equal {4} [r lrange mylist 4 4] + } + + test "LRANGE inverted indexes - $type" { + create_$type mylist "$large 1 2 3 4 5 6 7 8 9" + assert_equal {} [r lrange mylist 6 2] + } + + test "LRANGE out of range indexes including the full list - $type" { + create_$type mylist "$large 1 2 3" + assert_equal "$large 1 2 3" [r lrange mylist -1000 1000] + } + + test "LRANGE out of range negative end index - $type" { + create_$type mylist "$large 1 2 3" + assert_equal $large [r lrange mylist 0 -4] + assert_equal {} [r lrange mylist 0 -5] + } + } + + test {LRANGE against non existing key} { + assert_equal {} [r lrange nosuchkey 0 1] + } + + test {LRANGE with start > end yields an empty array for backward compatibility} { + create_$type mylist "1 $large 3" + assert_equal {} [r lrange mylist 1 0] + assert_equal {} [r lrange mylist -1 -2] + } + + foreach {type large} [array get largevalue] { + proc trim_list {type min max} { + upvar 1 large large + r del mylist + create_$type mylist "1 2 3 4 $large" + r ltrim mylist $min $max + r lrange mylist 0 -1 + } + + test "LTRIM basics - $type" { + assert_equal "1" [trim_list $type 0 0] + assert_equal "1 2" [trim_list $type 0 1] + assert_equal "1 2 3" [trim_list $type 0 2] + assert_equal "2 3" [trim_list $type 1 2] + assert_equal "2 3 4 $large" [trim_list $type 1 -1] + assert_equal "2 3 4" [trim_list $type 1 -2] + assert_equal "4 $large" [trim_list $type -2 -1] + assert_equal "$large" [trim_list $type -1 -1] + assert_equal "1 2 3 4 $large" [trim_list $type -5 -1] + assert_equal "1 2 3 4 $large" [trim_list $type -10 10] + assert_equal "1 2 3 4 $large" [trim_list $type 0 5] + assert_equal "1 2 3 4 $large" [trim_list $type 0 10] + } + + test "LTRIM out of range negative end index - $type" { + assert_equal {1} [trim_list $type 0 -5] + assert_equal {} [trim_list $type 0 -6] + } + + test "LSET - $type" { + create_$type mylist "99 98 $large 96 95" + r lset mylist 1 foo + r lset mylist -1 bar + assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1] + } + + test "LSET out of range index - $type" { + assert_error ERR*range* {r lset mylist 10 foo} + } + } + + test {LSET against non existing key} { + assert_error ERR*key* {r lset nosuchkey 10 foo} + } + + test {LSET against non list value} { + r set nolist foobar + assert_error WRONGTYPE* {r lset nolist 0 foo} + } + + foreach {type e} [array get largevalue] { + test "LREM remove all the occurrences - $type" { + create_$type mylist "$e foo bar foobar foobared zap bar test foo" + assert_equal 2 [r lrem mylist 0 bar] + assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1] + } + + test "LREM remove the first occurrence - $type" { + assert_equal 1 [r lrem mylist 1 foo] + assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1] + } + + test "LREM remove non existing element - $type" { + assert_equal 0 [r lrem mylist 1 nosuchelement] + assert_equal "$e foobar foobared zap test foo" [r lrange mylist 0 -1] + } + + test "LREM starting from tail with negative count - $type" { + create_$type mylist "$e foo bar foobar foobared zap bar test foo foo" + assert_equal 1 [r lrem mylist -1 bar] + assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1] + } + + test "LREM starting from tail with negative count (2) - $type" { + assert_equal 2 [r lrem mylist -2 foo] + assert_equal "$e foo bar foobar foobared zap test" [r lrange mylist 0 -1] + } + + test "LREM deleting objects that may be int encoded - $type" { + create_$type myotherlist "$e 1 2 3" + assert_equal 1 [r lrem myotherlist 1 2] + assert_equal 3 [r llen myotherlist] + } + } + + test "Regression for bug 593 - chaining BRPOPLPUSH with other blocking cmds" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + $rd1 brpoplpush a{t} b{t} 0 + $rd1 brpoplpush a{t} b{t} 0 + wait_for_blocked_clients_count 1 + $rd2 brpoplpush b{t} c{t} 0 + wait_for_blocked_clients_count 2 + r lpush a{t} data + $rd1 close + $rd2 close + r ping + } {PONG} + + test "BLPOP/BLMOVE should increase dirty" { + r del lst{t} lst1{t} + set rd [redis_deferring_client] + + set dirty [s rdb_changes_since_last_save] + $rd blpop lst{t} 0 + wait_for_blocked_client + r lpush lst{t} a + assert_equal {lst{t} a} [$rd read] + set dirty2 [s rdb_changes_since_last_save] + assert {$dirty2 == $dirty + 2} + + set dirty [s rdb_changes_since_last_save] + $rd blmove lst{t} lst1{t} left left 0 + wait_for_blocked_client + r lpush lst{t} a + assert_equal {a} [$rd read] + set dirty2 [s rdb_changes_since_last_save] + assert {$dirty2 == $dirty + 2} + + $rd close + } + +foreach {pop} {BLPOP BLMPOP_RIGHT} { + test "client unblock tests" { + r del l + set rd [redis_deferring_client] + $rd client id + set id [$rd read] + + # test default args + bpop_command $rd $pop l 0 + wait_for_blocked_client + r client unblock $id + assert_equal {} [$rd read] + + # test with timeout + bpop_command $rd $pop l 0 + wait_for_blocked_client + r client unblock $id TIMEOUT + assert_equal {} [$rd read] + + # test with error + bpop_command $rd $pop l 0 + wait_for_blocked_client + r client unblock $id ERROR + catch {[$rd read]} e + assert_equal $e "UNBLOCKED client unblocked via CLIENT UNBLOCK" + + # test with invalid client id + catch {[r client unblock asd]} e + assert_equal $e "ERR value is not an integer or out of range" + + # test with non blocked client + set myid [r client id] + catch {[r client unblock $myid]} e + assert_equal $e {invalid command name "0"} + + # finally, see the this client and list are still functional + bpop_command $rd $pop l 0 + wait_for_blocked_client + r lpush l foo + assert_equal {l foo} [$rd read] + $rd close + } +} + + foreach {max_lp_size large} "3 $largevalue(listpack) -1 $largevalue(quicklist)" { + test "List listpack -> quicklist encoding conversion" { + set origin_conf [config_get_set list-max-listpack-size $max_lp_size] + + # RPUSH + create_listpack lst "a b c" + r RPUSH lst $large + assert_encoding quicklist lst + + # LINSERT + create_listpack lst "a b c" + r LINSERT lst after b $large + assert_encoding quicklist lst + + # LSET + create_listpack lst "a b c" + r LSET lst 0 $large + assert_encoding quicklist lst + + # LMOVE + create_quicklist lsrc{t} "a b c $large" + create_listpack ldes{t} "d e f" + r LMOVE lsrc{t} ldes{t} right right + assert_encoding quicklist ldes{t} + + r config set list-max-listpack-size $origin_conf + } + } + + test "List quicklist -> listpack encoding conversion" { + set origin_conf [config_get_set list-max-listpack-size 3] + + # RPOP + create_quicklist lst "a b c d" + r RPOP lst 3 + assert_encoding listpack lst + + # LREM + create_quicklist lst "a a a d" + r LREM lst 3 a + assert_encoding listpack lst + + # LTRIM + create_quicklist lst "a b c d" + r LTRIM lst 1 1 + assert_encoding listpack lst + + r config set list-max-listpack-size -1 + + # RPOP + create_quicklist lst "a b c $largevalue(quicklist)" + r RPOP lst 1 + assert_encoding listpack lst + + # LREM + create_quicklist lst "a $largevalue(quicklist)" + r LREM lst 1 $largevalue(quicklist) + assert_encoding listpack lst + + # LTRIM + create_quicklist lst "a b $largevalue(quicklist)" + r LTRIM lst 0 1 + assert_encoding listpack lst + + # LSET + create_quicklist lst "$largevalue(quicklist) a b" + r RPOP lst 2 + assert_encoding quicklist lst + r LSET lst -1 c + assert_encoding listpack lst + + r config set list-max-listpack-size $origin_conf + } + + test "List encoding conversion when RDB loading" { + set origin_conf [config_get_set list-max-listpack-size 3] + create_listpack lst "a b c" + + # list is still a listpack after DEBUG RELOAD + r DEBUG RELOAD + assert_encoding listpack lst + + # list is still a quicklist after DEBUG RELOAD + r RPUSH lst d + r DEBUG RELOAD + assert_encoding quicklist lst + + # when a quicklist has only one packed node, it will be + # converted to listpack during rdb loading + r RPOP lst + assert_encoding quicklist lst + r DEBUG RELOAD + assert_encoding listpack lst + + r config set list-max-listpack-size $origin_conf + } {OK} {needs:debug} + + test "List invalid list-max-listpack-size config" { + # ​When list-max-listpack-size is 0 we treat it as 1 and it'll + # still be listpack if there's a single element in the list. + r config set list-max-listpack-size 0 + r DEL lst + r RPUSH lst a + assert_encoding listpack lst + r RPUSH lst b + assert_encoding quicklist lst + + # When list-max-listpack-size < -5 we treat it as -5. + r config set list-max-listpack-size -6 + r DEL lst + r RPUSH lst [string repeat "x" 60000] + assert_encoding listpack lst + # Converted to quicklist when the size of listpack exceed 65536 + r RPUSH lst [string repeat "x" 5536] + assert_encoding quicklist lst + } + + test "List of various encodings" { + r del k + r lpush k 127 ;# ZIP_INT_8B + r lpush k 32767 ;# ZIP_INT_16B + r lpush k 2147483647 ;# ZIP_INT_32B + r lpush k 9223372036854775808 ;# ZIP_INT_64B + r lpush k 0 ;# ZIP_INT_IMM_MIN + r lpush k 12 ;# ZIP_INT_IMM_MAX + r lpush k [string repeat x 31] ;# ZIP_STR_06B + r lpush k [string repeat x 8191] ;# ZIP_STR_14B + r lpush k [string repeat x 65535] ;# ZIP_STR_32B + assert_encoding quicklist k ;# exceeds the size limit of quicklist node + set k [r lrange k 0 -1] + set dump [r dump k] + + # coverage for objectComputeSize + assert_morethan [memory_usage k] 0 + + config_set sanitize-dump-payload no mayfail + r restore kk 0 $dump replace + assert_encoding quicklist kk + set kk [r lrange kk 0 -1] + + # try some forward and backward searches to make sure all encodings + # can be traversed + assert_equal [r lindex kk 5] {9223372036854775808} + assert_equal [r lindex kk -5] {0} + assert_equal [r lpos kk foo rank 1] {} + assert_equal [r lpos kk foo rank -1] {} + + # make sure the values are right + assert_equal $k $kk + assert_equal [lpop k] [string repeat x 65535] + assert_equal [lpop k] [string repeat x 8191] + assert_equal [lpop k] [string repeat x 31] + set _ $k + } {12 0 9223372036854775808 2147483647 32767 127} + + test "List of various encodings - sanitize dump" { + config_set sanitize-dump-payload yes mayfail + r restore kk 0 $dump replace + assert_encoding quicklist kk + set k [r lrange k 0 -1] + set kk [r lrange kk 0 -1] + + # make sure the values are right + assert_equal $k $kk + assert_equal [lpop k] [string repeat x 65535] + assert_equal [lpop k] [string repeat x 8191] + assert_equal [lpop k] [string repeat x 31] + set _ $k + } {12 0 9223372036854775808 2147483647 32767 127} + + test "Unblock fairness is kept while pipelining" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + # delete the list in case already exists + r del mylist + + # block a client on the list + $rd1 BLPOP mylist 0 + wait_for_blocked_clients_count 1 + + # pipeline on other client a list push and a blocking pop + # we should expect the fairness to be kept and have $rd1 + # being unblocked + set buf "" + append buf "LPUSH mylist 1\r\n" + append buf "BLPOP mylist 0\r\n" + $rd2 write $buf + $rd2 flush + + # we check that we still have 1 blocked client + # and that the first blocked client has been served + assert_equal [$rd1 read] {mylist 1} + assert_equal [$rd2 read] {1} + wait_for_blocked_clients_count 1 + + # We no unblock the last client and verify it was served last + r LPUSH mylist 2 + wait_for_blocked_clients_count 0 + assert_equal [$rd2 read] {mylist 2} + + $rd1 close + $rd2 close + } + + test "Unblock fairness is kept during nested unblock" { + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + set rd3 [redis_deferring_client] + + # delete the list in case already exists + r del l1{t} l2{t} l3{t} + + # block a client on the list + $rd1 BRPOPLPUSH l1{t} l3{t} 0 + wait_for_blocked_clients_count 1 + + $rd2 BLPOP l2{t} 0 + wait_for_blocked_clients_count 2 + + $rd3 BLMPOP 0 2 l2{t} l3{t} LEFT COUNT 1 + wait_for_blocked_clients_count 3 + + r multi + r lpush l1{t} 1 + r lpush l2{t} 2 + r exec + + wait_for_blocked_clients_count 0 + + assert_equal [$rd1 read] {1} + assert_equal [$rd2 read] {l2{t} 2} + assert_equal [$rd3 read] {l3{t} 1} + + $rd1 close + $rd2 close + $rd3 close + } + + test "Blocking command accounted only once in commandstats" { + # cleanup first + r del mylist + + # create a test client + set rd [redis_deferring_client] + + # reset the server stats + r config resetstat + + # block a client on the list + $rd BLPOP mylist 0 + wait_for_blocked_clients_count 1 + + # unblock the list + r LPUSH mylist 1 + wait_for_blocked_clients_count 0 + + assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdrstat blpop r] + + $rd close + } + + test "Blocking command accounted only once in commandstats after timeout" { + # cleanup first + r del mylist + + # create a test client + set rd [redis_deferring_client] + $rd client id + set id [$rd read] + + # reset the server stats + r config resetstat + + # block a client on the list + $rd BLPOP mylist 0 + wait_for_blocked_clients_count 1 + + # unblock the client on timeout + r client unblock $id timeout + + assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdrstat blpop r] + + $rd close + } + + test {Command being unblocked cause another command to get unblocked execution order test} { + r del src{t} dst{t} key1{t} key2{t} key3{t} + set repl [attach_to_replication_stream] + + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + set rd3 [redis_deferring_client] + + $rd1 blmove src{t} dst{t} left right 0 + wait_for_blocked_clients_count 1 + + $rd2 blmove dst{t} src{t} right left 0 + wait_for_blocked_clients_count 2 + + # Create a pipeline of commands that will be processed in one socket read. + # Insert two set commands before and after lpush to observe the execution order. + set buf "" + append buf "set key1{t} value1\r\n" + append buf "lpush src{t} dummy\r\n" + append buf "set key2{t} value2\r\n" + $rd3 write $buf + $rd3 flush + + wait_for_blocked_clients_count 0 + + r set key3{t} value3 + + # If a command being unblocked causes another command to get unblocked, like a BLMOVE would do, + # then the new unblocked command will get processed right away rather than wait for later. + # If the set command occurs between two lmove commands, the results are not as expected. + assert_replication_stream $repl { + {select *} + {set key1{t} value1} + {lpush src{t} dummy} + {lmove src{t} dst{t} left right} + {lmove dst{t} src{t} right left} + {set key2{t} value2} + {set key3{t} value3} + } + + $rd1 close + $rd2 close + $rd3 close + + close_replication_stream $repl + } {} {needs:repl} + +} ;# stop servers