summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile18
-rw-r--r--src/anet.c10
-rw-r--r--src/anet.h1
-rw-r--r--src/aof.c8
-rw-r--r--src/endian.c63
-rw-r--r--src/endian.h20
-rw-r--r--src/intset.c5
-rw-r--r--src/intset.h1
-rw-r--r--src/networking.c89
-rw-r--r--src/object.c1
-rw-r--r--src/rdb.c150
-rw-r--r--src/redis-cli.c10
-rw-r--r--src/redis.c21
-rw-r--r--src/redis.h9
-rw-r--r--src/t_hash.c17
-rw-r--r--src/t_set.c32
-rw-r--r--src/util.c5
-rw-r--r--src/vm.c12
-rw-r--r--src/ziplist.c69
-rw-r--r--src/ziplist.h2
-rw-r--r--src/zipmap.c16
-rw-r--r--src/zipmap.h1
-rw-r--r--tests/integration/aof.tcl72
-rw-r--r--tests/unit/type/hash.tcl9
-rw-r--r--tests/unit/type/set.tcl8
25 files changed, 464 insertions, 185 deletions
diff --git a/src/Makefile b/src/Makefile
index 48422f3b1..40ee7e6ff 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -65,7 +65,6 @@ ae_select.o: ae_select.c
anet.o: anet.c fmacros.h anet.h
aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
-chprgname.o: chprgname.c
config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
@@ -73,6 +72,7 @@ db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
debug.o: debug.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h sha1.h
dict.o: dict.c fmacros.h dict.h zmalloc.h
+endian.o: endian.c
intset.o: intset.c intset.h zmalloc.h
lzf_c.o: lzf_c.c lzfP.h
lzf_d.o: lzf_d.c lzfP.h
@@ -87,11 +87,12 @@ pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
-redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
- zmalloc.h
+redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
+ ../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h
-redis-cli.o: redis-cli.c fmacros.h version.h sds.h adlist.h zmalloc.h
+redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
+ sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h
redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
release.o: release.c release.h
@@ -101,7 +102,8 @@ sds.o: sds.c sds.h zmalloc.h
sha1.o: sha1.c sha1.h
sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h
-syncio.o: syncio.c
+syncio.o: syncio.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
t_hash.o: t_hash.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
t_list.o: t_list.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
@@ -116,8 +118,8 @@ util.o: util.c util.h
vm.o: vm.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
ziplist.o: ziplist.c zmalloc.h ziplist.h
-zipmap.o: zipmap.c zmalloc.h
-zmalloc.o: zmalloc.c config.h
+zipmap.o: zipmap.c zmalloc.h endian.h
+zmalloc.o: zmalloc.c config.h zmalloc.h
dependencies:
cd ../deps/hiredis && $(MAKE) static ARCH="$(ARCH)"
@@ -152,7 +154,7 @@ clean:
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov
dep:
- $(CC) -MM *.c
+ $(CC) -I../deps/hiredis -I../deps/linenoise -MM *.c
test: redis-server
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
diff --git a/src/anet.c b/src/anet.c
index 4e16f2e4c..692cef194 100644
--- a/src/anet.c
+++ b/src/anet.c
@@ -345,3 +345,13 @@ int anetUnixAccept(char *err, int s) {
return fd;
}
+
+int anetPeerToString(int fd, char *ip, int *port) {
+ struct sockaddr_in sa;
+ socklen_t salen = sizeof(sa);
+
+ if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) return -1;
+ if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
+ if (port) *port = ntohs(sa.sin_port);
+ return 0;
+}
diff --git a/src/anet.h b/src/anet.h
index 118b4ddac..2b2dea456 100644
--- a/src/anet.h
+++ b/src/anet.h
@@ -53,5 +53,6 @@ int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd);
int anetTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
+int anetPeerToString(int fd, char *ip, int *port);
#endif
diff --git a/src/aof.c b/src/aof.c
index 766f89d00..9b571b95a 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -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;
diff --git a/src/endian.c b/src/endian.c
new file mode 100644
index 000000000..aff2425a6
--- /dev/null
+++ b/src/endian.c
@@ -0,0 +1,63 @@
+/* Toggle the 16 bit unsigned integer pointed by *p from little endian to
+ * big endian */
+void memrev16(void *p) {
+ unsigned char *x = p, t;
+
+ t = x[0];
+ x[0] = x[1];
+ x[1] = t;
+}
+
+/* Toggle the 32 bit unsigned integer pointed by *p from little endian to
+ * big endian */
+void memrev32(void *p) {
+ unsigned char *x = p, t;
+
+ t = x[0];
+ x[0] = x[3];
+ x[3] = t;
+ t = x[1];
+ x[1] = x[2];
+ x[2] = t;
+}
+
+/* Toggle the 64 bit unsigned integer pointed by *p from little endian to
+ * big endian */
+void memrev64(void *p) {
+ unsigned char *x = p, t;
+
+ t = x[0];
+ x[0] = x[7];
+ x[7] = t;
+ t = x[1];
+ x[1] = x[6];
+ x[6] = t;
+ t = x[2];
+ x[2] = x[5];
+ x[5] = t;
+ t = x[3];
+ x[3] = x[4];
+ x[4] = t;
+}
+
+#ifdef TESTMAIN
+#include <stdio.h>
+
+int main(void) {
+ char buf[32];
+
+ sprintf(buf,"ciaoroma");
+ memrev16(buf);
+ printf("%s\n", buf);
+
+ sprintf(buf,"ciaoroma");
+ memrev32(buf);
+ printf("%s\n", buf);
+
+ sprintf(buf,"ciaoroma");
+ memrev64(buf);
+ printf("%s\n", buf);
+
+ return 0;
+}
+#endif
diff --git a/src/endian.h b/src/endian.h
new file mode 100644
index 000000000..bef822727
--- /dev/null
+++ b/src/endian.h
@@ -0,0 +1,20 @@
+#ifndef __ENDIAN_H
+#define __ENDIAN_H
+
+void memrev16(void *p);
+void memrev32(void *p);
+void memrev64(void *p);
+
+/* variants of the function doing the actual convertion only if the target
+ * host is big endian */
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define memrev16ifbe(p)
+#define memrev32ifbe(p)
+#define memrev64ifbe(p)
+#else
+#define memrev16ifbe(p) memrev16(p)
+#define memrev32ifbe(p) memrev32(p)
+#define memrev64ifbe(p) memrev64(p)
+#endif
+
+#endif
diff --git a/src/intset.c b/src/intset.c
index bfd3307d2..13bd220e7 100644
--- a/src/intset.c
+++ b/src/intset.c
@@ -222,6 +222,11 @@ uint32_t intsetLen(intset *is) {
return is->length;
}
+/* Return intset blob size in bytes. */
+size_t intsetBlobLen(intset *is) {
+ return sizeof(intset)+is->length*is->encoding;
+}
+
#ifdef INTSET_TEST_MAIN
#include <sys/time.h>
diff --git a/src/intset.h b/src/intset.h
index 10d49d2e0..ee4b91fa9 100644
--- a/src/intset.h
+++ b/src/intset.h
@@ -15,5 +15,6 @@ uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(intset *is);
+size_t intsetBlobLen(intset *is);
#endif // __INTSET_H
diff --git a/src/networking.c b/src/networking.c
index 7a57031cc..258d63fda 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -16,7 +16,6 @@ redisClient *createClient(int fd) {
anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd);
- if (!c) return NULL;
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
@@ -322,7 +321,12 @@ void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
}
void addReplyLongLong(redisClient *c, long long ll) {
- _addReplyLongLong(c,ll,':');
+ if (ll == 0)
+ addReply(c,shared.czero);
+ else if (ll == 1)
+ addReply(c,shared.cone);
+ else
+ _addReplyLongLong(c,ll,':');
}
void addReplyMultiBulkLen(redisClient *c, long length) {
@@ -523,10 +527,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 */
@@ -870,3 +880,70 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
*biggest_input_buffer = bib;
}
+void clientCommand(redisClient *c) {
+ listNode *ln;
+ listIter li;
+ redisClient *client;
+
+ if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) {
+ sds o = sdsempty();
+ time_t now = time(NULL);
+
+ listRewind(server.clients,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ char ip[32], flags[16], *p;
+ int port;
+
+ client = listNodeValue(ln);
+ if (anetPeerToString(client->fd,ip,&port) == -1) continue;
+ p = flags;
+ if (client->flags & REDIS_SLAVE) {
+ if (client->flags & REDIS_MONITOR)
+ *p++ = 'O';
+ else
+ *p++ = 'S';
+ }
+ if (client->flags & REDIS_MASTER) *p++ = 'M';
+ if (p == flags) *p++ = 'N';
+ if (client->flags & REDIS_MULTI) *p++ = 'x';
+ if (client->flags & REDIS_BLOCKED) *p++ = 'b';
+ if (client->flags & REDIS_IO_WAIT) *p++ = 'i';
+ if (client->flags & REDIS_DIRTY_CAS) *p++ = 'd';
+ if (client->flags & REDIS_CLOSE_AFTER_REPLY) *p++ = 'c';
+ if (client->flags & REDIS_UNBLOCKED) *p++ = 'u';
+ *p++ = '\0';
+ o = sdscatprintf(o,
+ "addr=%s:%d fd=%d idle=%ld flags=%s db=%d sub=%d psub=%d\n",
+ ip,port,client->fd,
+ (long)(now - client->lastinteraction),
+ flags,
+ client->db->id,
+ (int) dictSize(client->pubsub_channels),
+ (int) listLength(client->pubsub_patterns));
+ }
+ addReplyBulkCBuffer(c,o,sdslen(o));
+ sdsfree(o);
+ } else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) {
+ listRewind(server.clients,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ char ip[32], addr[64];
+ int port;
+
+ client = listNodeValue(ln);
+ if (anetPeerToString(client->fd,ip,&port) == -1) continue;
+ snprintf(addr,sizeof(addr),"%s:%d",ip,port);
+ if (strcmp(addr,c->argv[2]->ptr) == 0) {
+ addReply(c,shared.ok);
+ if (c == client) {
+ client->flags |= REDIS_CLOSE_AFTER_REPLY;
+ } else {
+ freeClient(client);
+ }
+ return;
+ }
+ }
+ addReplyError(c,"No such client");
+ } else {
+ addReplyError(c, "Syntax error, try CLIENT (LIST | KILL ip:port)");
+ }
+}
diff --git a/src/object.c b/src/object.c
index 2f99e1414..e521e5dab 100644
--- a/src/object.c
+++ b/src/object.c
@@ -475,6 +475,7 @@ unsigned long estimateObjectIdleTime(robj *o) {
robj *objectCommandLookup(redisClient *c, robj *key) {
dictEntry *de;
+ if (server.vm_enabled) lookupKeyRead(c->db,key);
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
return (robj*) dictGetEntryVal(de);
}
diff --git a/src/rdb.c b/src/rdb.c
index a9e4b1a0d..2ddbafa47 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -139,7 +139,7 @@ writeerr:
}
/* Save a string objet as [len][data] on disk. If the object is a string
- * representation of an integer value we try to safe it in a special form */
+ * representation of an integer value we try to save it in a special form */
int rdbSaveRawString(FILE *fp, unsigned char *s, size_t len) {
int enclen;
int n, nwritten = 0;
@@ -256,27 +256,10 @@ int rdbSaveObject(FILE *fp, robj *o) {
} else if (o->type == REDIS_LIST) {
/* Save a list value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
- unsigned char *p;
- unsigned char *vstr;
- unsigned int vlen;
- long long vlong;
+ size_t l = ziplistBlobLen((unsigned char*)o->ptr);
- if ((n = rdbSaveLen(fp,ziplistLen(o->ptr))) == -1) return -1;
+ if ((n = rdbSaveRawString(fp,o->ptr,l)) == -1) return -1;
nwritten += n;
-
- p = ziplistIndex(o->ptr,0);
- while(ziplistGet(p,&vstr,&vlen,&vlong)) {
- if (vstr) {
- if ((n = rdbSaveRawString(fp,vstr,vlen)) == -1)
- return -1;
- nwritten += n;
- } else {
- if ((n = rdbSaveLongLongAsStringObject(fp,vlong)) == -1)
- return -1;
- nwritten += n;
- }
- p = ziplistNext(o->ptr,p);
- }
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listIter li;
@@ -311,56 +294,20 @@ int rdbSaveObject(FILE *fp, robj *o) {
}
dictReleaseIterator(di);
} else if (o->encoding == REDIS_ENCODING_INTSET) {
- intset *is = o->ptr;
- int64_t llval;
- int i = 0;
+ size_t l = intsetBlobLen((intset*)o->ptr);
- if ((n = rdbSaveLen(fp,intsetLen(is))) == -1) return -1;
+ if ((n = rdbSaveRawString(fp,o->ptr,l)) == -1) return -1;
nwritten += n;
-
- while(intsetGet(is,i++,&llval)) {
- if ((n = rdbSaveLongLongAsStringObject(fp,llval)) == -1) return -1;
- nwritten += n;
- }
} else {
redisPanic("Unknown set encoding");
}
} else if (o->type == REDIS_ZSET) {
/* Save a sorted set value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
- unsigned char *zl = o->ptr;
- unsigned char *eptr, *sptr;
- unsigned char *vstr;
- unsigned int vlen;
- long long vlong;
- double score;
+ size_t l = ziplistBlobLen((unsigned char*)o->ptr);
- if ((n = rdbSaveLen(fp,zsetLength(o))) == -1) return -1;
+ if ((n = rdbSaveRawString(fp,o->ptr,l)) == -1) return -1;
nwritten += n;
-
- eptr = ziplistIndex(zl,0);
- redisAssert(eptr != NULL);
- sptr = ziplistNext(zl,eptr);
- redisAssert(sptr != NULL);
-
- while (eptr != NULL) {
- redisAssert(ziplistGet(eptr,&vstr,&vlen,&vlong));
- if (vstr) {
- if ((n = rdbSaveRawString(fp,vstr,vlen)) == -1)
- return -1;
- nwritten += n;
- } else {
- if ((n = rdbSaveLongLongAsStringObject(fp,vlong)) == -1)
- return -1;
- nwritten += n;
- }
-
- score = zzlGetScore(sptr);
- if ((n = rdbSaveDoubleValue(fp,score)) == -1) return -1;
- nwritten += n;
-
- zzlNext(zl,&eptr,&sptr);
- }
} else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict);
@@ -385,20 +332,10 @@ int rdbSaveObject(FILE *fp, robj *o) {
} else if (o->type == REDIS_HASH) {
/* Save a hash value */
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
- unsigned char *p = zipmapRewind(o->ptr);
- unsigned int count = zipmapLen(o->ptr);
- unsigned char *key, *val;
- unsigned int klen, vlen;
+ size_t l = zipmapBlobLen((unsigned char*)o->ptr);
- if ((n = rdbSaveLen(fp,count)) == -1) return -1;
+ if ((n = rdbSaveRawString(fp,o->ptr,l)) == -1) return -1;
nwritten += n;
-
- while((p = zipmapNext(p,&key,&klen,&val,&vlen)) != NULL) {
- if ((n = rdbSaveRawString(fp,key,klen)) == -1) return -1;
- nwritten += n;
- if ((n = rdbSaveRawString(fp,val,vlen)) == -1) return -1;
- nwritten += n;
- }
} else {
dictIterator *di = dictGetIterator(o->ptr);
dictEntry *de;
@@ -439,6 +376,20 @@ off_t rdbSavedObjectPages(robj *o) {
return (bytes+(server.vm_page_size-1))/server.vm_page_size;
}
+int getObjectSaveType(robj *o) {
+ /* Fix the type id for specially encoded data types */
+ if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_ZIPMAP)
+ return REDIS_HASH_ZIPMAP;
+ else if (o->type == REDIS_LIST && o->encoding == REDIS_ENCODING_ZIPLIST)
+ return REDIS_LIST_ZIPLIST;
+ else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_INTSET)
+ return REDIS_SET_INTSET;
+ else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_ZIPLIST)
+ return REDIS_ZSET_ZIPLIST;
+ else
+ return o->type;
+}
+
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */
int rdbSave(char *filename) {
dictIterator *di = NULL;
@@ -495,8 +446,10 @@ int rdbSave(char *filename) {
* handling if the value is swapped out. */
if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY ||
o->storage == REDIS_VM_SWAPPING) {
+ int otype = getObjectSaveType(o);
+
/* Save type, key, value */
- if (rdbSaveType(fp,o->type) == -1) goto werr;
+ if (rdbSaveType(fp,otype) == -1) goto werr;
if (rdbSaveStringObject(fp,&key) == -1) goto werr;
if (rdbSaveObject(fp,o) == -1) goto werr;
} else {
@@ -505,7 +458,8 @@ int rdbSave(char *filename) {
/* Get a preview of the object in memory */
po = vmPreviewObject(o);
/* Save type, key, value */
- if (rdbSaveType(fp,po->type) == -1) goto werr;
+ if (rdbSaveType(fp,getObjectSaveType(po)) == -1)
+ goto werr;
if (rdbSaveStringObject(fp,&key) == -1) goto werr;
if (rdbSaveObject(fp,po) == -1) goto werr;
/* Remove the loaded object from memory */
@@ -890,6 +844,54 @@ robj *rdbLoadObject(int type, FILE *fp) {
dictAdd((dict*)o->ptr,key,val);
}
}
+ } else if (type == REDIS_HASH_ZIPMAP ||
+ type == REDIS_LIST_ZIPLIST ||
+ type == REDIS_SET_INTSET ||
+ type == REDIS_ZSET_ZIPLIST)
+ {
+ robj *aux = rdbLoadStringObject(fp);
+
+ if (aux == NULL) return NULL;
+ o = createObject(REDIS_STRING,NULL); /* string is just placeholder */
+ o->ptr = zmalloc(sdslen(aux->ptr));
+ memcpy(o->ptr,aux->ptr,sdslen(aux->ptr));
+ decrRefCount(aux);
+
+ /* Fix the object encoding, and make sure to convert the encoded
+ * data type into the base type if accordingly to the current
+ * configuration there are too many elements in the encoded data
+ * type. Note that we only check the length and not max element
+ * size as this is an O(N) scan. Eventually everything will get
+ * converted. */
+ switch(type) {
+ case REDIS_HASH_ZIPMAP:
+ o->type = REDIS_HASH;
+ o->encoding = REDIS_ENCODING_ZIPMAP;
+ if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries)
+ convertToRealHash(o);
+ break;
+ case REDIS_LIST_ZIPLIST:
+ o->type = REDIS_LIST;
+ o->encoding = REDIS_ENCODING_ZIPLIST;
+ if (ziplistLen(o->ptr) > server.list_max_ziplist_entries)
+ listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
+ break;
+ case REDIS_SET_INTSET:
+ o->type = REDIS_SET;
+ o->encoding = REDIS_ENCODING_INTSET;
+ if (intsetLen(o->ptr) > server.set_max_intset_entries)
+ setTypeConvert(o,REDIS_ENCODING_HT);
+ break;
+ case REDIS_ZSET_ZIPLIST:
+ o->type = REDIS_ZSET;
+ o->encoding = REDIS_ENCODING_ZIPLIST;
+ if (zsetLength(o) > server.zset_max_ziplist_entries)
+ zsetConvert(o,REDIS_ENCODING_SKIPLIST);
+ break;
+ default:
+ redisPanic("Unknown encoding");
+ break;
+ }
} else {
redisPanic("Unknown object type");
}
diff --git a/src/redis-cli.c b/src/redis-cli.c
index f0c712e74..9e497b4bf 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -465,7 +465,15 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
return REDIS_OK;
}
- output_raw = !strcasecmp(command,"info");
+ output_raw = 0;
+ if (!strcasecmp(command,"info") ||
+ (argc == 2 && !strcasecmp(command,"client") &&
+ !strcasecmp(argv[1],"list")))
+
+ {
+ output_raw = 1;
+ }
+
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
cliOutputHelp(--argc, ++argv);
return REDIS_OK;
diff --git a/src/redis.c b/src/redis.c
index 87d46e019..e089cefae 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -102,8 +102,8 @@ struct redisCommand readonlyCommandTable[] = {
{"ltrim",ltrimCommand,4,0,NULL,1,1,1},
{"lrem",lremCommand,4,0,NULL,1,1,1},
{"rpoplpush",rpoplpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
- {"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
- {"srem",sremCommand,3,0,NULL,1,1,1},
+ {"sadd",saddCommand,-3,REDIS_CMD_DENYOOM,NULL,1,1,1},
+ {"srem",sremCommand,-3,0,NULL,1,1,1},
{"smove",smoveCommand,4,0,NULL,1,2,1},
{"sismember",sismemberCommand,3,0,NULL,1,1,1},
{"scard",scardCommand,2,0,NULL,1,1,1},
@@ -138,7 +138,7 @@ struct redisCommand readonlyCommandTable[] = {
{"hmset",hmsetCommand,-4,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"hmget",hmgetCommand,-3,0,NULL,1,1,1},
{"hincrby",hincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1},
- {"hdel",hdelCommand,3,0,NULL,1,1,1},
+ {"hdel",hdelCommand,-3,0,NULL,1,1,1},
{"hlen",hlenCommand,2,0,NULL,1,1,1},
{"hkeys",hkeysCommand,2,0,NULL,1,1,1},
{"hvals",hvalsCommand,2,0,NULL,1,1,1},
@@ -188,7 +188,8 @@ struct redisCommand readonlyCommandTable[] = {
{"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0},
{"watch",watchCommand,-2,0,NULL,0,0,0},
{"unwatch",unwatchCommand,1,0,NULL,0,0,0},
- {"object",objectCommand,-2,0,NULL,0,0,0}
+ {"object",objectCommand,-2,0,NULL,0,0,0},
+ {"client",clientCommand,-2,0,NULL,0,0,0}
};
/*============================ Utility functions ============================ */
@@ -541,6 +542,10 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
*/
updateLRUClock();
+ /* Record the max memory used since the server was started. */
+ if (zmalloc_used_memory() > server.stat_peak_memory)
+ server.stat_peak_memory = zmalloc_used_memory();
+
/* We received a SIGTERM, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */
if (server.shutdown_asap) {
@@ -902,6 +907,7 @@ void initServer() {
server.stat_starttime = time(NULL);
server.stat_keyspace_misses = 0;
server.stat_keyspace_hits = 0;
+ server.stat_peak_memory = 0;
server.unixtime = time(NULL);
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,
@@ -1148,7 +1154,7 @@ sds genRedisInfoString(void) {
sds info;
time_t uptime = time(NULL)-server.stat_starttime;
int j;
- char hmem[64];
+ char hmem[64], peak_hmem[64];
struct rusage self_ru, c_ru;
unsigned long lol, bib;
@@ -1157,6 +1163,7 @@ sds genRedisInfoString(void) {
getClientsMaxBuffers(&lol,&bib);
bytesToHuman(hmem,zmalloc_used_memory());
+ bytesToHuman(peak_hmem,server.stat_peak_memory);
info = sdscatprintf(sdsempty(),
"redis_version:%s\r\n"
"redis_git_sha1:%s\r\n"
@@ -1179,6 +1186,8 @@ sds genRedisInfoString(void) {
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
"used_memory_rss:%zu\r\n"
+ "used_memory_peak:%zu\r\n"
+ "used_memory_peak_human:%s\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
"mem_allocator:%s\r\n"
"loading:%d\r\n"
@@ -1219,6 +1228,8 @@ sds genRedisInfoString(void) {
zmalloc_used_memory(),
hmem,
zmalloc_get_rss(),
+ server.stat_peak_memory,
+ peak_hmem,
zmalloc_get_fragmentation_ratio(),
REDIS_MALLOC,
server.loading,
diff --git a/src/redis.h b/src/redis.h
index dfc0a41bb..a425aada3 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -72,6 +72,12 @@
#define REDIS_HASH 4
#define REDIS_VMPOINTER 8
+/* Object types only used for persistence in .rdb files */
+#define REDIS_HASH_ZIPMAP 9
+#define REDIS_LIST_ZIPLIST 10
+#define REDIS_SET_INTSET 11
+#define REDIS_ZSET_ZIPLIST 12
+
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
@@ -391,6 +397,7 @@ struct redisServer {
long long stat_evictedkeys; /* number of evicted keys (maxmemory) */
long long stat_keyspace_hits; /* number of successful lookups of keys */
long long stat_keyspace_misses; /* number of failed lookups of keys */
+ size_t stat_peak_memory; /* max used memory record */
/* Configuration */
int verbosity;
int maxidletime;
@@ -766,6 +773,7 @@ off_t rdbSavedObjectLen(robj *o);
off_t rdbSavedObjectPages(robj *o);
robj *rdbLoadObject(int type, FILE *fp);
void backgroundSaveDoneHandler(int statloc);
+int getObjectSaveType(robj *o);
/* AOF persistence */
void flushAppendOnlyFile(void);
@@ -1013,6 +1021,7 @@ void publishCommand(redisClient *c);
void watchCommand(redisClient *c);
void unwatchCommand(redisClient *c);
void objectCommand(redisClient *c);
+void clientCommand(redisClient *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
diff --git a/src/t_hash.c b/src/t_hash.c
index 488bf6b7a..a5324b7eb 100644
--- a/src/t_hash.c
+++ b/src/t_hash.c
@@ -396,17 +396,22 @@ void hmgetCommand(redisClient *c) {
void hdelCommand(redisClient *c) {
robj *o;
+ int j, deleted = 0;
+
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
- if (hashTypeDelete(o,c->argv[2])) {
- if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
- addReply(c,shared.cone);
+ for (j = 2; j < c->argc; j++) {
+ if (hashTypeDelete(o,c->argv[j])) {
+ if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
+ deleted++;
+ }
+ }
+ if (deleted) {
touchWatchedKey(c->db,c->argv[1]);
- server.dirty++;
- } else {
- addReply(c,shared.czero);
+ server.dirty += deleted;
}
+ addReplyLongLong(c,deleted);
}
void hlenCommand(redisClient *c) {
diff --git a/src/t_set.c b/src/t_set.c
index 9bff7c626..26f70f646 100644
--- a/src/t_set.c
+++ b/src/t_set.c
@@ -218,9 +218,9 @@ void setTypeConvert(robj *setobj, int enc) {
void saddCommand(redisClient *c) {
robj *set;
+ int j, added = 0;
set = lookupKeyWrite(c->db,c->argv[1]);
- c->argv[2] = tryObjectEncoding(c->argv[2]);
if (set == NULL) {
set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set);
@@ -230,30 +230,34 @@ void saddCommand(redisClient *c) {
return;
}
}
- if (setTypeAdd(set,c->argv[2])) {
- touchWatchedKey(c->db,c->argv[1]);
- server.dirty++;
- addReply(c,shared.cone);
- } else {
- addReply(c,shared.czero);
+
+ for (j = 2; j < c->argc; j++) {
+ c->argv[j] = tryObjectEncoding(c->argv[j]);
+ if (setTypeAdd(set,c->argv[j])) added++;
}
+ if (added) touchWatchedKey(c->db,c->argv[1]);
+ server.dirty += added;
+ addReplyLongLong(c,added);
}
void sremCommand(redisClient *c) {
robj *set;
+ int j, deleted = 0;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- if (setTypeRemove(set,c->argv[2])) {
- if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
+ for (j = 2; j < c->argc; j++) {
+ if (setTypeRemove(set,c->argv[j])) {
+ if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
+ deleted++;
+ }
+ }
+ if (deleted) {
touchWatchedKey(c->db,c->argv[1]);
- server.dirty++;
- addReply(c,shared.cone);
- } else {
- addReply(c,shared.czero);
+ server.dirty += deleted;
}
+ addReplyLongLong(c,deleted);
}
void smoveCommand(redisClient *c) {
diff --git a/src/util.c b/src/util.c
index 7dc5d4a60..e83dbeddc 100644
--- a/src/util.c
+++ b/src/util.c
@@ -231,10 +231,13 @@ int string2ll(char *s, size_t slen, long long *value) {
return 0;
}
- /* First digit should be 1-9. */
+ /* First digit should be 1-9, otherwise the string should just be 0. */
if (p[0] >= '1' && p[0] <= '9') {
v = p[0]-'0';
p++; plen++;
+ } else if (p[0] == '0' && slen == 1) {
+ *value = 0;
+ return 1;
} else {
return 0;
}
diff --git a/src/vm.c b/src/vm.c
index cfced9535..a46fed7d3 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -30,12 +30,12 @@
/* Create a VM pointer object. This kind of objects are used in place of
* values in the key -> value hash table, for swapped out objects. */
-vmpointer *createVmPointer(int vtype) {
+vmpointer *createVmPointer(robj *o) {
vmpointer *vp = zmalloc(sizeof(vmpointer));
vp->type = REDIS_VMPOINTER;
vp->storage = REDIS_VM_SWAPPED;
- vp->vtype = vtype;
+ vp->vtype = getObjectSaveType(o);
return vp;
}
@@ -272,7 +272,7 @@ vmpointer *vmSwapObjectBlocking(robj *val) {
if (vmFindContiguousPages(&page,pages) == REDIS_ERR) return NULL;
if (vmWriteObjectOnSwap(val,page) == REDIS_ERR) return NULL;
- vp = createVmPointer(val->type);
+ vp = createVmPointer(val);
vp->page = page;
vp->usedpages = pages;
decrRefCount(val); /* Deallocate the object from memory. */
@@ -380,7 +380,7 @@ double computeObjectSwappability(robj *o) {
break;
case REDIS_LIST:
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
- asize = sizeof(*o)+ziplistSize(o->ptr);
+ asize = sizeof(*o)+ziplistBlobLen(o->ptr);
} else {
l = o->ptr;
ln = listFirst(l);
@@ -411,7 +411,7 @@ double computeObjectSwappability(robj *o) {
break;
case REDIS_ZSET:
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
- asize = sizeof(*o)+(ziplistSize(o->ptr) / 2);
+ asize = sizeof(*o)+(ziplistBlobLen(o->ptr) / 2);
} else {
d = ((zset*)o->ptr)->dict;
asize = sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d));
@@ -663,7 +663,7 @@ void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
printf("val->ptr: %s\n",(char*)j->val->ptr);
}
redisAssert(j->val->storage == REDIS_VM_SWAPPING);
- vp = createVmPointer(j->val->type);
+ vp = createVmPointer(j->val);
vp->page = j->page;
vp->usedpages = j->pages;
dictGetEntryVal(de) = vp;
diff --git a/src/ziplist.c b/src/ziplist.c
index b491bf09f..e775b77f7 100644
--- a/src/ziplist.c
+++ b/src/ziplist.c
@@ -384,12 +384,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);
@@ -718,8 +723,8 @@ unsigned int ziplistLen(unsigned char *zl) {
return len;
}
-/* Return size in bytes of ziplist. */
-unsigned int ziplistSize(unsigned char *zl) {
+/* Return ziplist blob size in bytes. */
+size_t ziplistBlobLen(unsigned char *zl) {
return ZIPLIST_BYTES(zl);
}
@@ -865,7 +870,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) {
@@ -887,10 +892,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;
@@ -1223,6 +1227,7 @@ int main(int argc, char **argv) {
int i,j,len,where;
unsigned char *p;
char buf[1024];
+ int buflen;
list *ref;
listNode *refnode;
@@ -1231,10 +1236,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();
@@ -1244,31 +1245,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);
}
@@ -1283,12 +1285,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);
diff --git a/src/ziplist.h b/src/ziplist.h
index 311257256..a07b84404 100644
--- a/src/ziplist.h
+++ b/src/ziplist.h
@@ -12,4 +12,4 @@ unsigned char *ziplistDelete(unsigned char *zl, unsigned char **p);
unsigned char *ziplistDeleteRange(unsigned char *zl, unsigned int index, unsigned int num);
unsigned int ziplistCompare(unsigned char *p, unsigned char *s, unsigned int slen);
unsigned int ziplistLen(unsigned char *zl);
-unsigned int ziplistSize(unsigned char *zl);
+size_t ziplistBlobLen(unsigned char *zl);
diff --git a/src/zipmap.c b/src/zipmap.c
index be780a828..9f0fc7181 100644
--- a/src/zipmap.c
+++ b/src/zipmap.c
@@ -80,6 +80,7 @@
#include <string.h>
#include <assert.h>
#include "zmalloc.h"
+#include "endian.h"
#define ZIPMAP_BIGLEN 254
#define ZIPMAP_END 255
@@ -108,6 +109,7 @@ static unsigned int zipmapDecodeLength(unsigned char *p) {
if (len < ZIPMAP_BIGLEN) return len;
memcpy(&len,p+1,sizeof(unsigned int));
+ memrev32ifbe(&len);
return len;
}
@@ -123,6 +125,7 @@ static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
} else {
p[0] = ZIPMAP_BIGLEN;
memcpy(p+1,&len,sizeof(len));
+ memrev32ifbe(p+1);
return 1+sizeof(len);
}
}
@@ -144,7 +147,7 @@ static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, uns
/* Match or skip the key */
l = zipmapDecodeLength(p);
llen = zipmapEncodeLength(NULL,l);
- if (k == NULL && l == klen && !memcmp(p+llen,key,l)) {
+ if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) {
/* Only return when the user doesn't care
* for the total length of the zipmap. */
if (totlen != NULL) {
@@ -360,6 +363,16 @@ unsigned int zipmapLen(unsigned char *zm) {
return len;
}
+/* Return the raw size in bytes of a zipmap, so that we can serialize
+ * the zipmap on disk (or everywhere is needed) just writing the returned
+ * amount of bytes of the C array starting at the zipmap pointer. */
+size_t zipmapBlobLen(unsigned char *zm) {
+ unsigned int totlen;
+ zipmapLookupRaw(zm,NULL,0,&totlen);
+ return totlen;
+}
+
+#ifdef ZIPMAP_TEST_MAIN
void zipmapRepr(unsigned char *p) {
unsigned int l;
@@ -393,7 +406,6 @@ void zipmapRepr(unsigned char *p) {
printf("\n");
}
-#ifdef ZIPMAP_TEST_MAIN
int main(void) {
unsigned char *zm;
diff --git a/src/zipmap.h b/src/zipmap.h
index e5f6c9f28..acb25d67a 100644
--- a/src/zipmap.h
+++ b/src/zipmap.h
@@ -43,6 +43,7 @@ unsigned char *zipmapNext(unsigned char *zm, unsigned char **key, unsigned int *
int zipmapGet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char **value, unsigned int *vlen);
int zipmapExists(unsigned char *zm, unsigned char *key, unsigned int klen);
unsigned int zipmapLen(unsigned char *zm);
+size_t zipmapBlobLen(unsigned char *zm);
void zipmapRepr(unsigned char *p);
#endif
diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl
index 4cbe6eaae..927969b62 100644
--- a/tests/integration/aof.tcl
+++ b/tests/integration/aof.tcl
@@ -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" {
+ set client [redis [dict get $srv host] [dict get $srv port]]
+ 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 {Fixed AOF: Keyspace should contain values that were parsable} {
+ test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]]
- list [$client get foo] [$client get bar]
- } {hello {}}
+ assert_equal 1 [$client scard set]
+ }
}
}
diff --git a/tests/unit/type/hash.tcl b/tests/unit/type/hash.tcl
index 8559dc3c3..9b043d3f3 100644
--- a/tests/unit/type/hash.tcl
+++ b/tests/unit/type/hash.tcl
@@ -226,6 +226,15 @@ start_server {tags {"hash"}} {
set _ $rv
} {0 0 1 0 {} 1 0 {}}
+ test {HDEL - more than a single value} {
+ set rv {}
+ r del myhash
+ r hmset myhash a 1 b 2 c 3
+ assert_equal 0 [r hdel myhash x y]
+ assert_equal 2 [r hdel myhash a c f]
+ r hgetall myhash
+ } {b 2}
+
test {HEXISTS} {
set rv {}
set k [lindex [array names smallhash *] 0]
diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl
index 5608a6480..a6d6875bc 100644
--- a/tests/unit/type/set.tcl
+++ b/tests/unit/type/set.tcl
@@ -90,6 +90,14 @@ start_server {
assert_equal {3 5} [lsort [r smembers myset]]
}
+ test {SREM with multiple arguments} {
+ r del myset
+ r sadd myset a b c d
+ assert_equal 0 [r srem myset k k k]
+ assert_equal 2 [r srem myset b d x y]
+ lsort [r smembers myset]
+ } {a c}
+
foreach {type} {hashtable intset} {
for {set i 1} {$i <= 5} {incr i} {
r del [format "set%d" $i]