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