Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d8c021a9a | ||
|
|
be87e76906 | ||
|
|
09d9879963 | ||
|
|
dfc74051ce | ||
|
|
196fc32b77 | ||
|
|
b190b0c98f | ||
|
|
d7061f8137 | ||
|
|
20867e8009 | ||
|
|
10e987c5fd | ||
|
|
cc958ccf42 | ||
|
|
ec279203df | ||
|
|
480a2e73cf | ||
|
|
f447a7ebb4 | ||
|
|
748a2da3e8 | ||
|
|
b02e81be07 | ||
|
|
430719ca53 | ||
|
|
cc8a0f898b | ||
|
|
cd128d2882 | ||
|
|
c717adbc2e | ||
|
|
1ffa5d73ed | ||
|
|
a7fa2baf06 | ||
|
|
f7aef5241b | ||
|
|
c86a4f9102 | ||
|
|
2e638590ad | ||
|
|
3554f09ddc | ||
|
|
ccab83e729 | ||
|
|
97ddfbbfc3 | ||
|
|
50e50d6a25 | ||
|
|
9d665825d9 | ||
|
|
19b55f8ebc | ||
|
|
bf3692797d | ||
|
|
da06854477 | ||
|
|
38de9362b2 | ||
|
|
7600994937 |
@@ -12,6 +12,52 @@ for 2.0.
|
||||
CHANGELOG
|
||||
---------
|
||||
|
||||
What's new in Redis 2.2.8
|
||||
=========================
|
||||
|
||||
Redis 2.2.9 is an important bugfix release:
|
||||
|
||||
* A Slave used to rewrite the AOF log after a sync with the master, but the
|
||||
rewrite was performed at the wrong time, causing inconsistencies in the
|
||||
AOF file generated.
|
||||
* [LR]LPUSH replication in presence of BRPOPLPUSH was broken. Fixed.
|
||||
* Memory leak in BRPOPLPUSH fixed.
|
||||
* Pub/Sub bug resulting in random crashes fixed.
|
||||
|
||||
What's new in Redis 2.2.8
|
||||
=========================
|
||||
|
||||
* A new form of dict.c (hash table implementation) iterator that performs less
|
||||
copy-on-write of pages, introduced in Redis 2.2.7, caused ZINTERSTORE,
|
||||
ZUNIONSTORE, SINTER, SINTERSTORE commands to behave in the wrong way.
|
||||
This bug is now fixed.
|
||||
* Print version info before running the test with 'make test'. This is mainly
|
||||
useful for the Continuous Integration system we run.
|
||||
* Fix for DEBUG DIGEST, key may expire on lookup, producing the wrong result.
|
||||
* Replication with expire test modified to produce no or less false failures.
|
||||
* Fixed Z*STORE when dealing with intsets, regression test added.
|
||||
|
||||
What's new in Redis 2.2.7
|
||||
=========================
|
||||
|
||||
* Fixed bug #543-2 (the issue was reopened with a completely different report)
|
||||
that caused Redis to randomly crash on list push performed against lists
|
||||
with other clients blocked with BLPOP (or variants).
|
||||
|
||||
What's new in Redis 2.2.6
|
||||
=========================
|
||||
|
||||
* Fixed bug #543. If you saw Redis instances crashing on List operations
|
||||
(only happening with a non-default max entry size ziplist setting in
|
||||
redis.conf) it was almost certainly this problem.
|
||||
* Fixed a bug with replication where SLAVEOF NO ONE caused a slave to close the
|
||||
connection with all its slaves.
|
||||
|
||||
What's new in Redis 2.2.5
|
||||
=========================
|
||||
|
||||
* Fixed a crash occurring when loading an AOF containing the SPOP command.
|
||||
|
||||
What's new in Redis 2.2.4
|
||||
=========================
|
||||
|
||||
|
||||
@@ -285,9 +285,11 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* The fake client should not have a reply */
|
||||
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
|
||||
|
||||
/* Clean up, ready for the next command */
|
||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||
zfree(argv);
|
||||
/* Clean up. Command code may have changed argv/argc so we use the
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
for (j = 0; j < fakeClient->argc; j++)
|
||||
decrRefCount(fakeClient->argv[j]);
|
||||
zfree(fakeClient->argv);
|
||||
|
||||
/* Handle swapping while loading big datasets when VM is on */
|
||||
force_swapout = 0;
|
||||
|
||||
+6
-1
@@ -80,7 +80,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
redisDb *db = server.db+j;
|
||||
|
||||
if (dictSize(db->dict) == 0) continue;
|
||||
di = dictGetIterator(db->dict);
|
||||
di = dictGetSafeIterator(db->dict);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
@@ -101,6 +101,11 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
|
||||
/* Make sure the key is loaded if VM is active */
|
||||
o = lookupKeyRead(db,keyobj);
|
||||
if (o == NULL) {
|
||||
/* Key expired on lookup? Try the next one. */
|
||||
decrRefCount(keyobj);
|
||||
continue;
|
||||
}
|
||||
|
||||
aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
|
||||
+15
-5
@@ -244,9 +244,9 @@ int dictRehashMilliseconds(dict *d, int ms) {
|
||||
}
|
||||
|
||||
/* This function performs just a step of rehashing, and only if there are
|
||||
* not iterators bound to our hash table. When we have iterators in the middle
|
||||
* of a rehashing we can't mess with the two hash tables otherwise some element
|
||||
* can be missed or duplicated.
|
||||
* no safe iterators bound to our hash table. When we have iterators in the
|
||||
* middle of a rehashing we can't mess with the two hash tables otherwise
|
||||
* some element can be missed or duplicated.
|
||||
*
|
||||
* This function is called by common lookup or update operations in the
|
||||
* dictionary so that the hash table automatically migrates from H1 to H2
|
||||
@@ -423,17 +423,26 @@ dictIterator *dictGetIterator(dict *d)
|
||||
iter->d = d;
|
||||
iter->table = 0;
|
||||
iter->index = -1;
|
||||
iter->safe = 0;
|
||||
iter->entry = NULL;
|
||||
iter->nextEntry = NULL;
|
||||
return iter;
|
||||
}
|
||||
|
||||
dictIterator *dictGetSafeIterator(dict *d) {
|
||||
dictIterator *i = dictGetIterator(d);
|
||||
|
||||
i->safe = 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
dictEntry *dictNext(dictIterator *iter)
|
||||
{
|
||||
while (1) {
|
||||
if (iter->entry == NULL) {
|
||||
dictht *ht = &iter->d->ht[iter->table];
|
||||
if (iter->index == -1 && iter->table == 0) iter->d->iterators++;
|
||||
if (iter->safe && iter->index == -1 && iter->table == 0)
|
||||
iter->d->iterators++;
|
||||
iter->index++;
|
||||
if (iter->index >= (signed) ht->size) {
|
||||
if (dictIsRehashing(iter->d) && iter->table == 0) {
|
||||
@@ -460,7 +469,8 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
|
||||
void dictReleaseIterator(dictIterator *iter)
|
||||
{
|
||||
if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--;
|
||||
if (iter->safe && !(iter->index == -1 && iter->table == 0))
|
||||
iter->d->iterators--;
|
||||
zfree(iter);
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -74,10 +74,13 @@ typedef struct dict {
|
||||
int iterators; /* number of iterators currently running */
|
||||
} dict;
|
||||
|
||||
/* If safe is set to 1 this is a safe iteartor, that means, you can call
|
||||
* dictAdd, dictFind, and other functions against the dictionary even while
|
||||
* iterating. Otherwise it is a non safe iterator, and only dictNext()
|
||||
* should be called while iterating. */
|
||||
typedef struct dictIterator {
|
||||
dict *d;
|
||||
int table;
|
||||
int index;
|
||||
int table, index, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
} dictIterator;
|
||||
|
||||
@@ -132,6 +135,7 @@ dictEntry * dictFind(dict *d, const void *key);
|
||||
void *dictFetchValue(dict *d, const void *key);
|
||||
int dictResize(dict *d);
|
||||
dictIterator *dictGetIterator(dict *d);
|
||||
dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
|
||||
+37
-6
@@ -416,7 +416,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
@@ -431,7 +431,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
@@ -523,10 +523,16 @@ void freeClient(redisClient *c) {
|
||||
* close the connection with all our slaves if we have any, so
|
||||
* when we'll resync with the master the other slaves will sync again
|
||||
* with us as well. Note that also when the slave is not connected
|
||||
* to the master it will keep refusing connections by other slaves. */
|
||||
while (listLength(server.slaves)) {
|
||||
ln = listFirst(server.slaves);
|
||||
freeClient((redisClient*)ln->value);
|
||||
* to the master it will keep refusing connections by other slaves.
|
||||
*
|
||||
* We do this only if server.masterhost != NULL. If it is NULL this
|
||||
* means the user called SLAVEOF NO ONE and we are freeing our
|
||||
* link with the master, so no need to close link with slaves. */
|
||||
if (server.masterhost != NULL) {
|
||||
while (listLength(server.slaves)) {
|
||||
ln = listFirst(server.slaves);
|
||||
freeClient((redisClient*)ln->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Release memory */
|
||||
@@ -866,3 +872,28 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...) {
|
||||
va_list ap;
|
||||
int j;
|
||||
robj **argv; /* The new argument vector */
|
||||
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
va_start(ap,argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
robj *a;
|
||||
|
||||
a = va_arg(ap, robj*);
|
||||
argv[j] = a;
|
||||
incrRefCount(a);
|
||||
}
|
||||
/* We free the objects in the original vector at the end, so we are
|
||||
* sure that if the same objects are reused in the new vector the
|
||||
* refcount gets incremented before it gets decremented. */
|
||||
for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
/* Replace argv and argc with our new versions. */
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed from. */
|
||||
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
|
||||
dictIterator *di = dictGetIterator(c->pubsub_channels);
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
int count = 0;
|
||||
|
||||
|
||||
+2
-2
@@ -85,8 +85,8 @@ struct redisCommand readonlyCommandTable[] = {
|
||||
{"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"mget",mgetCommand,-2,0,NULL,1,-1,1},
|
||||
{"rpush",rpushCommand,-3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"lpush",lpushCommand,-3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"rpush",rpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"lpush",lpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"rpushx",rpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"lpushx",lpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
{"linsert",linsertCommand,5,REDIS_CMD_DENYOOM,NULL,1,1,1},
|
||||
|
||||
@@ -661,6 +661,7 @@ void addReplyMultiBulkLen(redisClient *c, long length);
|
||||
void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
|
||||
+2
-1
@@ -390,6 +390,8 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.master->authenticated = 1;
|
||||
server.replstate = REDIS_REPL_CONNECTED;
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
/* Rewrite the AOF file now that the dataset changed. */
|
||||
if (server.appendonly) rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +521,6 @@ void replicationCron(void) {
|
||||
redisLog(REDIS_NOTICE,"Connecting to MASTER...");
|
||||
if (syncWithMaster() == REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started: SYNC sent");
|
||||
if (server.appendonly) rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+57
-36
@@ -259,35 +259,30 @@ void listTypeConvert(robj *subject, int enc) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void pushGenericCommand(redisClient *c, int where) {
|
||||
int j, addlen = 0, pushed = 0;
|
||||
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
|
||||
int may_have_waiting_clients = (lobj == NULL);
|
||||
|
||||
if (lobj && lobj->type != REDIS_LIST) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
c->argv[j] = tryObjectEncoding(c->argv[j]);
|
||||
if (may_have_waiting_clients) {
|
||||
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) {
|
||||
addlen++;
|
||||
continue;
|
||||
} else {
|
||||
may_have_waiting_clients = 0;
|
||||
}
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
if (lobj == NULL) {
|
||||
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
|
||||
addReply(c,shared.cone);
|
||||
return;
|
||||
}
|
||||
if (!lobj) {
|
||||
lobj = createZiplistObject();
|
||||
dbAdd(c->db,c->argv[1],lobj);
|
||||
lobj = createZiplistObject();
|
||||
dbAdd(c->db,c->argv[1],lobj);
|
||||
} else {
|
||||
if (lobj->type != REDIS_LIST) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
return;
|
||||
}
|
||||
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
addReply(c,shared.cone);
|
||||
return;
|
||||
}
|
||||
listTypePush(lobj,c->argv[j],where);
|
||||
pushed++;
|
||||
}
|
||||
addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0));
|
||||
if (pushed) touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty += pushed;
|
||||
listTypePush(lobj,c->argv[2],where);
|
||||
addReplyLongLong(c,listTypeLength(lobj));
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
void lpushCommand(redisClient *c) {
|
||||
@@ -640,7 +635,9 @@ void lremCommand(redisClient *c) {
|
||||
* as well. This command was originally proposed by Ezra Zygmuntowicz.
|
||||
*/
|
||||
|
||||
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
|
||||
void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
|
||||
robj *aux;
|
||||
|
||||
if (!handleClientsWaitingListPush(c,dstkey,value)) {
|
||||
/* Create the list if the key does not exist */
|
||||
if (!dstobj) {
|
||||
@@ -648,9 +645,25 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
} else {
|
||||
touchWatchedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
}
|
||||
listTypePush(dstobj,value,REDIS_HEAD);
|
||||
/* If we are pushing as a result of LPUSH against a key
|
||||
* watched by BLPOPLPUSH, we need to rewrite the command vector.
|
||||
* But if this is called directly by RPOPLPUSH (either directly
|
||||
* or via a BRPOPLPUSH where the popped list exists)
|
||||
* we should replicate the BRPOPLPUSH command itself. */
|
||||
if (c != origclient) {
|
||||
aux = createStringObject("LPUSH",5);
|
||||
rewriteClientCommandVector(origclient,3,aux,dstkey,value);
|
||||
decrRefCount(aux);
|
||||
} else {
|
||||
/* Make sure to always use RPOPLPUSH in the replication / AOF,
|
||||
* even if the original command was BRPOPLPUSH. */
|
||||
aux = createStringObject("RPOPLPUSH",9);
|
||||
rewriteClientCommandVector(origclient,3,aux,c->argv[1],c->argv[2]);
|
||||
decrRefCount(aux);
|
||||
}
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
/* Always send the pushed value to the client. */
|
||||
@@ -666,16 +679,22 @@ void rpoplpushCommand(redisClient *c) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
|
||||
robj *touchedkey = c->argv[1];
|
||||
|
||||
if (dobj && checkType(c,dobj,REDIS_LIST)) return;
|
||||
value = listTypePop(sobj,REDIS_TAIL);
|
||||
rpoplpushHandlePush(c,c->argv[2],dobj,value);
|
||||
/* We saved touched key, and protect it, since rpoplpushHandlePush
|
||||
* may change the client command argument vector. */
|
||||
incrRefCount(touchedkey);
|
||||
rpoplpushHandlePush(c,c,c->argv[2],dobj,value);
|
||||
|
||||
/* listTypePop returns an object with its refcount incremented */
|
||||
decrRefCount(value);
|
||||
|
||||
/* Delete the source list when it is empty */
|
||||
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
|
||||
touchWatchedKey(c->db,c->argv[1]);
|
||||
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
|
||||
touchWatchedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -777,6 +796,7 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
/* Cleanup the client structure */
|
||||
zfree(c->bpop.keys);
|
||||
c->bpop.keys = NULL;
|
||||
if (c->bpop.target) decrRefCount(c->bpop.target);
|
||||
c->bpop.target = NULL;
|
||||
c->flags &= ~REDIS_BLOCKED;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
@@ -820,26 +840,27 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
|
||||
receiver = ln->value;
|
||||
dstkey = receiver->bpop.target;
|
||||
|
||||
/* Protect receiver->bpop.target, that will be freed by
|
||||
* the next unblockClientWaitingData() call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
/* This should remove the first element of the "clients" list. */
|
||||
unblockClientWaitingData(receiver);
|
||||
redisAssert(ln != listFirst(clients));
|
||||
|
||||
if (dstkey == NULL) {
|
||||
/* BRPOP/BLPOP */
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
addReplyBulk(receiver,key);
|
||||
addReplyBulk(receiver,ele);
|
||||
return 1;
|
||||
return 1; /* Serve just the first client as in B[RL]POP semantics */
|
||||
} else {
|
||||
/* BRPOPLPUSH, note that receiver->db is always equal to c->db. */
|
||||
dstobj = lookupKeyWrite(receiver->db,dstkey);
|
||||
if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) {
|
||||
decrRefCount(dstkey);
|
||||
} else {
|
||||
rpoplpushHandlePush(receiver,dstkey,dstobj,ele);
|
||||
if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) {
|
||||
rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele);
|
||||
decrRefCount(dstkey);
|
||||
return 1;
|
||||
}
|
||||
decrRefCount(dstkey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-11
@@ -325,7 +325,7 @@ void scardCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void spopCommand(redisClient *c) {
|
||||
robj *set, *ele;
|
||||
robj *set, *ele, *aux;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
@@ -341,16 +341,11 @@ void spopCommand(redisClient *c) {
|
||||
setTypeRemove(set,ele);
|
||||
}
|
||||
|
||||
/* Change argv to replicate as SREM */
|
||||
c->argc = 3;
|
||||
c->argv = zrealloc(c->argv,sizeof(robj*)*(c->argc));
|
||||
|
||||
/* Overwrite SREM with SPOP (same length) */
|
||||
redisAssert(sdslen(c->argv[0]->ptr) == 4);
|
||||
memcpy(c->argv[0]->ptr, "SREM", 4);
|
||||
|
||||
/* Popped element already has incremented refcount */
|
||||
c->argv[2] = ele;
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
aux = createStringObject("SREM",4);
|
||||
rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
|
||||
decrRefCount(ele);
|
||||
decrRefCount(aux);
|
||||
|
||||
addReplyBulk(c,ele);
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
@@ -433,6 +428,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
si = setTypeInitIterator(sets[0]);
|
||||
while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
|
||||
for (j = 1; j < setnum; j++) {
|
||||
if (sets[j] == sets[0]) continue;
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
/* intset with intset is simple... and fast */
|
||||
if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
|
||||
|
||||
+28
-4
@@ -615,6 +615,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
if (obj->type == REDIS_ZSET) {
|
||||
src[i].dict = ((zset*)obj->ptr)->dict;
|
||||
} else if (obj->type == REDIS_SET) {
|
||||
if (obj->encoding == REDIS_ENCODING_INTSET)
|
||||
setTypeConvert(obj, REDIS_ENCODING_HT);
|
||||
|
||||
redisAssert(obj->encoding == REDIS_ENCODING_HT);
|
||||
src[i].dict = (obj->ptr);
|
||||
} else {
|
||||
zfree(src);
|
||||
@@ -682,7 +686,18 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
|
||||
score = src[0].weight * zunionInterDictValue(de);
|
||||
for (j = 1; j < setnum; j++) {
|
||||
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
|
||||
dictEntry *other;
|
||||
|
||||
/* If it's the same dictionary don't lookup as we are not
|
||||
* in the context of a safe iterator. It's the same
|
||||
* dictionary so we are sure the element is inside.
|
||||
* This happens on SINTERSTORE dest 2 mykey mykey. */
|
||||
if (src[j].dict == src[0].dict) {
|
||||
other = de;
|
||||
} else {
|
||||
other = dictFind(src[j].dict,dictGetEntryKey(de));
|
||||
}
|
||||
|
||||
if (other) {
|
||||
value = src[j].weight * zunionInterDictValue(other);
|
||||
zunionInterAggregate(&score,value,aggregate);
|
||||
@@ -720,10 +735,19 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
/* because the zsets are sorted by size, its only possible
|
||||
* for sets at larger indices to hold this entry */
|
||||
for (j = (i+1); j < setnum; j++) {
|
||||
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
|
||||
if (other) {
|
||||
value = src[j].weight * zunionInterDictValue(other);
|
||||
/* It is not safe to access the zset we are
|
||||
* iterating, so explicitly check for equal object. */
|
||||
if (src[j].dict == src[i].dict) {
|
||||
value = src[i].weight * zunionInterDictValue(de);
|
||||
zunionInterAggregate(&score,value,aggregate);
|
||||
} else {
|
||||
dictEntry *other;
|
||||
|
||||
other = dictFind(src[j].dict,dictGetEntryKey(de));
|
||||
if (other) {
|
||||
value = src[j].weight * zunionInterDictValue(other);
|
||||
zunionInterAggregate(&score,value,aggregate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.2.4"
|
||||
#define REDIS_VERSION "2.2.9"
|
||||
|
||||
+34
-31
@@ -398,12 +398,17 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
|
||||
offset = p-zl;
|
||||
extra = rawlensize-next.prevrawlensize;
|
||||
zl = ziplistResize(zl,curlen+extra);
|
||||
ZIPLIST_TAIL_OFFSET(zl) += extra;
|
||||
p = zl+offset;
|
||||
|
||||
/* Move the tail to the back. */
|
||||
/* Current pointer and offset for next element. */
|
||||
np = p+rawlen;
|
||||
noffset = np-zl;
|
||||
|
||||
/* Update tail offset when next element is not the tail element. */
|
||||
if ((zl+ZIPLIST_TAIL_OFFSET(zl)) != np)
|
||||
ZIPLIST_TAIL_OFFSET(zl) += extra;
|
||||
|
||||
/* Move the tail to the back. */
|
||||
memmove(np+rawlensize,
|
||||
np+next.prevrawlensize,
|
||||
curlen-noffset-next.prevrawlensize-1);
|
||||
@@ -877,7 +882,7 @@ void pop(unsigned char *zl, int where) {
|
||||
}
|
||||
}
|
||||
|
||||
void randstring(char *target, unsigned int min, unsigned int max) {
|
||||
int randstring(char *target, unsigned int min, unsigned int max) {
|
||||
int p, len = min+rand()%(max-min+1);
|
||||
int minval, maxval;
|
||||
switch(rand() % 3) {
|
||||
@@ -899,10 +904,9 @@ void randstring(char *target, unsigned int min, unsigned int max) {
|
||||
|
||||
while(p < len)
|
||||
target[p++] = minval+rand()%(maxval-minval+1);
|
||||
return;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned char *zl, *p;
|
||||
unsigned char *entry;
|
||||
@@ -1235,6 +1239,7 @@ int main(int argc, char **argv) {
|
||||
int i,j,len,where;
|
||||
unsigned char *p;
|
||||
char buf[1024];
|
||||
int buflen;
|
||||
list *ref;
|
||||
listNode *refnode;
|
||||
|
||||
@@ -1243,10 +1248,6 @@ int main(int argc, char **argv) {
|
||||
unsigned int slen;
|
||||
long long sval;
|
||||
|
||||
/* In the regression for the cascade bug, it was triggered
|
||||
* with a random seed of 2. */
|
||||
srand(2);
|
||||
|
||||
for (i = 0; i < 20000; i++) {
|
||||
zl = ziplistNew();
|
||||
ref = listCreate();
|
||||
@@ -1256,31 +1257,32 @@ int main(int argc, char **argv) {
|
||||
/* Create lists */
|
||||
for (j = 0; j < len; j++) {
|
||||
where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
|
||||
switch(rand() % 4) {
|
||||
case 0:
|
||||
sprintf(buf,"%lld",(0LL + rand()) >> 20);
|
||||
break;
|
||||
case 1:
|
||||
sprintf(buf,"%lld",(0LL + rand()));
|
||||
break;
|
||||
case 2:
|
||||
sprintf(buf,"%lld",(0LL + rand()) << 20);
|
||||
break;
|
||||
case 3:
|
||||
randstring(buf,0,256);
|
||||
break;
|
||||
default:
|
||||
assert(NULL);
|
||||
if (rand() % 2) {
|
||||
buflen = randstring(buf,1,sizeof(buf)-1);
|
||||
} else {
|
||||
switch(rand() % 3) {
|
||||
case 0:
|
||||
buflen = sprintf(buf,"%lld",(0LL + rand()) >> 20);
|
||||
break;
|
||||
case 1:
|
||||
buflen = sprintf(buf,"%lld",(0LL + rand()));
|
||||
break;
|
||||
case 2:
|
||||
buflen = sprintf(buf,"%lld",(0LL + rand()) << 20);
|
||||
break;
|
||||
default:
|
||||
assert(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to ziplist */
|
||||
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), where);
|
||||
zl = ziplistPush(zl, (unsigned char*)buf, buflen, where);
|
||||
|
||||
/* Add to reference list */
|
||||
if (where == ZIPLIST_HEAD) {
|
||||
listAddNodeHead(ref,sdsnew(buf));
|
||||
listAddNodeHead(ref,sdsnewlen(buf, buflen));
|
||||
} else if (where == ZIPLIST_TAIL) {
|
||||
listAddNodeTail(ref,sdsnew(buf));
|
||||
listAddNodeTail(ref,sdsnewlen(buf, buflen));
|
||||
} else {
|
||||
assert(NULL);
|
||||
}
|
||||
@@ -1295,12 +1297,13 @@ int main(int argc, char **argv) {
|
||||
|
||||
assert(ziplistGet(p,&sstr,&slen,&sval));
|
||||
if (sstr == NULL) {
|
||||
sprintf(buf,"%lld",sval);
|
||||
buflen = sprintf(buf,"%lld",sval);
|
||||
} else {
|
||||
memcpy(buf,sstr,slen);
|
||||
buf[slen] = '\0';
|
||||
buflen = slen;
|
||||
memcpy(buf,sstr,buflen);
|
||||
buf[buflen] = '\0';
|
||||
}
|
||||
assert(strcmp(buf,listNodeValue(refnode)) == 0);
|
||||
assert(memcmp(buf,listNodeValue(refnode),buflen) == 0);
|
||||
}
|
||||
zfree(zl);
|
||||
listRelease(ref);
|
||||
|
||||
+47
-25
@@ -31,13 +31,14 @@ tags {"aof"} {
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Unfinished MULTI: Server should not have been started} {
|
||||
is_alive $srv
|
||||
} {0}
|
||||
test "Unfinished MULTI: Server should not have been started" {
|
||||
assert_equal 0 [is_alive $srv]
|
||||
}
|
||||
|
||||
test {Unfinished MULTI: Server should have logged an error} {
|
||||
exec cat [dict get $srv stdout] | tail -n1
|
||||
} {*Unexpected end of file reading the append only file*}
|
||||
test "Unfinished MULTI: Server should have logged an error" {
|
||||
set result [exec cat [dict get $srv stdout] | tail -n1]
|
||||
assert_match "*Unexpected end of file reading the append only file*" $result
|
||||
}
|
||||
}
|
||||
|
||||
## Test that the server exits when the AOF contains a short read
|
||||
@@ -47,36 +48,57 @@ tags {"aof"} {
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Short read: Server should not have been started} {
|
||||
is_alive $srv
|
||||
} {0}
|
||||
test "Short read: Server should not have been started" {
|
||||
assert_equal 0 [is_alive $srv]
|
||||
}
|
||||
|
||||
test {Short read: Server should have logged an error} {
|
||||
exec cat [dict get $srv stdout] | tail -n1
|
||||
} {*Bad file format reading the append only file*}
|
||||
test "Short read: Server should have logged an error" {
|
||||
set result [exec cat [dict get $srv stdout] | tail -n1]
|
||||
assert_match "*Bad file format reading the append only file*" $result
|
||||
}
|
||||
}
|
||||
|
||||
## Test that redis-check-aof indeed sees this AOF is not valid
|
||||
test {Short read: Utility should confirm the AOF is not valid} {
|
||||
test "Short read: Utility should confirm the AOF is not valid" {
|
||||
catch {
|
||||
exec src/redis-check-aof $aof_path
|
||||
} str
|
||||
set _ $str
|
||||
} {*not valid*}
|
||||
} result
|
||||
assert_match "*not valid*" $result
|
||||
}
|
||||
|
||||
test {Short read: Utility should be able to fix the AOF} {
|
||||
exec echo y | src/redis-check-aof --fix $aof_path
|
||||
} {*Successfully truncated AOF*}
|
||||
test "Short read: Utility should be able to fix the AOF" {
|
||||
set result [exec echo y | src/redis-check-aof --fix $aof_path]
|
||||
assert_match "*Successfully truncated AOF*" $result
|
||||
}
|
||||
|
||||
## Test that the server can be started using the truncated AOF
|
||||
start_server_aof [list dir $server_path] {
|
||||
test {Fixed AOF: Server should have been started} {
|
||||
is_alive $srv
|
||||
} {1}
|
||||
test "Fixed AOF: Server should have been started" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
test {Fixed AOF: Keyspace should contain values that were parsable} {
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
list [$client get foo] [$client get bar]
|
||||
} {hello {}}
|
||||
assert_equal "hello" [$client get foo]
|
||||
assert_equal "" [$client get bar]
|
||||
}
|
||||
}
|
||||
|
||||
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
|
||||
create_aof {
|
||||
append_to_aof [formatCommand sadd set foo]
|
||||
append_to_aof [formatCommand sadd set bar]
|
||||
append_to_aof [formatCommand spop set]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
test "AOF+SPOP: Server should have been started" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
test "AOF+SPOP: Set should have 1 member" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
assert_equal 1 [$client scard set]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,24 @@ start_server {tags {"repl"}} {
|
||||
s -1 role
|
||||
} {slave}
|
||||
|
||||
test {BRPOPLPUSH replication, when blocking against empty list} {
|
||||
set rd [redis_deferring_client]
|
||||
$rd brpoplpush a b 5
|
||||
r lpush a foo
|
||||
after 1000
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
}
|
||||
|
||||
test {BRPOPLPUSH replication, list exists} {
|
||||
set rd [redis_deferring_client]
|
||||
r lpush c 1
|
||||
r lpush c 2
|
||||
r lpush c 3
|
||||
$rd brpoplpush c d 5
|
||||
after 1000
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
}
|
||||
|
||||
test {MASTER and SLAVE dataset should be identical after complex ops} {
|
||||
createComplexDataset r 10000
|
||||
after 500
|
||||
@@ -27,6 +45,8 @@ start_server {tags {"repl"}} {
|
||||
test {MASTER and SLAVE consistency with expire} {
|
||||
createComplexDataset r 50000 useexpire
|
||||
after 4000 ;# Make sure everything expired before taking the digest
|
||||
r keys * ;# Force DEL syntesizing to slave
|
||||
after 1000 ;# Wait another second. Now everything should be fine.
|
||||
if {[r debug digest] ne [r -1 debug digest]} {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
|
||||
@@ -108,6 +108,7 @@ proc cleanup {} {
|
||||
}
|
||||
|
||||
proc execute_everything {} {
|
||||
execute_tests "unit/printver"
|
||||
execute_tests "unit/auth"
|
||||
execute_tests "unit/protocol"
|
||||
execute_tests "unit/basic"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
start_server {} {
|
||||
set i [r info]
|
||||
regexp {redis_version:(.*?)\r\n} $i - version
|
||||
regexp {redis_git_sha1:(.*?)\r\n} $i - sha1
|
||||
puts "Testing Redis version $version ($sha1)"
|
||||
}
|
||||
@@ -55,13 +55,6 @@ start_server {
|
||||
assert_equal $largevalue(linkedlist) [r lindex mylist2 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 - ziplist} {
|
||||
assert_equal 1 [r del myziplist2]
|
||||
assert_equal 0 [r exists myziplist2]
|
||||
@@ -149,15 +142,6 @@ start_server {
|
||||
}
|
||||
}
|
||||
|
||||
test "BLPOP with variadic LPUSH" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
$rd blpop blist 0
|
||||
assert_equal 2 [r lpush blist foo bar]
|
||||
assert_equal {blist foo} [$rd read]
|
||||
assert_equal bar [lindex [r lrange blist 0 -1] 0]
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with zero timeout should block indefinitely" {
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
@@ -278,6 +262,14 @@ start_server {
|
||||
r exec
|
||||
} {foo bar {} {} {bar foo}}
|
||||
|
||||
test {BRPOPLPUSH timeout} {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
$rd brpoplpush foo_list bar_list 1
|
||||
after 2000
|
||||
$rd read
|
||||
} {}
|
||||
|
||||
foreach {pop} {BLPOP BRPOP} {
|
||||
test "$pop: with single empty list argument" {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
@@ -484,6 +484,13 @@ start_server {tags {"zset"}} {
|
||||
test {ZINTERSTORE with AGGREGATE MAX} {
|
||||
list [r zinterstore zsetc 2 zseta zsetb aggregate max] [r zrange zsetc 0 -1 withscores]
|
||||
} {2 {b 2 c 3}}
|
||||
|
||||
test {ZINTERSTORE regression with two sets, intset+hashtable} {
|
||||
r del seta setb setc
|
||||
r sadd set1 a
|
||||
r sadd set2 10
|
||||
r zinterstore set3 2 set1 set2
|
||||
} {0}
|
||||
|
||||
foreach cmd {ZUNIONSTORE ZINTERSTORE} {
|
||||
test "$cmd with +inf/-inf scores" {
|
||||
|
||||
Reference in New Issue
Block a user