Commit b04ce2a3 authored by Pieter Noordhuis's avatar Pieter Noordhuis
Browse files

Merge master with resolved conflict in src/redis-cli.c

parents 4fe83b55 b4f2e412
...@@ -26,12 +26,12 @@ ...@@ -26,12 +26,12 @@
</div> </div>
<div class="narrow"> <div class="narrow">
&iuml;&raquo;&iquest;= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tuturial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul> &iuml;&raquo;&iquest;= Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python">&lt;-&gt;</code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virutal Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul> <h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python">&lt;-&gt;</code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
<h1><a name="Hacking">Hacking</a></h1> <h1><a name="Hacking">Hacking</a></h1>
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul> <ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul>
<ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul> <ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li></ul> <h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
</div> </div>
</div> </div>
......
...@@ -15,11 +15,11 @@ endif ...@@ -15,11 +15,11 @@ endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF) CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb DEBUG?= -g -rdynamic -ggdb
INSTALL_TOP= /usr/local PREFIX= /usr/local
INSTALL_BIN= $(INSTALL_TOP)/bin INSTALL_BIN= $(PREFIX)/bin
INSTALL= cp -p INSTALL= cp -p
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
...@@ -33,6 +33,7 @@ CHECKAOFPRGNAME = redis-check-aof ...@@ -33,6 +33,7 @@ CHECKAOFPRGNAME = redis-check-aof
all: redis-server redis-benchmark redis-cli redis-check-dump redis-check-aof all: redis-server redis-benchmark redis-cli redis-check-dump redis-check-aof
# Deps (use make dep to generate this) # Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h zmalloc.h adlist.o: adlist.c adlist.h zmalloc.h
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
...@@ -40,22 +41,59 @@ ae_epoll.o: ae_epoll.c ...@@ -40,22 +41,59 @@ ae_epoll.o: ae_epoll.c
ae_kqueue.o: ae_kqueue.c ae_kqueue.o: ae_kqueue.c
ae_select.o: ae_select.c ae_select.o: ae_select.c
anet.o: anet.c fmacros.h anet.h 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
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 \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.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 dict.o: dict.c fmacros.h dict.h zmalloc.h
intset.o: intset.c intset.h zmalloc.h
linenoise.o: linenoise.c fmacros.h linenoise.o: linenoise.c fmacros.h
lzf_c.o: lzf_c.c lzfP.h lzf_c.o: lzf_c.c lzfP.h
lzf_d.o: lzf_d.c lzfP.h lzf_d.o: lzf_d.c lzfP.h
multi.o: multi.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
networking.o: networking.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
object.o: object.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.o: pqsort.c pqsort.o: pqsort.c
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 \ redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
zmalloc.h zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h redis-check-dump.o: redis-check-dump.c lzf.h
redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h \ redis-cli.o: redis-cli.c fmacros.h version.h anet.h sds.h adlist.h \
linenoise.h zmalloc.h linenoise.h
redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \ redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
adlist.h zmalloc.h lzf.h pqsort.h zipmap.h ziplist.h sha1.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
release.o: release.c release.h release.o: release.c release.h
replication.o: replication.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
sds.o: sds.c sds.h zmalloc.h sds.o: sds.c sds.h zmalloc.h
sha1.o: sha1.c sha1.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
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 \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
t_set.o: t_set.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_string.o: t_string.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_zset.o: t_zset.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
util.o: util.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
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 ziplist.o: ziplist.c zmalloc.h ziplist.h
zipmap.o: zipmap.c zmalloc.h zipmap.o: zipmap.c zmalloc.h
zmalloc.o: zmalloc.c config.h zmalloc.o: zmalloc.c config.h
...@@ -115,6 +153,7 @@ noopt: ...@@ -115,6 +153,7 @@ noopt:
make PROF="-pg" ARCH="-arch i386" make PROF="-pg" ARCH="-arch i386"
install: all install: all
mkdir -p $(INSTALL_BIN)
$(INSTALL) $(PRGNAME) $(INSTALL_BIN) $(INSTALL) $(PRGNAME) $(INSTALL_BIN)
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN) $(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN) $(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
......
...@@ -189,6 +189,7 @@ struct redisClient *createFakeClient(void) { ...@@ -189,6 +189,7 @@ struct redisClient *createFakeClient(void) {
c->querybuf = sdsempty(); c->querybuf = sdsempty();
c->argc = 0; c->argc = 0;
c->argv = NULL; c->argv = NULL;
c->bufpos = 0;
c->flags = 0; c->flags = 0;
/* We set the fake client as a slave waiting for the synchronization /* We set the fake client as a slave waiting for the synchronization
* so that Redis will not try to send replies to this client. */ * so that Redis will not try to send replies to this client. */
...@@ -272,12 +273,14 @@ int loadAppendOnlyFile(char *filename) { ...@@ -272,12 +273,14 @@ int loadAppendOnlyFile(char *filename) {
fakeClient->argc = argc; fakeClient->argc = argc;
fakeClient->argv = argv; fakeClient->argv = argv;
cmd->proc(fakeClient); cmd->proc(fakeClient);
/* Discard the reply objects list from the fake client */
while(listLength(fakeClient->reply)) /* The fake client should not have a reply */
listDelNode(fakeClient->reply,listFirst(fakeClient->reply)); redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* Clean up, ready for the next command */ /* Clean up, ready for the next command */
for (j = 0; j < argc; j++) decrRefCount(argv[j]); for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv); zfree(argv);
/* Handle swapping while loading big datasets when VM is on */ /* Handle swapping while loading big datasets when VM is on */
force_swapout = 0; force_swapout = 0;
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32) if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
...@@ -307,7 +310,7 @@ readerr: ...@@ -307,7 +310,7 @@ readerr:
} }
exit(1); exit(1);
fmterr: fmterr:
redisLog(REDIS_WARNING,"Bad file format reading the append only file"); redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
exit(1); exit(1);
} }
...@@ -463,20 +466,30 @@ int rewriteAppendOnlyFile(char *filename) { ...@@ -463,20 +466,30 @@ int rewriteAppendOnlyFile(char *filename) {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
} else if (o->type == REDIS_SET) { } else if (o->type == REDIS_SET) {
/* Emit the SADDs needed to rebuild the set */ char cmd[]="*3\r\n$4\r\nSADD\r\n";
dict *set = o->ptr;
dictIterator *di = dictGetIterator(set);
dictEntry *de;
while((de = dictNext(di)) != NULL) { /* Emit the SADDs needed to rebuild the set */
char cmd[]="*3\r\n$4\r\nSADD\r\n"; if (o->encoding == REDIS_ENCODING_INTSET) {
robj *eleobj = dictGetEntryKey(de); int ii = 0;
int64_t llval;
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr; while(intsetGet(o->ptr,ii++,&llval)) {
if (fwriteBulkObject(fp,&key) == 0) goto werr; if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkObject(fp,eleobj) == 0) goto werr; if (fwriteBulkObject(fp,&key) == 0) goto werr;
if (fwriteBulkLongLong(fp,llval) == 0) goto werr;
}
} else if (o->encoding == REDIS_ENCODING_HT) {
dictIterator *di = dictGetIterator(o->ptr);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetEntryKey(de);
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkObject(fp,&key) == 0) goto werr;
if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
}
dictReleaseIterator(di);
} else {
redisPanic("Unknown set encoding");
} }
dictReleaseIterator(di);
} else if (o->type == REDIS_ZSET) { } else if (o->type == REDIS_ZSET) {
/* Emit the ZADDs needed to rebuild the sorted set */ /* Emit the ZADDs needed to rebuild the sorted set */
zset *zs = o->ptr; zset *zs = o->ptr;
...@@ -620,12 +633,11 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -620,12 +633,11 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(redisClient *c) { void bgrewriteaofCommand(redisClient *c) {
if (server.bgrewritechildpid != -1) { if (server.bgrewritechildpid != -1) {
addReplySds(c,sdsnew("-ERR background append only file rewriting already in progress\r\n")); addReplyError(c,"Background append only file rewriting already in progress");
return; return;
} }
if (rewriteAppendOnlyFileBackground() == REDIS_OK) { if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
char *status = "+Background append only file rewriting started\r\n"; addReplyStatus(c,"Background append only file rewriting started");
addReplySds(c,sdsnew(status));
} else { } else {
addReply(c,shared.err); addReply(c,shared.err);
} }
......
...@@ -201,6 +201,8 @@ void loadServerConfig(char *filename) { ...@@ -201,6 +201,8 @@ void loadServerConfig(char *filename) {
server.list_max_ziplist_entries = memtoll(argv[1], NULL); server.list_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2){ } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2){
server.list_max_ziplist_value = memtoll(argv[1], NULL); server.list_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2){
server.set_max_intset_entries = memtoll(argv[1], NULL);
} else { } else {
err = "Bad directive or wrong number of arguments"; goto loaderr; err = "Bad directive or wrong number of arguments"; goto loaderr;
} }
...@@ -241,6 +243,7 @@ void configSetCommand(redisClient *c) { ...@@ -241,6 +243,7 @@ void configSetCommand(redisClient *c) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt; ll < 0) goto badfmt;
server.maxmemory = ll; server.maxmemory = ll;
if (server.maxmemory) freeMemoryIfNeeded();
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) { } else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > LONG_MAX) goto badfmt; ll < 0 || ll > LONG_MAX) goto badfmt;
...@@ -270,8 +273,8 @@ void configSetCommand(redisClient *c) { ...@@ -270,8 +273,8 @@ void configSetCommand(redisClient *c) {
stopAppendOnly(); stopAppendOnly();
} else { } else {
if (startAppendOnly() == REDIS_ERR) { if (startAppendOnly() == REDIS_ERR) {
addReplySds(c,sdscatprintf(sdsempty(), addReplyError(c,
"-ERR Unable to turn on AOF. Check server logs.\r\n")); "Unable to turn on AOF. Check server logs.");
decrRefCount(o); decrRefCount(o);
return; return;
} }
...@@ -312,9 +315,8 @@ void configSetCommand(redisClient *c) { ...@@ -312,9 +315,8 @@ void configSetCommand(redisClient *c) {
} }
sdsfreesplitres(v,vlen); sdsfreesplitres(v,vlen);
} else { } else {
addReplySds(c,sdscatprintf(sdsempty(), addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
"-ERR not supported CONFIG parameter %s\r\n", (char*)c->argv[2]->ptr);
(char*)c->argv[2]->ptr));
decrRefCount(o); decrRefCount(o);
return; return;
} }
...@@ -323,22 +325,18 @@ void configSetCommand(redisClient *c) { ...@@ -323,22 +325,18 @@ void configSetCommand(redisClient *c) {
return; return;
badfmt: /* Bad format errors */ badfmt: /* Bad format errors */
addReplySds(c,sdscatprintf(sdsempty(), addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
"-ERR invalid argument '%s' for CONFIG SET '%s'\r\n",
(char*)o->ptr, (char*)o->ptr,
(char*)c->argv[2]->ptr)); (char*)c->argv[2]->ptr);
decrRefCount(o); decrRefCount(o);
} }
void configGetCommand(redisClient *c) { void configGetCommand(redisClient *c) {
robj *o = getDecodedObject(c->argv[2]); robj *o = getDecodedObject(c->argv[2]);
robj *lenobj = createObject(REDIS_STRING,NULL); void *replylen = addDeferredMultiBulkLength(c);
char *pattern = o->ptr; char *pattern = o->ptr;
int matches = 0; int matches = 0;
addReply(c,lenobj);
decrRefCount(lenobj);
if (stringmatch(pattern,"dbfilename",0)) { if (stringmatch(pattern,"dbfilename",0)) {
addReplyBulkCString(c,"dbfilename"); addReplyBulkCString(c,"dbfilename");
addReplyBulkCString(c,server.dbfilename); addReplyBulkCString(c,server.dbfilename);
...@@ -410,7 +408,7 @@ void configGetCommand(redisClient *c) { ...@@ -410,7 +408,7 @@ void configGetCommand(redisClient *c) {
matches++; matches++;
} }
decrRefCount(o); decrRefCount(o);
lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2); setDeferredMultiBulkLength(c,replylen,matches*2);
} }
void configCommand(redisClient *c) { void configCommand(redisClient *c) {
...@@ -428,13 +426,12 @@ void configCommand(redisClient *c) { ...@@ -428,13 +426,12 @@ void configCommand(redisClient *c) {
server.stat_starttime = time(NULL); server.stat_starttime = time(NULL);
addReply(c,shared.ok); addReply(c,shared.ok);
} else { } else {
addReplySds(c,sdscatprintf(sdsempty(), addReplyError(c,
"-ERR CONFIG subcommand must be one of GET, SET, RESETSTAT\r\n")); "CONFIG subcommand must be one of GET, SET, RESETSTAT");
} }
return; return;
badarity: badarity:
addReplySds(c,sdscatprintf(sdsempty(), addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
"-ERR Wrong number of arguments for CONFIG %s\r\n", (char*) c->argv[1]->ptr);
(char*) c->argv[1]->ptr));
} }
...@@ -21,6 +21,16 @@ ...@@ -21,6 +21,16 @@
#define redis_stat stat #define redis_stat stat
#endif #endif
/* test for proc filesystem */
#ifdef __linux__
#define HAVE_PROCFS 1
#endif
/* test for task_info() */
#if defined(__APPLE__)
#define HAVE_TASKINFO 1
#endif
/* test for backtrace() */ /* test for backtrace() */
#if defined(__APPLE__) || defined(__linux__) #if defined(__APPLE__) || defined(__linux__)
#define HAVE_BACKTRACE 1 #define HAVE_BACKTRACE 1
......
...@@ -45,7 +45,7 @@ robj *lookupKeyRead(redisDb *db, robj *key) { ...@@ -45,7 +45,7 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
} }
robj *lookupKeyWrite(redisDb *db, robj *key) { robj *lookupKeyWrite(redisDb *db, robj *key) {
deleteIfVolatile(db,key); expireIfNeeded(db,key);
return lookupKey(db,key); return lookupKey(db,key);
} }
...@@ -123,6 +123,11 @@ robj *dbRandomKey(redisDb *db) { ...@@ -123,6 +123,11 @@ robj *dbRandomKey(redisDb *db) {
/* Delete a key, value, and associated expiration entry if any, from the DB */ /* Delete a key, value, and associated expiration entry if any, from the DB */
int dbDelete(redisDb *db, robj *key) { int dbDelete(redisDb *db, robj *key) {
/* If VM is enabled make sure to awake waiting clients for this key:
* deleting the key will kill the I/O thread bringing the key from swap
* to memory, so the client will never be notified and unblocked if we
* don't do it now. */
if (server.vm_enabled) handleClientsBlockedOnSwappedKey(db,key);
/* Deleting an entry from the expires dict will not free the sds of /* Deleting an entry from the expires dict will not free the sds of
* the key, because it is shared with the main dictionary. */ * the key, because it is shared with the main dictionary. */
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
...@@ -199,7 +204,7 @@ void selectCommand(redisClient *c) { ...@@ -199,7 +204,7 @@ void selectCommand(redisClient *c) {
int id = atoi(c->argv[1]->ptr); int id = atoi(c->argv[1]->ptr);
if (selectDb(c,id) == REDIS_ERR) { if (selectDb(c,id) == REDIS_ERR) {
addReplySds(c,sdsnew("-ERR invalid DB index\r\n")); addReplyError(c,"invalid DB index");
} else { } else {
addReply(c,shared.ok); addReply(c,shared.ok);
} }
...@@ -221,19 +226,17 @@ void keysCommand(redisClient *c) { ...@@ -221,19 +226,17 @@ void keysCommand(redisClient *c) {
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
sds pattern = c->argv[1]->ptr; sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern); int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0; unsigned long numkeys = 0;
robj *lenobj = createObject(REDIS_STRING,NULL); void *replylen = addDeferredMultiBulkLength(c);
di = dictGetIterator(c->db->dict); di = dictGetIterator(c->db->dict);
addReply(c,lenobj); allkeys = (pattern[0] == '*' && pattern[1] == '\0');
decrRefCount(lenobj);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
sds key = dictGetEntryKey(de); sds key = dictGetEntryKey(de);
robj *keyobj; robj *keyobj;
if ((pattern[0] == '*' && pattern[1] == '\0') || if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key)); keyobj = createStringObject(key,sdslen(key));
if (expireIfNeeded(c->db,keyobj) == 0) { if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj); addReplyBulk(c,keyobj);
...@@ -243,17 +246,15 @@ void keysCommand(redisClient *c) { ...@@ -243,17 +246,15 @@ void keysCommand(redisClient *c) {
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",numkeys); setDeferredMultiBulkLength(c,replylen,numkeys);
} }
void dbsizeCommand(redisClient *c) { void dbsizeCommand(redisClient *c) {
addReplySds(c, addReplyLongLong(c,dictSize(c->db->dict));
sdscatprintf(sdsempty(),":%lu\r\n",dictSize(c->db->dict)));
} }
void lastsaveCommand(redisClient *c) { void lastsaveCommand(redisClient *c) {
addReplySds(c, addReplyLongLong(c,server.lastsave);
sdscatprintf(sdsempty(),":%lu\r\n",server.lastsave));
} }
void typeCommand(redisClient *c) { void typeCommand(redisClient *c) {
...@@ -262,24 +263,23 @@ void typeCommand(redisClient *c) { ...@@ -262,24 +263,23 @@ void typeCommand(redisClient *c) {
o = lookupKeyRead(c->db,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1]);
if (o == NULL) { if (o == NULL) {
type = "+none"; type = "none";
} else { } else {
switch(o->type) { switch(o->type) {
case REDIS_STRING: type = "+string"; break; case REDIS_STRING: type = "string"; break;
case REDIS_LIST: type = "+list"; break; case REDIS_LIST: type = "list"; break;
case REDIS_SET: type = "+set"; break; case REDIS_SET: type = "set"; break;
case REDIS_ZSET: type = "+zset"; break; case REDIS_ZSET: type = "zset"; break;
case REDIS_HASH: type = "+hash"; break; case REDIS_HASH: type = "hash"; break;
default: type = "+unknown"; break; default: type = "unknown"; break;
} }
} }
addReplySds(c,sdsnew(type)); addReplyStatus(c,type);
addReply(c,shared.crlf);
} }
void saveCommand(redisClient *c) { void saveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) { if (server.bgsavechildpid != -1) {
addReplySds(c,sdsnew("-ERR background save in progress\r\n")); addReplyError(c,"Background save already in progress");
return; return;
} }
if (rdbSave(server.dbfilename) == REDIS_OK) { if (rdbSave(server.dbfilename) == REDIS_OK) {
...@@ -291,12 +291,11 @@ void saveCommand(redisClient *c) { ...@@ -291,12 +291,11 @@ void saveCommand(redisClient *c) {
void bgsaveCommand(redisClient *c) { void bgsaveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) { if (server.bgsavechildpid != -1) {
addReplySds(c,sdsnew("-ERR background save already in progress\r\n")); addReplyError(c,"Background save already in progress");
return; return;
} }
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) { if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
char *status = "+Background saving started\r\n"; addReplyStatus(c,"Background saving started");
addReplySds(c,sdsnew(status));
} else { } else {
addReply(c,shared.err); addReply(c,shared.err);
} }
...@@ -305,7 +304,7 @@ void bgsaveCommand(redisClient *c) { ...@@ -305,7 +304,7 @@ void bgsaveCommand(redisClient *c) {
void shutdownCommand(redisClient *c) { void shutdownCommand(redisClient *c) {
if (prepareForShutdown() == REDIS_OK) if (prepareForShutdown() == REDIS_OK)
exit(0); exit(0);
addReplySds(c, sdsnew("-ERR Errors trying to SHUTDOWN. Check logs.\r\n")); addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
} }
void renameGenericCommand(redisClient *c, int nx) { void renameGenericCommand(redisClient *c, int nx) {
...@@ -321,7 +320,6 @@ void renameGenericCommand(redisClient *c, int nx) { ...@@ -321,7 +320,6 @@ void renameGenericCommand(redisClient *c, int nx) {
return; return;
incrRefCount(o); incrRefCount(o);
deleteIfVolatile(c->db,c->argv[2]);
if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) { if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
if (nx) { if (nx) {
decrRefCount(o); decrRefCount(o);
...@@ -375,7 +373,6 @@ void moveCommand(redisClient *c) { ...@@ -375,7 +373,6 @@ void moveCommand(redisClient *c) {
} }
/* Try to add the element to the target DB */ /* Try to add the element to the target DB */
deleteIfVolatile(dst,c->argv[1]);
if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) { if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
...@@ -396,23 +393,16 @@ int removeExpire(redisDb *db, robj *key) { ...@@ -396,23 +393,16 @@ int removeExpire(redisDb *db, robj *key) {
/* An expire may only be removed if there is a corresponding entry in the /* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */ * main dict. Otherwise, the key will never be freed. */
redisAssert(dictFind(db->dict,key->ptr) != NULL); redisAssert(dictFind(db->dict,key->ptr) != NULL);
if (dictDelete(db->expires,key->ptr) == DICT_OK) { return dictDelete(db->expires,key->ptr) == DICT_OK;
return 1;
} else {
return 0;
}
} }
int setExpire(redisDb *db, robj *key, time_t when) { void setExpire(redisDb *db, robj *key, time_t when) {
dictEntry *de; dictEntry *de;
/* Reuse the sds from the main dict in the expire dict */ /* Reuse the sds from the main dict in the expire dict */
redisAssert((de = dictFind(db->dict,key->ptr)) != NULL); de = dictFind(db->dict,key->ptr);
if (dictAdd(db->expires,dictGetEntryKey(de),(void*)when) == DICT_ERR) { redisAssert(de != NULL);
return 0; dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
} else {
return 1;
}
} }
/* Return the expire time of the specified key, or -1 if no expire /* Return the expire time of the specified key, or -1 if no expire
...@@ -430,8 +420,46 @@ time_t getExpire(redisDb *db, robj *key) { ...@@ -430,8 +420,46 @@ time_t getExpire(redisDb *db, robj *key) {
return (time_t) dictGetEntryVal(de); return (time_t) dictGetEntryVal(de);
} }
/* Propagate expires into slaves and the AOF file.
* When a key expires in the master, a DEL operation for this key is sent
* to all the slaves and the AOF file if enabled.
*
* This way the key expiry is centralized in one place, and since both
* AOF and the master->slave link guarantee operation ordering, everything
* will be consistent even if we allow write operations against expiring
* keys. */
void propagateExpire(redisDb *db, robj *key) {
struct redisCommand *cmd;
robj *argv[2];
cmd = lookupCommand("del");
argv[0] = createStringObject("DEL",3);
argv[1] = key;
incrRefCount(key);
if (server.appendonly)
feedAppendOnlyFile(cmd,db->id,argv,2);
if (listLength(server.slaves))
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
int expireIfNeeded(redisDb *db, robj *key) { int expireIfNeeded(redisDb *db, robj *key) {
time_t when = getExpire(db,key); time_t when = getExpire(db,key);
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) {
return time(NULL) > when;
}
if (when < 0) return 0; if (when < 0) return 0;
/* Return when this key has not expired */ /* Return when this key has not expired */
...@@ -440,15 +468,7 @@ int expireIfNeeded(redisDb *db, robj *key) { ...@@ -440,15 +468,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
/* Delete the key */ /* Delete the key */
server.stat_expiredkeys++; server.stat_expiredkeys++;
server.dirty++; server.dirty++;
return dbDelete(db,key); propagateExpire(db,key);
}
int deleteIfVolatile(redisDb *db, robj *key) {
if (getExpire(db,key) < 0) return 0;
/* Delete the key */
server.stat_expiredkeys++;
server.dirty++;
return dbDelete(db,key); return dbDelete(db,key);
} }
...@@ -458,7 +478,7 @@ int deleteIfVolatile(redisDb *db, robj *key) { ...@@ -458,7 +478,7 @@ int deleteIfVolatile(redisDb *db, robj *key) {
void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) { void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
dictEntry *de; dictEntry *de;
time_t seconds; long seconds;
if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return; if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
...@@ -476,13 +496,10 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) { ...@@ -476,13 +496,10 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
return; return;
} else { } else {
time_t when = time(NULL)+seconds; time_t when = time(NULL)+seconds;
if (setExpire(c->db,key,when)) { setExpire(c->db,key,when);
addReply(c,shared.cone); addReply(c,shared.cone);
touchWatchedKey(c->db,key); touchWatchedKey(c->db,key);
server.dirty++; server.dirty++;
} else {
addReply(c,shared.czero);
}
return; return;
} }
} }
...@@ -496,13 +513,28 @@ void expireatCommand(redisClient *c) { ...@@ -496,13 +513,28 @@ void expireatCommand(redisClient *c) {
} }
void ttlCommand(redisClient *c) { void ttlCommand(redisClient *c) {
time_t expire; time_t expire, ttl = -1;
int ttl = -1;
expire = getExpire(c->db,c->argv[1]); expire = getExpire(c->db,c->argv[1]);
if (expire != -1) { if (expire != -1) {
ttl = (int) (expire-time(NULL)); ttl = (expire-time(NULL));
if (ttl < 0) ttl = -1; if (ttl < 0) ttl = -1;
} }
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl)); addReplyLongLong(c,(long long)ttl);
}
void persistCommand(redisClient *c) {
dictEntry *de;
de = dictFind(c->db->dict,c->argv[1]->ptr);
if (de == NULL) {
addReply(c,shared.czero);
} else {
if (removeExpire(c->db,c->argv[1])) {
addReply(c,shared.cone);
server.dirty++;
} else {
addReply(c,shared.czero);
}
}
} }
...@@ -119,16 +119,13 @@ void computeDatasetDigest(unsigned char *final) { ...@@ -119,16 +119,13 @@ void computeDatasetDigest(unsigned char *final) {
} }
listTypeReleaseIterator(li); listTypeReleaseIterator(li);
} else if (o->type == REDIS_SET) { } else if (o->type == REDIS_SET) {
dict *set = o->ptr; setTypeIterator *si = setTypeInitIterator(o);
dictIterator *di = dictGetIterator(set); robj *ele;
dictEntry *de; while((ele = setTypeNext(si)) != NULL) {
xorObjectDigest(digest,ele);
while((de = dictNext(di)) != NULL) { decrRefCount(ele);
robj *eleobj = dictGetEntryKey(de);
xorObjectDigest(digest,eleobj);
} }
dictReleaseIterator(di); setTypeReleaseIterator(si);
} else if (o->type == REDIS_ZSET) { } else if (o->type == REDIS_ZSET) {
zset *zs = o->ptr; zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict); dictIterator *di = dictGetIterator(zs->dict);
...@@ -214,18 +211,18 @@ void debugCommand(redisClient *c) { ...@@ -214,18 +211,18 @@ void debugCommand(redisClient *c) {
char *strenc; char *strenc;
strenc = strEncoding(val->encoding); strenc = strEncoding(val->encoding);
addReplySds(c,sdscatprintf(sdsempty(), addReplyStatusFormat(c,
"+Value at:%p refcount:%d " "Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld\r\n", "encoding:%s serializedlength:%lld",
(void*)val, val->refcount, (void*)val, val->refcount,
strenc, (long long) rdbSavedObjectLen(val,NULL))); strenc, (long long) rdbSavedObjectLen(val,NULL));
} else { } else {
vmpointer *vp = (vmpointer*) val; vmpointer *vp = (vmpointer*) val;
addReplySds(c,sdscatprintf(sdsempty(), addReplyStatusFormat(c,
"+Value swapped at: page %llu " "Value swapped at: page %llu "
"using %llu pages\r\n", "using %llu pages",
(unsigned long long) vp->page, (unsigned long long) vp->page,
(unsigned long long) vp->usedpages)); (unsigned long long) vp->usedpages);
} }
} else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) {
lookupKeyRead(c->db,c->argv[2]); lookupKeyRead(c->db,c->argv[2]);
...@@ -236,7 +233,7 @@ void debugCommand(redisClient *c) { ...@@ -236,7 +233,7 @@ void debugCommand(redisClient *c) {
vmpointer *vp; vmpointer *vp;
if (!server.vm_enabled) { if (!server.vm_enabled) {
addReplySds(c,sdsnew("-ERR Virtual Memory is disabled\r\n")); addReplyError(c,"Virtual Memory is disabled");
return; return;
} }
if (!de) { if (!de) {
...@@ -246,9 +243,9 @@ void debugCommand(redisClient *c) { ...@@ -246,9 +243,9 @@ void debugCommand(redisClient *c) {
val = dictGetEntryVal(de); val = dictGetEntryVal(de);
/* Swap it */ /* Swap it */
if (val->storage != REDIS_VM_MEMORY) { if (val->storage != REDIS_VM_MEMORY) {
addReplySds(c,sdsnew("-ERR This key is not in memory\r\n")); addReplyError(c,"This key is not in memory");
} else if (val->refcount != 1) { } else if (val->refcount != 1) {
addReplySds(c,sdsnew("-ERR Object is shared\r\n")); addReplyError(c,"Object is shared");
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) { } else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
dictGetEntryVal(de) = vp; dictGetEntryVal(de) = vp;
addReply(c,shared.ok); addReply(c,shared.ok);
...@@ -277,18 +274,17 @@ void debugCommand(redisClient *c) { ...@@ -277,18 +274,17 @@ void debugCommand(redisClient *c) {
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
unsigned char digest[20]; unsigned char digest[20];
sds d = sdsnew("+"); sds d = sdsempty();
int j; int j;
computeDatasetDigest(digest); computeDatasetDigest(digest);
for (j = 0; j < 20; j++) for (j = 0; j < 20; j++)
d = sdscatprintf(d, "%02x",digest[j]); d = sdscatprintf(d, "%02x",digest[j]);
addReplyStatus(c,d);
d = sdscatlen(d,"\r\n",2); sdsfree(d);
addReplySds(c,d);
} else { } else {
addReplySds(c,sdsnew( addReplyError(c,
"-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]\r\n")); "Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
} }
} }
......
...@@ -49,8 +49,13 @@ ...@@ -49,8 +49,13 @@
/* Using dictEnableResize() / dictDisableResize() we make possible to /* Using dictEnableResize() / dictDisableResize() we make possible to
* enable/disable resizing of the hash table as needed. This is very important * enable/disable resizing of the hash table as needed. This is very important
* for Redis, as we use copy-on-write and don't want to move too much memory * for Redis, as we use copy-on-write and don't want to move too much memory
* around when there is a child performing saving operations. */ * around when there is a child performing saving operations.
*
* Note that even when dict_can_resize is set to 0, not all resizes are
* prevented: an hash table is still allowed to grow if the ratio between
* the number of elements and the buckets > dict_force_resize_ratio. */
static int dict_can_resize = 1; static int dict_can_resize = 1;
static unsigned int dict_force_resize_ratio = 5;
/* -------------------------- private prototypes ---------------------------- */ /* -------------------------- private prototypes ---------------------------- */
...@@ -125,7 +130,7 @@ int _dictInit(dict *d, dictType *type, ...@@ -125,7 +130,7 @@ int _dictInit(dict *d, dictType *type,
} }
/* Resize the table to the minimal size that contains all the elements, /* Resize the table to the minimal size that contains all the elements,
* but with the invariant of a USER/BUCKETS ration near to <= 1 */ * but with the invariant of a USER/BUCKETS ratio near to <= 1 */
int dictResize(dict *d) int dictResize(dict *d)
{ {
int minimal; int minimal;
...@@ -493,14 +498,23 @@ dictEntry *dictGetRandomKey(dict *d) ...@@ -493,14 +498,23 @@ dictEntry *dictGetRandomKey(dict *d)
/* Expand the hash table if needed */ /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d) static int _dictExpandIfNeeded(dict *d)
{ {
/* If the hash table is empty expand it to the intial size, /* Incremental rehashing already in progress. Return. */
* if the table is "full" dobule its size. */
if (dictIsRehashing(d)) return DICT_OK; if (dictIsRehashing(d)) return DICT_OK;
if (d->ht[0].size == 0)
return dictExpand(d, DICT_HT_INITIAL_SIZE); /* If the hash table is empty expand it to the intial size. */
if (d->ht[0].used >= d->ht[0].size && dict_can_resize) if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
/* If we reached the 1:1 ratio, and we are allowed to resize the hash
* table (global setting) or we should avoid it but the ratio between
* elements/buckets is over the "safe" threshold, we resize doubling
* the number of buckets. */
if (d->ht[0].used >= d->ht[0].size &&
(dict_can_resize ||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ? return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
d->ht[0].size : d->ht[0].used)*2); d->ht[0].size : d->ht[0].used)*2);
}
return DICT_OK; return DICT_OK;
} }
......
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "intset.h"
#include "zmalloc.h"
/* Note that these encodings are ordered, so:
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
/* Return the required encoding for the provided value. */
static uint8_t _intsetValueEncoding(int64_t v) {
if (v < INT32_MIN || v > INT32_MAX)
return INTSET_ENC_INT64;
else if (v < INT16_MIN || v > INT16_MAX)
return INTSET_ENC_INT32;
return INTSET_ENC_INT16;
}
/* Return the value at pos, given an encoding. */
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
if (enc == INTSET_ENC_INT64)
return ((int64_t*)is->contents)[pos];
else if (enc == INTSET_ENC_INT32)
return ((int32_t*)is->contents)[pos];
return ((int16_t*)is->contents)[pos];
}
/* Return the value at pos, using the configured encoding. */
static int64_t _intsetGet(intset *is, int pos) {
return _intsetGetEncoded(is,pos,is->encoding);
}
/* Set the value at pos, using the configured encoding. */
static void _intsetSet(intset *is, int pos, int64_t value) {
if (is->encoding == INTSET_ENC_INT64)
((int64_t*)is->contents)[pos] = value;
else if (is->encoding == INTSET_ENC_INT32)
((int32_t*)is->contents)[pos] = value;
else
((int16_t*)is->contents)[pos] = value;
}
/* Create an empty intset. */
intset *intsetNew(void) {
intset *is = zmalloc(sizeof(intset));
is->encoding = INTSET_ENC_INT16;
is->length = 0;
return is;
}
/* Resize the intset */
static intset *intsetResize(intset *is, uint32_t len) {
uint32_t size = len*is->encoding;
is = zrealloc(is,sizeof(intset)+size);
return is;
}
/* Search for the position of "value". Return 1 when the value was found and
* sets "pos" to the position of the value within the intset. Return 0 when
* the value is not present in the intset and sets "pos" to the position
* where "value" can be inserted. */
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
int min = 0, max = is->length-1, mid = -1;
int64_t cur = -1;
/* The value can never be found when the set is empty */
if (is->length == 0) {
if (pos) *pos = 0;
return 0;
} else {
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
if (value > _intsetGet(is,is->length-1)) {
if (pos) *pos = is->length;
return 0;
} else if (value < _intsetGet(is,0)) {
if (pos) *pos = 0;
return 0;
}
}
while(max >= min) {
mid = (min+max)/2;
cur = _intsetGet(is,mid);
if (value > cur) {
min = mid+1;
} else if (value < cur) {
max = mid-1;
} else {
break;
}
}
if (value == cur) {
if (pos) *pos = mid;
return 1;
} else {
if (pos) *pos = min;
return 0;
}
}
/* Upgrades the intset to a larger encoding and inserts the given integer. */
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
uint8_t curenc = is->encoding;
uint8_t newenc = _intsetValueEncoding(value);
int length = is->length;
int prepend = value < 0 ? 1 : 0;
/* First set new encoding and resize */
is->encoding = newenc;
is = intsetResize(is,is->length+1);
/* Upgrade back-to-front so we don't overwrite values.
* Note that the "prepend" variable is used to make sure we have an empty
* space at either the beginning or the end of the intset. */
while(length--)
_intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
/* Set the value at the beginning or the end. */
if (prepend)
_intsetSet(is,0,value);
else
_intsetSet(is,is->length,value);
is->length++;
return is;
}
static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
void *src, *dst;
uint32_t bytes = is->length-from;
if (is->encoding == INTSET_ENC_INT64) {
src = (int64_t*)is->contents+from;
dst = (int64_t*)is->contents+to;
bytes *= sizeof(int64_t);
} else if (is->encoding == INTSET_ENC_INT32) {
src = (int32_t*)is->contents+from;
dst = (int32_t*)is->contents+to;
bytes *= sizeof(int32_t);
} else {
src = (int16_t*)is->contents+from;
dst = (int16_t*)is->contents+to;
bytes *= sizeof(int16_t);
}
memmove(dst,src,bytes);
}
/* Insert an integer in the intset */
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 1;
/* Upgrade encoding if necessary. If we need to upgrade, we know that
* this value should be either appended (if > 0) or prepended (if < 0),
* because it lies outside the range of existing values. */
if (valenc > is->encoding) {
/* This always succeeds, so we don't need to curry *success. */
return intsetUpgradeAndAdd(is,value);
} else {
/* Abort if the value is already present in the set.
* This call will populate "pos" with the right position to insert
* the value when it cannot be found. */
if (intsetSearch(is,value,&pos)) {
if (success) *success = 0;
return is;
}
is = intsetResize(is,is->length+1);
if (pos < is->length) intsetMoveTail(is,pos,pos+1);
}
_intsetSet(is,pos,value);
is->length++;
return is;
}
/* Delete integer from intset */
intset *intsetRemove(intset *is, int64_t value, uint8_t *success) {
uint8_t valenc = _intsetValueEncoding(value);
uint32_t pos;
if (success) *success = 0;
if (valenc <= is->encoding && intsetSearch(is,value,&pos)) {
/* We know we can delete */
if (success) *success = 1;
/* Overwrite value with tail and update length */
if (pos < (is->length-1)) intsetMoveTail(is,pos+1,pos);
is = intsetResize(is,is->length-1);
is->length--;
}
return is;
}
/* Determine whether a value belongs to this set */
uint8_t intsetFind(intset *is, int64_t value) {
uint8_t valenc = _intsetValueEncoding(value);
return valenc <= is->encoding && intsetSearch(is,value,NULL);
}
/* Return random member */
int64_t intsetRandom(intset *is) {
return _intsetGet(is,rand()%is->length);
}
/* Sets the value to the value at the given position. When this position is
* out of range the function returns 0, when in range it returns 1. */
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
if (pos < is->length) {
*value = _intsetGet(is,pos);
return 1;
}
return 0;
}
/* Return intset length */
uint32_t intsetLen(intset *is) {
return is->length;
}
#ifdef INTSET_TEST_MAIN
#include <sys/time.h>
void intsetRepr(intset *is) {
int i;
for (i = 0; i < is->length; i++) {
printf("%lld\n", (uint64_t)_intsetGet(is,i));
}
printf("\n");
}
void error(char *err) {
printf("%s\n", err);
exit(1);
}
void ok(void) {
printf("OK\n");
}
long long usec(void) {
struct timeval tv;
gettimeofday(&tv,NULL);
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
}
#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
void _assert(char *estr, char *file, int line) {
printf("\n\n=== ASSERTION FAILED ===\n");
printf("==> %s:%d '%s' is not true\n",file,line,estr);
}
intset *createSet(int bits, int size) {
uint64_t mask = (1<<bits)-1;
uint64_t i, value;
intset *is = intsetNew();
for (i = 0; i < size; i++) {
if (bits > 32) {
value = (rand()*rand()) & mask;
} else {
value = rand() & mask;
}
is = intsetAdd(is,value,NULL);
}
return is;
}
void checkConsistency(intset *is) {
int i;
for (i = 0; i < (is->length-1); i++) {
if (is->encoding == INTSET_ENC_INT16) {
int16_t *i16 = (int16_t*)is->contents;
assert(i16[i] < i16[i+1]);
} else if (is->encoding == INTSET_ENC_INT32) {
int32_t *i32 = (int32_t*)is->contents;
assert(i32[i] < i32[i+1]);
} else {
int64_t *i64 = (int64_t*)is->contents;
assert(i64[i] < i64[i+1]);
}
}
}
int main(int argc, char **argv) {
uint8_t success;
int i;
intset *is;
sranddev();
printf("Value encodings: "); {
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
assert(_intsetValueEncoding(+32767) == INTSET_ENC_INT16);
assert(_intsetValueEncoding(-32769) == INTSET_ENC_INT32);
assert(_intsetValueEncoding(+32768) == INTSET_ENC_INT32);
assert(_intsetValueEncoding(-2147483648) == INTSET_ENC_INT32);
assert(_intsetValueEncoding(+2147483647) == INTSET_ENC_INT32);
assert(_intsetValueEncoding(-2147483649) == INTSET_ENC_INT64);
assert(_intsetValueEncoding(+2147483648) == INTSET_ENC_INT64);
assert(_intsetValueEncoding(-9223372036854775808ull) == INTSET_ENC_INT64);
assert(_intsetValueEncoding(+9223372036854775807ull) == INTSET_ENC_INT64);
ok();
}
printf("Basic adding: "); {
is = intsetNew();
is = intsetAdd(is,5,&success); assert(success);
is = intsetAdd(is,6,&success); assert(success);
is = intsetAdd(is,4,&success); assert(success);
is = intsetAdd(is,4,&success); assert(!success);
ok();
}
printf("Large number of random adds: "); {
int inserts = 0;
is = intsetNew();
for (i = 0; i < 1024; i++) {
is = intsetAdd(is,rand()%0x800,&success);
if (success) inserts++;
}
assert(is->length == inserts);
checkConsistency(is);
ok();
}
printf("Upgrade from int16 to int32: "); {
is = intsetNew();
is = intsetAdd(is,32,NULL);
assert(is->encoding == INTSET_ENC_INT16);
is = intsetAdd(is,65535,NULL);
assert(is->encoding == INTSET_ENC_INT32);
assert(intsetFind(is,32));
assert(intsetFind(is,65535));
checkConsistency(is);
is = intsetNew();
is = intsetAdd(is,32,NULL);
assert(is->encoding == INTSET_ENC_INT16);
is = intsetAdd(is,-65535,NULL);
assert(is->encoding == INTSET_ENC_INT32);
assert(intsetFind(is,32));
assert(intsetFind(is,-65535));
checkConsistency(is);
ok();
}
printf("Upgrade from int16 to int64: "); {
is = intsetNew();
is = intsetAdd(is,32,NULL);
assert(is->encoding == INTSET_ENC_INT16);
is = intsetAdd(is,4294967295,NULL);
assert(is->encoding == INTSET_ENC_INT64);
assert(intsetFind(is,32));
assert(intsetFind(is,4294967295));
checkConsistency(is);
is = intsetNew();
is = intsetAdd(is,32,NULL);
assert(is->encoding == INTSET_ENC_INT16);
is = intsetAdd(is,-4294967295,NULL);
assert(is->encoding == INTSET_ENC_INT64);
assert(intsetFind(is,32));
assert(intsetFind(is,-4294967295));
checkConsistency(is);
ok();
}
printf("Upgrade from int32 to int64: "); {
is = intsetNew();
is = intsetAdd(is,65535,NULL);
assert(is->encoding == INTSET_ENC_INT32);
is = intsetAdd(is,4294967295,NULL);
assert(is->encoding == INTSET_ENC_INT64);
assert(intsetFind(is,65535));
assert(intsetFind(is,4294967295));
checkConsistency(is);
is = intsetNew();
is = intsetAdd(is,65535,NULL);
assert(is->encoding == INTSET_ENC_INT32);
is = intsetAdd(is,-4294967295,NULL);
assert(is->encoding == INTSET_ENC_INT64);
assert(intsetFind(is,65535));
assert(intsetFind(is,-4294967295));
checkConsistency(is);
ok();
}
printf("Stress lookups: "); {
long num = 100000, size = 10000;
int i, bits = 20;
long long start;
is = createSet(bits,size);
checkConsistency(is);
start = usec();
for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
printf("%ld lookups, %ld element set, %lldusec\n",num,size,usec()-start);
}
printf("Stress add+delete: "); {
int i, v1, v2;
is = intsetNew();
for (i = 0; i < 0xffff; i++) {
v1 = rand() % 0xfff;
is = intsetAdd(is,v1,NULL);
assert(intsetFind(is,v1));
v2 = rand() % 0xfff;
is = intsetRemove(is,v2,NULL);
assert(!intsetFind(is,v2));
}
checkConsistency(is);
ok();
}
}
#endif
#ifndef __INTSET_H
#define __INTSET_H
#include <stdint.h>
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
intset *intsetNew(void);
intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, uint8_t *success);
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);
#endif // __INTSET_H
...@@ -42,7 +42,7 @@ void queueMultiCommand(redisClient *c, struct redisCommand *cmd) { ...@@ -42,7 +42,7 @@ void queueMultiCommand(redisClient *c, struct redisCommand *cmd) {
void multiCommand(redisClient *c) { void multiCommand(redisClient *c) {
if (c->flags & REDIS_MULTI) { if (c->flags & REDIS_MULTI) {
addReplySds(c,sdsnew("-ERR MULTI calls can not be nested\r\n")); addReplyError(c,"MULTI calls can not be nested");
return; return;
} }
c->flags |= REDIS_MULTI; c->flags |= REDIS_MULTI;
...@@ -51,7 +51,7 @@ void multiCommand(redisClient *c) { ...@@ -51,7 +51,7 @@ void multiCommand(redisClient *c) {
void discardCommand(redisClient *c) { void discardCommand(redisClient *c) {
if (!(c->flags & REDIS_MULTI)) { if (!(c->flags & REDIS_MULTI)) {
addReplySds(c,sdsnew("-ERR DISCARD without MULTI\r\n")); addReplyError(c,"DISCARD without MULTI");
return; return;
} }
...@@ -82,7 +82,7 @@ void execCommand(redisClient *c) { ...@@ -82,7 +82,7 @@ void execCommand(redisClient *c) {
int orig_argc; int orig_argc;
if (!(c->flags & REDIS_MULTI)) { if (!(c->flags & REDIS_MULTI)) {
addReplySds(c,sdsnew("-ERR EXEC without MULTI\r\n")); addReplyError(c,"EXEC without MULTI");
return; return;
} }
...@@ -107,7 +107,7 @@ void execCommand(redisClient *c) { ...@@ -107,7 +107,7 @@ void execCommand(redisClient *c) {
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */ unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv; orig_argv = c->argv;
orig_argc = c->argc; orig_argc = c->argc;
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->mstate.count)); addReplyMultiBulkLen(c,c->mstate.count);
for (j = 0; j < c->mstate.count; j++) { for (j = 0; j < c->mstate.count; j++) {
c->argc = c->mstate.commands[j].argc; c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv; c->argv = c->mstate.commands[j].argv;
...@@ -251,7 +251,7 @@ void watchCommand(redisClient *c) { ...@@ -251,7 +251,7 @@ void watchCommand(redisClient *c) {
int j; int j;
if (c->flags & REDIS_MULTI) { if (c->flags & REDIS_MULTI) {
addReplySds(c,sdsnew("-ERR WATCH inside MULTI is not allowed\r\n")); addReplyError(c,"WATCH inside MULTI is not allowed");
return; return;
} }
for (j = 1; j < c->argc; j++) for (j = 1; j < c->argc; j++)
......
#include "redis.h" #include "redis.h"
#include <sys/uio.h> #include <sys/uio.h>
void *dupClientReplyValue(void *o) { void *dupClientReplyValue(void *o) {
...@@ -12,14 +11,24 @@ int listMatchObjects(void *a, void *b) { ...@@ -12,14 +11,24 @@ int listMatchObjects(void *a, void *b) {
} }
redisClient *createClient(int fd) { redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(*c)); redisClient *c = zmalloc(sizeof(redisClient));
c->bufpos = 0;
anetNonBlock(NULL,fd); anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd); anetTcpNoDelay(NULL,fd);
if (!c) return NULL; if (!c) return NULL;
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
selectDb(c,0); selectDb(c,0);
c->fd = fd; c->fd = fd;
c->querybuf = sdsempty(); c->querybuf = sdsempty();
c->newline = NULL;
c->argc = 0; c->argc = 0;
c->argv = NULL; c->argv = NULL;
c->bulklen = -1; c->bulklen = -1;
...@@ -43,80 +52,254 @@ redisClient *createClient(int fd) { ...@@ -43,80 +52,254 @@ redisClient *createClient(int fd) {
c->pubsub_patterns = listCreate(); c->pubsub_patterns = listCreate();
listSetFreeMethod(c->pubsub_patterns,decrRefCount); listSetFreeMethod(c->pubsub_patterns,decrRefCount);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects); listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
readQueryFromClient, c) == AE_ERR) {
freeClient(c);
return NULL;
}
listAddNodeTail(server.clients,c); listAddNodeTail(server.clients,c);
initClientMultiState(c); initClientMultiState(c);
return c; return c;
} }
void addReply(redisClient *c, robj *obj) { int _installWriteEvent(redisClient *c) {
if (listLength(c->reply) == 0 && if (c->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE || (c->replstate == REDIS_REPL_NONE ||
c->replstate == REDIS_REPL_ONLINE) && c->replstate == REDIS_REPL_ONLINE) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c) == AE_ERR) return; sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
return REDIS_OK;
}
if (server.vm_enabled && obj->storage != REDIS_VM_MEMORY) { /* Create a duplicate of the last object in the reply list when
obj = dupStringObject(obj); * it is not exclusively owned by the reply list. */
obj->refcount = 0; /* getDecodedObject() will increment the refcount */ robj *dupLastObjectIfNeeded(list *reply) {
robj *new, *cur;
listNode *ln;
redisAssert(listLength(reply) > 0);
ln = listLast(reply);
cur = listNodeValue(ln);
if (cur->refcount > 1) {
new = dupStringObject(cur);
decrRefCount(cur);
listNodeValue(ln) = new;
} }
listAddNodeTail(c->reply,getDecodedObject(obj)); return listNodeValue(ln);
} }
void addReplySds(redisClient *c, sds s) { int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
robj *o = createObject(REDIS_STRING,s); size_t available = sizeof(c->buf)-c->bufpos;
addReply(c,o);
decrRefCount(o); /* If there already are entries in the reply list, we cannot
* add anything more to the static buffer. */
if (listLength(c->reply) > 0) return REDIS_ERR;
/* Check that the buffer has enough space available for this string. */
if (len > available) return REDIS_ERR;
memcpy(c->buf+c->bufpos,s,len);
c->bufpos+=len;
return REDIS_OK;
} }
void addReplyDouble(redisClient *c, double d) { void _addReplyObjectToList(redisClient *c, robj *o) {
char buf[128]; robj *tail;
if (listLength(c->reply) == 0) {
incrRefCount(o);
listAddNodeTail(c->reply,o);
} else {
tail = listNodeValue(listLast(c->reply));
snprintf(buf,sizeof(buf),"%.17g",d); /* Append to this object when possible. */
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n%s\r\n", if (tail->ptr != NULL &&
(unsigned long) strlen(buf),buf)); sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
{
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
} else {
incrRefCount(o);
listAddNodeTail(c->reply,o);
}
}
} }
void addReplyLongLong(redisClient *c, long long ll) { /* This method takes responsibility over the sds. When it is no longer
char buf[128]; * needed it will be free'd, otherwise it ends up in a robj. */
size_t len; void _addReplySdsToList(redisClient *c, sds s) {
robj *tail;
if (listLength(c->reply) == 0) {
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
} else {
tail = listNodeValue(listLast(c->reply));
if (ll == 0) { /* Append to this object when possible. */
addReply(c,shared.czero); if (tail->ptr != NULL &&
return; sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES)
} else if (ll == 1) { {
addReply(c,shared.cone); tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,s,sdslen(s));
sdsfree(s);
} else {
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
}
}
}
void _addReplyStringToList(redisClient *c, char *s, size_t len) {
robj *tail;
if (listLength(c->reply) == 0) {
listAddNodeTail(c->reply,createStringObject(s,len));
} else {
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
if (tail->ptr != NULL &&
sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES)
{
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,s,len);
} else {
listAddNodeTail(c->reply,createStringObject(s,len));
}
}
}
void addReply(redisClient *c, robj *obj) {
if (_installWriteEvent(c) != REDIS_OK) return;
redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
/* This is an important place where we can avoid copy-on-write
* when there is a saving child running, avoiding touching the
* refcount field of the object if it's not needed.
*
* If the encoding is RAW and there is room in the static buffer
* we'll be able to send the object to the client without
* messing with its page. */
if (obj->encoding == REDIS_ENCODING_RAW) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
} else {
obj = getDecodedObject(obj);
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
decrRefCount(obj);
}
}
void addReplySds(redisClient *c, sds s) {
if (_installWriteEvent(c) != REDIS_OK) {
/* The caller expects the sds to be free'd. */
sdsfree(s);
return; return;
} }
buf[0] = ':'; if (_addReplyToBuffer(c,s,sdslen(s)) == REDIS_OK) {
sdsfree(s);
} else {
/* This method free's the sds when it is no longer needed. */
_addReplySdsToList(c,s);
}
}
void addReplyString(redisClient *c, char *s, size_t len) {
if (_installWriteEvent(c) != REDIS_OK) return;
if (_addReplyToBuffer(c,s,len) != REDIS_OK)
_addReplyStringToList(c,s,len);
}
void _addReplyError(redisClient *c, char *s, size_t len) {
addReplyString(c,"-ERR ",5);
addReplyString(c,s,len);
addReplyString(c,"\r\n",2);
}
void addReplyError(redisClient *c, char *err) {
_addReplyError(c,err,strlen(err));
}
void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap);
_addReplyError(c,s,sdslen(s));
sdsfree(s);
}
void _addReplyStatus(redisClient *c, char *s, size_t len) {
addReplyString(c,"+",1);
addReplyString(c,s,len);
addReplyString(c,"\r\n",2);
}
void addReplyStatus(redisClient *c, char *status) {
_addReplyStatus(c,status,strlen(status));
}
void addReplyStatusFormat(redisClient *c, const char *fmt, ...) {
va_list ap;
va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap);
_addReplyStatus(c,s,sdslen(s));
sdsfree(s);
}
/* Adds an empty object to the reply list that will contain the multi bulk
* length, which is not known when this function is called. */
void *addDeferredMultiBulkLength(redisClient *c) {
/* Note that we install the write event here even if the object is not
* ready to be sent, since we are sure that before returning to the
* event loop setDeferredMultiBulkLength() will be called. */
if (_installWriteEvent(c) != REDIS_OK) return NULL;
listAddNodeTail(c->reply,createObject(REDIS_STRING,NULL));
return listLast(c->reply);
}
/* Populate the length object and try glueing it to the next chunk. */
void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
listNode *ln = (listNode*)node;
robj *len, *next;
/* Abort when *node is NULL (see addDeferredMultiBulkLength). */
if (node == NULL) return;
len = listNodeValue(ln);
len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length);
if (ln->next != NULL) {
next = listNodeValue(ln->next);
/* Only glue when the next node is non-NULL (an sds in this case) */
if (next->ptr != NULL) {
len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr));
listDelNode(c->reply,ln->next);
}
}
}
void addReplyDouble(redisClient *c, double d) {
char dbuf[128], sbuf[128];
int dlen, slen;
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
addReplyString(c,sbuf,slen);
}
void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
char buf[128];
int len;
buf[0] = prefix;
len = ll2string(buf+1,sizeof(buf)-1,ll); len = ll2string(buf+1,sizeof(buf)-1,ll);
buf[len+1] = '\r'; buf[len+1] = '\r';
buf[len+2] = '\n'; buf[len+2] = '\n';
addReplySds(c,sdsnewlen(buf,len+3)); addReplyString(c,buf,len+3);
} }
void addReplyUlong(redisClient *c, unsigned long ul) { void addReplyLongLong(redisClient *c, long long ll) {
char buf[128]; _addReplyLongLong(c,ll,':');
size_t len; }
if (ul == 0) { void addReplyMultiBulkLen(redisClient *c, long length) {
addReply(c,shared.czero); _addReplyLongLong(c,length,'*');
return;
} else if (ul == 1) {
addReply(c,shared.cone);
return;
}
len = snprintf(buf,sizeof(buf),":%lu\r\n",ul);
addReplySds(c,sdsnewlen(buf,len));
} }
void addReplyBulkLen(redisClient *c, robj *obj) { void addReplyBulkLen(redisClient *c, robj *obj) {
size_t len, intlen; size_t len;
char buf[128];
if (obj->encoding == REDIS_ENCODING_RAW) { if (obj->encoding == REDIS_ENCODING_RAW) {
len = sdslen(obj->ptr); len = sdslen(obj->ptr);
...@@ -133,11 +316,7 @@ void addReplyBulkLen(redisClient *c, robj *obj) { ...@@ -133,11 +316,7 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
len++; len++;
} }
} }
buf[0] = '$'; _addReplyLongLong(c,len,'$');
intlen = ll2string(buf+1,sizeof(buf)-1,(long long)len);
buf[intlen+1] = '\r';
buf[intlen+2] = '\n';
addReplySds(c,sdsnewlen(buf,intlen+3));
} }
void addReplyBulk(redisClient *c, robj *obj) { void addReplyBulk(redisClient *c, robj *obj) {
...@@ -275,7 +454,8 @@ void freeClient(redisClient *c) { ...@@ -275,7 +454,8 @@ void freeClient(redisClient *c) {
server.vm_blocked_clients--; server.vm_blocked_clients--;
} }
listRelease(c->io_keys); listRelease(c->io_keys);
/* Master/slave cleanup */ /* Master/slave cleanup.
* Case 1: we lost the connection with a slave. */
if (c->flags & REDIS_SLAVE) { if (c->flags & REDIS_SLAVE) {
if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
close(c->repldbfd); close(c->repldbfd);
...@@ -284,9 +464,20 @@ void freeClient(redisClient *c) { ...@@ -284,9 +464,20 @@ void freeClient(redisClient *c) {
redisAssert(ln != NULL); redisAssert(ln != NULL);
listDelNode(l,ln); listDelNode(l,ln);
} }
/* Case 2: we lost the connection with the master. */
if (c->flags & REDIS_MASTER) { if (c->flags & REDIS_MASTER) {
server.master = NULL; server.master = NULL;
server.replstate = REDIS_REPL_CONNECT; server.replstate = REDIS_REPL_CONNECT;
/* Since we lost the connection with the master, we should also
* 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);
}
} }
/* Release memory */ /* Release memory */
zfree(c->argv); zfree(c->argv);
...@@ -295,34 +486,6 @@ void freeClient(redisClient *c) { ...@@ -295,34 +486,6 @@ void freeClient(redisClient *c) {
zfree(c); zfree(c);
} }
#define GLUEREPLY_UP_TO (1024)
static void glueReplyBuffersIfNeeded(redisClient *c) {
int copylen = 0;
char buf[GLUEREPLY_UP_TO];
listNode *ln;
listIter li;
robj *o;
listRewind(c->reply,&li);
while((ln = listNext(&li))) {
int objlen;
o = ln->value;
objlen = sdslen(o->ptr);
if (copylen + objlen <= GLUEREPLY_UP_TO) {
memcpy(buf+copylen,o->ptr,objlen);
copylen += objlen;
listDelNode(c->reply,ln);
} else {
if (copylen == 0) return;
break;
}
}
/* Now the output buffer is empty, add the new single element */
o = createObject(REDIS_STRING,sdsnewlen(buf,copylen));
listAddNodeHead(c->reply,o);
}
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = privdata; redisClient *c = privdata;
int nwritten = 0, totwritten = 0, objlen; int nwritten = 0, totwritten = 0, objlen;
...@@ -339,31 +502,48 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -339,31 +502,48 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
} }
while(listLength(c->reply)) { while(c->bufpos > 0 || listLength(c->reply)) {
if (server.glueoutputbuf && listLength(c->reply) > 1) if (c->bufpos > 0) {
glueReplyBuffersIfNeeded(c); if (c->flags & REDIS_MASTER) {
/* Don't reply to a master */
nwritten = c->bufpos - c->sentlen;
} else {
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
if (nwritten <= 0) break;
}
c->sentlen += nwritten;
totwritten += nwritten;
/* If the buffer was sent, set bufpos to zero to continue with
* the remainder of the reply. */
if (c->sentlen == c->bufpos) {
c->bufpos = 0;
c->sentlen = 0;
}
} else {
o = listNodeValue(listFirst(c->reply));
objlen = sdslen(o->ptr);
o = listNodeValue(listFirst(c->reply)); if (objlen == 0) {
objlen = sdslen(o->ptr); listDelNode(c->reply,listFirst(c->reply));
continue;
}
if (objlen == 0) { if (c->flags & REDIS_MASTER) {
listDelNode(c->reply,listFirst(c->reply)); /* Don't reply to a master */
continue; nwritten = objlen - c->sentlen;
} } else {
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
if (nwritten <= 0) break;
}
c->sentlen += nwritten;
totwritten += nwritten;
if (c->flags & REDIS_MASTER) { /* If we fully sent the object on head go to the next one */
/* Don't reply to a master */ if (c->sentlen == objlen) {
nwritten = objlen - c->sentlen; listDelNode(c->reply,listFirst(c->reply));
} else { c->sentlen = 0;
nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen); }
if (nwritten <= 0) break;
}
c->sentlen += nwritten;
totwritten += nwritten;
/* If we fully sent the object on head go to the next one */
if (c->sentlen == objlen) {
listDelNode(c->reply,listFirst(c->reply));
c->sentlen = 0;
} }
/* Note that we avoid to send more thank REDIS_MAX_WRITE_PER_EVENT /* Note that we avoid to send more thank REDIS_MAX_WRITE_PER_EVENT
* bytes, in a single threaded server it's a good idea to serve * bytes, in a single threaded server it's a good idea to serve
...@@ -472,6 +652,7 @@ void resetClient(redisClient *c) { ...@@ -472,6 +652,7 @@ void resetClient(redisClient *c) {
freeClientArgv(c); freeClientArgv(c);
c->bulklen = -1; c->bulklen = -1;
c->multibulk = 0; c->multibulk = 0;
c->newline = NULL;
} }
void closeTimedoutClients(void) { void closeTimedoutClients(void) {
...@@ -486,6 +667,7 @@ void closeTimedoutClients(void) { ...@@ -486,6 +667,7 @@ void closeTimedoutClients(void) {
if (server.maxidletime && if (server.maxidletime &&
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
!(c->flags & REDIS_MASTER) && /* no timeout for masters */ !(c->flags & REDIS_MASTER) && /* no timeout for masters */
!(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */
dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */
listLength(c->pubsub_patterns) == 0 && listLength(c->pubsub_patterns) == 0 &&
(now - c->lastinteraction > server.maxidletime)) (now - c->lastinteraction > server.maxidletime))
...@@ -502,6 +684,8 @@ void closeTimedoutClients(void) { ...@@ -502,6 +684,8 @@ void closeTimedoutClients(void) {
} }
void processInputBuffer(redisClient *c) { void processInputBuffer(redisClient *c) {
int seeknewline = 0;
again: again:
/* Before to process the input buffer, make sure the client is not /* Before to process the input buffer, make sure the client is not
* waitig for a blocking operation such as BLPOP. Note that the first * waitig for a blocking operation such as BLPOP. Note that the first
...@@ -510,15 +694,19 @@ again: ...@@ -510,15 +694,19 @@ again:
* in the input buffer the client may be blocked, and the "goto again" * in the input buffer the client may be blocked, and the "goto again"
* will try to reiterate. The following line will make it return asap. */ * will try to reiterate. The following line will make it return asap. */
if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return;
if (seeknewline && c->bulklen == -1) c->newline = strchr(c->querybuf,'\n');
seeknewline = 1;
if (c->bulklen == -1) { if (c->bulklen == -1) {
/* Read the first line of the query */ /* Read the first line of the query */
char *p = strchr(c->querybuf,'\n');
size_t querylen; size_t querylen;
if (p) { if (c->newline) {
char *p = c->newline;
sds query, *argv; sds query, *argv;
int argc, j; int argc, j;
c->newline = NULL;
query = c->querybuf; query = c->querybuf;
c->querybuf = sdsempty(); c->querybuf = sdsempty();
querylen = 1+(p-(query)); querylen = 1+(p-(query));
...@@ -605,8 +793,14 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -605,8 +793,14 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
} }
if (nread) { if (nread) {
size_t oldlen = sdslen(c->querybuf);
c->querybuf = sdscatlen(c->querybuf, buf, nread); c->querybuf = sdscatlen(c->querybuf, buf, nread);
c->lastinteraction = time(NULL); c->lastinteraction = time(NULL);
/* Scan this new piece of the query for the newline. We do this
* here in order to make sure we perform this scan just one time
* per piece of buffer, leading to an O(N) scan instead of O(N*N) */
if (c->bulklen == -1 && c->newline == NULL)
c->newline = strchr(c->querybuf+oldlen,'\n');
} else { } else {
return; return;
} }
......
...@@ -74,7 +74,16 @@ robj *createZiplistObject(void) { ...@@ -74,7 +74,16 @@ robj *createZiplistObject(void) {
robj *createSetObject(void) { robj *createSetObject(void) {
dict *d = dictCreate(&setDictType,NULL); dict *d = dictCreate(&setDictType,NULL);
return createObject(REDIS_SET,d); robj *o = createObject(REDIS_SET,d);
o->encoding = REDIS_ENCODING_HT;
return o;
}
robj *createIntsetObject(void) {
intset *is = intsetNew();
robj *o = createObject(REDIS_SET,is);
o->encoding = REDIS_ENCODING_INTSET;
return o;
} }
robj *createHashObject(void) { robj *createHashObject(void) {
...@@ -115,7 +124,16 @@ void freeListObject(robj *o) { ...@@ -115,7 +124,16 @@ void freeListObject(robj *o) {
} }
void freeSetObject(robj *o) { void freeSetObject(robj *o) {
dictRelease((dict*) o->ptr); switch (o->encoding) {
case REDIS_ENCODING_HT:
dictRelease((dict*) o->ptr);
break;
case REDIS_ENCODING_INTSET:
zfree(o->ptr);
break;
default:
redisPanic("Unknown set encoding type");
}
} }
void freeZsetObject(robj *o) { void freeZsetObject(robj *o) {
...@@ -336,9 +354,9 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch ...@@ -336,9 +354,9 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch
double value; double value;
if (getDoubleFromObject(o, &value) != REDIS_OK) { if (getDoubleFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) { if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); addReplyError(c,(char*)msg);
} else { } else {
addReplySds(c, sdsnew("-ERR value is not a double\r\n")); addReplyError(c,"value is not a double");
} }
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -358,6 +376,8 @@ int getLongLongFromObject(robj *o, long long *target) { ...@@ -358,6 +376,8 @@ int getLongLongFromObject(robj *o, long long *target) {
if (o->encoding == REDIS_ENCODING_RAW) { if (o->encoding == REDIS_ENCODING_RAW) {
value = strtoll(o->ptr, &eptr, 10); value = strtoll(o->ptr, &eptr, 10);
if (eptr[0] != '\0') return REDIS_ERR; if (eptr[0] != '\0') return REDIS_ERR;
if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX))
return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) { } else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr; value = (long)o->ptr;
} else { } else {
...@@ -365,7 +385,7 @@ int getLongLongFromObject(robj *o, long long *target) { ...@@ -365,7 +385,7 @@ int getLongLongFromObject(robj *o, long long *target) {
} }
} }
*target = value; if (target) *target = value;
return REDIS_OK; return REDIS_OK;
} }
...@@ -373,9 +393,9 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con ...@@ -373,9 +393,9 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con
long long value; long long value;
if (getLongLongFromObject(o, &value) != REDIS_OK) { if (getLongLongFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) { if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); addReplyError(c,(char*)msg);
} else { } else {
addReplySds(c, sdsnew("-ERR value is not an integer\r\n")); addReplyError(c,"value is not an integer or out of range");
} }
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -390,9 +410,9 @@ int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char * ...@@ -390,9 +410,9 @@ int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *
if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR; if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
if (value < LONG_MIN || value > LONG_MAX) { if (value < LONG_MIN || value > LONG_MAX) {
if (msg != NULL) { if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg)); addReplyError(c,(char*)msg);
} else { } else {
addReplySds(c, sdsnew("-ERR value is out of range\r\n")); addReplyError(c,"value is out of range");
} }
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -409,6 +429,7 @@ char *strEncoding(int encoding) { ...@@ -409,6 +429,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_ZIPMAP: return "zipmap"; case REDIS_ENCODING_ZIPMAP: return "zipmap";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist"; case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_ZIPLIST: return "ziplist"; case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset";
default: return "unknown"; default: return "unknown";
} }
} }
...@@ -260,17 +260,29 @@ int rdbSaveObject(FILE *fp, robj *o) { ...@@ -260,17 +260,29 @@ int rdbSaveObject(FILE *fp, robj *o) {
} }
} else if (o->type == REDIS_SET) { } else if (o->type == REDIS_SET) {
/* Save a set value */ /* Save a set value */
dict *set = o->ptr; if (o->encoding == REDIS_ENCODING_HT) {
dictIterator *di = dictGetIterator(set); dict *set = o->ptr;
dictEntry *de; dictIterator *di = dictGetIterator(set);
dictEntry *de;
if (rdbSaveLen(fp,dictSize(set)) == -1) return -1;
while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetEntryKey(de);
if (rdbSaveStringObject(fp,eleobj) == -1) return -1; if (rdbSaveLen(fp,dictSize(set)) == -1) return -1;
while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetEntryKey(de);
if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
}
dictReleaseIterator(di);
} else if (o->encoding == REDIS_ENCODING_INTSET) {
intset *is = o->ptr;
int64_t llval;
int i = 0;
if (rdbSaveLen(fp,intsetLen(is)) == -1) return -1;
while(intsetGet(is,i++,&llval)) {
if (rdbSaveLongLongAsStringObject(fp,llval) == -1) return -1;
}
} else {
redisPanic("Unknown set encoding");
} }
dictReleaseIterator(di);
} else if (o->type == REDIS_ZSET) { } else if (o->type == REDIS_ZSET) {
/* Save a set value */ /* Save a set value */
zset *zs = o->ptr; zset *zs = o->ptr;
...@@ -445,6 +457,7 @@ int rdbSaveBackground(char *filename) { ...@@ -445,6 +457,7 @@ int rdbSaveBackground(char *filename) {
if (server.bgsavechildpid != -1) return REDIS_ERR; if (server.bgsavechildpid != -1) return REDIS_ERR;
if (server.vm_enabled) waitEmptyIOJobsQueue(); if (server.vm_enabled) waitEmptyIOJobsQueue();
server.dirty_before_bgsave = server.dirty;
if ((childpid = fork()) == 0) { if ((childpid = fork()) == 0) {
/* Child */ /* Child */
if (server.vm_enabled) vmReopenSwapFile(); if (server.vm_enabled) vmReopenSwapFile();
...@@ -629,6 +642,7 @@ int rdbLoadDoubleValue(FILE *fp, double *val) { ...@@ -629,6 +642,7 @@ int rdbLoadDoubleValue(FILE *fp, double *val) {
robj *rdbLoadObject(int type, FILE *fp) { robj *rdbLoadObject(int type, FILE *fp) {
robj *o, *ele, *dec; robj *o, *ele, *dec;
size_t len; size_t len;
unsigned int i;
redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp)); redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp));
if (type == REDIS_STRING) { if (type == REDIS_STRING) {
...@@ -670,16 +684,41 @@ robj *rdbLoadObject(int type, FILE *fp) { ...@@ -670,16 +684,41 @@ robj *rdbLoadObject(int type, FILE *fp) {
} else if (type == REDIS_SET) { } else if (type == REDIS_SET) {
/* Read list/set value */ /* Read list/set value */
if ((len = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL; if ((len = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL;
o = createSetObject();
/* It's faster to expand the dict to the right size asap in order /* Use a regular set when there are too many entries. */
* to avoid rehashing */ if (len > server.set_max_intset_entries) {
if (len > DICT_HT_INITIAL_SIZE) o = createSetObject();
dictExpand(o->ptr,len); /* It's faster to expand the dict to the right size asap in order
* to avoid rehashing */
if (len > DICT_HT_INITIAL_SIZE)
dictExpand(o->ptr,len);
} else {
o = createIntsetObject();
}
/* Load every single element of the list/set */ /* Load every single element of the list/set */
while(len--) { for (i = 0; i < len; i++) {
long long llval;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL; if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele); ele = tryObjectEncoding(ele);
dictAdd((dict*)o->ptr,ele,NULL);
if (o->encoding == REDIS_ENCODING_INTSET) {
/* Fetch integer value from element */
if (isObjectRepresentableAsLongLong(ele,&llval) == REDIS_OK) {
o->ptr = intsetAdd(o->ptr,llval,NULL);
} else {
setTypeConvert(o,REDIS_ENCODING_HT);
dictExpand(o->ptr,len);
}
}
/* This will also be called when the set was just converted
* to regular hashtable encoded set */
if (o->encoding == REDIS_ENCODING_HT) {
dictAdd((dict*)o->ptr,ele,NULL);
} else {
decrRefCount(ele);
}
} }
} else if (type == REDIS_ZSET) { } else if (type == REDIS_ZSET) {
/* Read list/set value */ /* Read list/set value */
...@@ -692,13 +731,14 @@ robj *rdbLoadObject(int type, FILE *fp) { ...@@ -692,13 +731,14 @@ robj *rdbLoadObject(int type, FILE *fp) {
/* Load every single element of the list/set */ /* Load every single element of the list/set */
while(zsetlen--) { while(zsetlen--) {
robj *ele; robj *ele;
double *score = zmalloc(sizeof(double)); double score;
zskiplistNode *znode;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL; if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele); ele = tryObjectEncoding(ele);
if (rdbLoadDoubleValue(fp,score) == -1) return NULL; if (rdbLoadDoubleValue(fp,&score) == -1) return NULL;
dictAdd(zs->dict,ele,score); znode = zslInsert(zs->zsl,score,ele);
zslInsert(zs->zsl,*score,ele); dictAdd(zs->dict,ele,&znode->score);
incrRefCount(ele); /* added to skiplist */ incrRefCount(ele); /* added to skiplist */
} }
} else if (type == REDIS_HASH) { } else if (type == REDIS_HASH) {
...@@ -876,7 +916,7 @@ void backgroundSaveDoneHandler(int statloc) { ...@@ -876,7 +916,7 @@ void backgroundSaveDoneHandler(int statloc) {
if (!bysignal && exitcode == 0) { if (!bysignal && exitcode == 0) {
redisLog(REDIS_NOTICE, redisLog(REDIS_NOTICE,
"Background saving terminated with success"); "Background saving terminated with success");
server.dirty = 0; server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL); server.lastsave = time(NULL);
} else if (!bysignal && exitcode != 0) { } else if (!bysignal && exitcode != 0) {
redisLog(REDIS_WARNING, "Background saving error"); redisLog(REDIS_WARNING, "Background saving error");
......
...@@ -76,6 +76,7 @@ static struct config { ...@@ -76,6 +76,7 @@ static struct config {
long long start; long long start;
long long totlatency; long long totlatency;
int *latency; int *latency;
char *title;
list *clients; list *clients;
int quiet; int quiet;
int loop; int loop;
...@@ -207,16 +208,27 @@ static void clientDone(client c) { ...@@ -207,16 +208,27 @@ static void clientDone(client c) {
} }
} }
/* Read a length from the buffer pointed to by *p, store the length in *len,
* and return the number of bytes that the cursor advanced. */
static int readLen(char *p, int *len) {
char *tail = strstr(p,"\r\n");
if (tail == NULL)
return 0;
*tail = '\0';
*len = atoi(p+1);
return tail+2-p;
}
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{ {
char buf[1024]; char buf[1024], *p;
int nread; int nread, pos=0, len=0;
client c = privdata; client c = privdata;
REDIS_NOTUSED(el); REDIS_NOTUSED(el);
REDIS_NOTUSED(fd); REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask); REDIS_NOTUSED(mask);
nread = read(c->fd, buf, 1024); nread = read(c->fd,buf,sizeof(buf));
if (nread == -1) { if (nread == -1) {
fprintf(stderr, "Reading from socket: %s\n", strerror(errno)); fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
freeClient(c); freeClient(c);
...@@ -229,82 +241,89 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) ...@@ -229,82 +241,89 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
} }
c->totreceived += nread; c->totreceived += nread;
c->ibuf = sdscatlen(c->ibuf,buf,nread); c->ibuf = sdscatlen(c->ibuf,buf,nread);
len = sdslen(c->ibuf);
processdata:
/* Are we waiting for the first line of the command of for sdf
* count in bulk or multi bulk operations? */
if (c->replytype == REPLY_INT || if (c->replytype == REPLY_INT ||
c->replytype == REPLY_RETCODE || c->replytype == REPLY_RETCODE)
(c->replytype == REPLY_BULK && c->readlen == -1) || {
(c->replytype == REPLY_MBULK && c->readlen == -1) || /* Check if the first line is complete. This is everything we need
(c->replytype == REPLY_MBULK && c->mbulk == -1)) { * when waiting for an integer or status code reply.*/
char *p; if ((p = strstr(c->ibuf,"\r\n")) != NULL)
goto done;
/* Check if the first line is complete. This is only true if } else if (c->replytype == REPLY_BULK) {
* there is a newline inside the buffer. */ int advance = 0;
if ((p = strchr(c->ibuf,'\n')) != NULL) { if (c->readlen < 0) {
if (c->replytype == REPLY_BULK || advance = readLen(c->ibuf+pos,&c->readlen);
(c->replytype == REPLY_MBULK && c->mbulk != -1)) if (advance) {
{ pos += advance;
/* Read the count of a bulk reply (being it a single bulk or if (c->readlen == -1) {
* a multi bulk reply). "$<count>" for the protocol spec. */ goto done;
*p = '\0'; } else {
*(p-1) = '\0'; /* include the trailing \r\n */
c->readlen = atoi(c->ibuf+1)+2; c->readlen += 2;
// printf("BULK ATOI: %s\n", c->ibuf+1);
/* Handle null bulk reply "$-1" */
if (c->readlen-2 == -1) {
clientDone(c);
return;
}
/* Leave all the rest in the input buffer */
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
/* fall through to reach the point where the code will try
* to check if the bulk reply is complete. */
} else if (c->replytype == REPLY_MBULK && c->mbulk == -1) {
/* Read the count of a multi bulk reply. That is, how many
* bulk replies we have to read next. "*<count>" protocol. */
*p = '\0';
*(p-1) = '\0';
c->mbulk = atoi(c->ibuf+1);
/* Handle null bulk reply "*-1" */
if (c->mbulk == -1) {
clientDone(c);
return;
} }
// printf("%p) %d elements list\n", c, c->mbulk);
/* Leave all the rest in the input buffer */
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
goto processdata;
} else { } else {
c->ibuf = sdstrim(c->ibuf,"\r\n"); goto skip;
clientDone(c);
return;
} }
} }
}
/* bulk read, did we read everything? */ int canconsume;
if (((c->replytype == REPLY_MBULK && c->mbulk != -1) || if (c->readlen > 0) {
(c->replytype == REPLY_BULK)) && c->readlen != -1 && canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
(unsigned)c->readlen <= sdslen(c->ibuf)) c->readlen -= canconsume;
{ pos += canconsume;
// printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n", }
// c->mbulk,c->readlen,sdslen(c->ibuf));
if (c->replytype == REPLY_BULK) { if (c->readlen == 0)
clientDone(c); goto done;
} else if (c->replytype == REPLY_MBULK) { } else if (c->replytype == REPLY_MBULK) {
// printf("%p) %d (%d)) ",c, c->mbulk, c->readlen); int advance = 0;
// fwrite(c->ibuf,c->readlen,1,stdout); if (c->mbulk == -1) {
// printf("\n"); advance = readLen(c->ibuf+pos,&c->mbulk);
if (--c->mbulk == 0) { if (advance) {
clientDone(c); pos += advance;
if (c->mbulk == -1)
goto done;
} else {
goto skip;
}
}
int canconsume;
while(c->mbulk > 0 && pos < len) {
if (c->readlen > 0) {
canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
c->readlen -= canconsume;
pos += canconsume;
if (c->readlen == 0)
c->mbulk--;
} else { } else {
c->ibuf = sdsrange(c->ibuf,c->readlen,-1); advance = readLen(c->ibuf+pos,&c->readlen);
c->readlen = -1; if (advance) {
goto processdata; pos += advance;
if (c->readlen == -1) {
c->mbulk--;
continue;
} else {
/* include the trailing \r\n */
c->readlen += 2;
}
} else {
goto skip;
}
} }
} }
if (c->mbulk == 0)
goto done;
} }
skip:
c->ibuf = sdsrange(c->ibuf,pos,-1);
return;
done:
clientDone(c);
return;
} }
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
...@@ -376,13 +395,13 @@ static void createMissingClients(client c) { ...@@ -376,13 +395,13 @@ static void createMissingClients(client c) {
} }
} }
static void showLatencyReport(char *title) { static void showLatencyReport(void) {
int j, seen = 0; int j, seen = 0;
float perc, reqpersec; float perc, reqpersec;
reqpersec = (float)config.donerequests/((float)config.totlatency/1000); reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
if (!config.quiet) { if (!config.quiet) {
printf("====== %s ======\n", title); printf("====== %s ======\n", config.title);
printf(" %d requests completed in %.2f seconds\n", config.donerequests, printf(" %d requests completed in %.2f seconds\n", config.donerequests,
(float)config.totlatency/1000); (float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients); printf(" %d parallel clients\n", config.numclients);
...@@ -398,20 +417,20 @@ static void showLatencyReport(char *title) { ...@@ -398,20 +417,20 @@ static void showLatencyReport(char *title) {
} }
printf("%.2f requests per second\n\n", reqpersec); printf("%.2f requests per second\n\n", reqpersec);
} else { } else {
printf("%s: %.2f requests per second\n", title, reqpersec); printf("%s: %.2f requests per second\n", config.title, reqpersec);
} }
} }
static void prepareForBenchmark(void) static void prepareForBenchmark(char *title) {
{
memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1)); memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
config.title = title;
config.start = mstime(); config.start = mstime();
config.donerequests = 0; config.donerequests = 0;
} }
static void endBenchmark(char *title) { static void endBenchmark(void) {
config.totlatency = mstime()-config.start; config.totlatency = mstime()-config.start;
showLatencyReport(title); showLatencyReport();
freeAllClients(); freeAllClients();
} }
...@@ -489,6 +508,18 @@ void parseOptions(int argc, char **argv) { ...@@ -489,6 +508,18 @@ void parseOptions(int argc, char **argv) {
} }
} }
int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
REDIS_NOTUSED(eventLoop);
REDIS_NOTUSED(id);
REDIS_NOTUSED(clientData);
float dt = (float)(mstime()-config.start)/1000.0;
float rps = (float)config.donerequests/dt;
printf("%s: %.2f\r", config.title, rps);
fflush(stdout);
return 250; /* every 250ms */
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
client c; client c;
...@@ -500,6 +531,7 @@ int main(int argc, char **argv) { ...@@ -500,6 +531,7 @@ int main(int argc, char **argv) {
config.requests = 10000; config.requests = 10000;
config.liveclients = 0; config.liveclients = 0;
config.el = aeCreateEventLoop(); config.el = aeCreateEventLoop();
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
config.keepalive = 1; config.keepalive = 1;
config.donerequests = 0; config.donerequests = 0;
config.datasize = 3; config.datasize = 3;
...@@ -524,7 +556,7 @@ int main(int argc, char **argv) { ...@@ -524,7 +556,7 @@ int main(int argc, char **argv) {
if (config.idlemode) { if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients); printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
prepareForBenchmark(); prepareForBenchmark("IDLE");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdsempty(); c->obuf = sdsempty();
...@@ -535,25 +567,25 @@ int main(int argc, char **argv) { ...@@ -535,25 +567,25 @@ int main(int argc, char **argv) {
} }
do { do {
prepareForBenchmark(); prepareForBenchmark("PING");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"PING\r\n"); c->obuf = sdscat(c->obuf,"PING\r\n");
prepareClientForReply(c,REPLY_RETCODE); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("PING"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("PING (multi bulk)");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n"); c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
prepareClientForReply(c,REPLY_RETCODE); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("PING (multi bulk)"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("SET");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize); c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
...@@ -567,106 +599,106 @@ int main(int argc, char **argv) { ...@@ -567,106 +599,106 @@ int main(int argc, char **argv) {
prepareClientForReply(c,REPLY_RETCODE); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("SET"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("GET");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n"); c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
prepareClientForReply(c,REPLY_BULK); prepareClientForReply(c,REPLY_BULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("GET"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("INCR");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n"); c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_INT); prepareClientForReply(c,REPLY_INT);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("INCR"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LPUSH");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n"); c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_INT); prepareClientForReply(c,REPLY_INT);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LPUSH"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LPOP");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n"); c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
prepareClientForReply(c,REPLY_BULK); prepareClientForReply(c,REPLY_BULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LPOP"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("SADD");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n"); c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_RETCODE); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("SADD"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("SPOP");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SPOP myset\r\n"); c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
prepareClientForReply(c,REPLY_BULK); prepareClientForReply(c,REPLY_BULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("SPOP"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n"); c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_RETCODE); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LPUSH (again, in order to bench LRANGE)"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LRANGE (first 100 elements)");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n"); c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
prepareClientForReply(c,REPLY_MBULK); prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LRANGE (first 100 elements)"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LRANGE (first 300 elements)");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n"); c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
prepareClientForReply(c,REPLY_MBULK); prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LRANGE (first 300 elements)"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LRANGE (first 450 elements)");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n"); c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
prepareClientForReply(c,REPLY_MBULK); prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LRANGE (first 450 elements)"); endBenchmark();
prepareForBenchmark(); prepareForBenchmark("LRANGE (first 600 elements)");
c = createClient(); c = createClient();
if (!c) exit(1); if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n"); c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
prepareClientForReply(c,REPLY_MBULK); prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
endBenchmark("LRANGE (first 600 elements)"); endBenchmark();
printf("\n"); printf("\n");
} while(config.loop); } while(config.loop);
......
...@@ -65,8 +65,8 @@ ...@@ -65,8 +65,8 @@
/* data type to hold offset in file and size */ /* data type to hold offset in file and size */
typedef struct { typedef struct {
void *data; void *data;
unsigned long size; size_t size;
unsigned long offset; size_t offset;
} pos; } pos;
static unsigned char level = 0; static unsigned char level = 0;
...@@ -77,8 +77,8 @@ static pos positions[16]; ...@@ -77,8 +77,8 @@ static pos positions[16];
/* Hold a stack of errors */ /* Hold a stack of errors */
typedef struct { typedef struct {
char error[16][1024]; char error[16][1024];
unsigned long offset[16]; size_t offset[16];
unsigned int level; size_t level;
} errors_t; } errors_t;
static errors_t errors; static errors_t errors;
...@@ -112,7 +112,7 @@ int readBytes(void *target, long num) { ...@@ -112,7 +112,7 @@ int readBytes(void *target, long num) {
if (p.offset + num > p.size) { if (p.offset + num > p.size) {
return 0; return 0;
} else { } else {
memcpy(target, (void*)((unsigned long)p.data + p.offset), num); memcpy(target, (void*)((size_t)p.data + p.offset), num);
if (!peek) positions[level].offset += num; if (!peek) positions[level].offset += num;
} }
return 1; return 1;
...@@ -494,15 +494,17 @@ void printCentered(int indent, int width, char* body) { ...@@ -494,15 +494,17 @@ void printCentered(int indent, int width, char* body) {
printf("%s %s %s\n", head, body, tail); printf("%s %s %s\n", head, body, tail);
} }
void printValid(int ops, int bytes) { void printValid(uint64_t ops, uint64_t bytes) {
char body[80]; char body[80];
sprintf(body, "Processed %d valid opcodes (in %d bytes)", ops, bytes); sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
(unsigned long long) ops, (unsigned long long) bytes);
printCentered(4, 80, body); printCentered(4, 80, body);
} }
void printSkipped(int bytes, int offset) { void printSkipped(uint64_t bytes, uint64_t offset) {
char body[80]; char body[80];
sprintf(body, "Skipped %d bytes (resuming at 0x%08x)", bytes, offset); sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
(unsigned long long) bytes, (unsigned long long) offset);
printCentered(4, 80, body); printCentered(4, 80, body);
} }
...@@ -541,7 +543,7 @@ void printErrorStack(entry *e) { ...@@ -541,7 +543,7 @@ void printErrorStack(entry *e) {
} }
void process() { void process() {
int i, num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0; uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
entry entry; entry entry;
processHeader(); processHeader();
...@@ -558,7 +560,9 @@ void process() { ...@@ -558,7 +560,9 @@ void process() {
num_valid_bytes = 0; num_valid_bytes = 0;
/* search for next valid entry */ /* search for next valid entry */
unsigned long offset = positions[0].offset + 1; uint64_t offset = positions[0].offset + 1;
int i = 0;
while (!entry.success && offset < positions[0].size) { while (!entry.success && offset < positions[0].size) {
positions[1].offset = offset; positions[1].offset = offset;
...@@ -606,9 +610,10 @@ void process() { ...@@ -606,9 +610,10 @@ void process() {
} }
/* print summary on errors */ /* print summary on errors */
if (num_errors > 0) { if (num_errors) {
printf("\n"); printf("\n");
printf("Total unprocessable opcodes: %d\n", num_errors); printf("Total unprocessable opcodes: %llu\n",
(unsigned long long) num_errors);
} }
} }
...@@ -620,7 +625,7 @@ int main(int argc, char **argv) { ...@@ -620,7 +625,7 @@ int main(int argc, char **argv) {
} }
int fd; int fd;
unsigned long size; off_t size;
struct stat stat; struct stat stat;
void *data; void *data;
...@@ -634,6 +639,10 @@ int main(int argc, char **argv) { ...@@ -634,6 +639,10 @@ int main(int argc, char **argv) {
size = stat.st_size; size = stat.st_size;
} }
if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) {
ERROR("Cannot check dump files >2GB on a 32-bit platform\n");
}
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) { if (data == MAP_FAILED) {
ERROR("Cannot mmap: %s\n", argv[1]); ERROR("Cannot mmap: %s\n", argv[1]);
......
...@@ -36,6 +36,8 @@ ...@@ -36,6 +36,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include "anet.h" #include "anet.h"
#include "sds.h" #include "sds.h"
...@@ -55,12 +57,14 @@ static struct config { ...@@ -55,12 +57,14 @@ static struct config {
char *hostsocket; char *hostsocket;
long repeat; long repeat;
int dbnum; int dbnum;
int argn_from_stdin;
int interactive; int interactive;
int shutdown; int shutdown;
int monitor_mode; int monitor_mode;
int pubsub_mode; int pubsub_mode;
int raw_output; int raw_output; /* output mode per command */
int tty; /* flag for default output format */
int stdinarg; /* get last arg from stdin. (-x option) */
char mb_sep;
char *auth; char *auth;
char *historyfile; char *historyfile;
} config; } config;
...@@ -68,19 +72,18 @@ static struct config { ...@@ -68,19 +72,18 @@ static struct config {
static int cliReadReply(int fd); static int cliReadReply(int fd);
static void usage(); static void usage();
static int cliConnect(void) { /* Connect to the client. If force is not zero the connection is performed
* even if there is already a connected socket. */
static int cliConnect(int force) {
char err[ANET_ERR_LEN]; char err[ANET_ERR_LEN];
static int fd = ANET_ERR; static int fd = ANET_ERR;
if (fd == ANET_ERR) { if (fd == ANET_ERR || force) {
if (force) close(fd);
if (config.hostsocket == NULL) { if (config.hostsocket == NULL) {
fd = anetTcpConnect(err,config.hostip,config.hostport); fd = anetTcpConnect(err,config.hostip,config.hostport);
} else { } else {
fd = anetUnixConnect(err,config.hostsocket); fd = anetUnixConnect(err,config.hostsocket);
if (fd == ANET_ERR) {
fprintf(stderr, "Could not connect to Redis at %s: %s", config.hostsocket, err);
return -1;
}
} }
if (fd == ANET_ERR) { if (fd == ANET_ERR) {
fprintf(stderr,"Could not connect to Redis at "); fprintf(stderr,"Could not connect to Redis at ");
...@@ -103,7 +106,7 @@ static sds cliReadLine(int fd) { ...@@ -103,7 +106,7 @@ static sds cliReadLine(int fd) {
ssize_t ret; ssize_t ret;
ret = read(fd,&c,1); ret = read(fd,&c,1);
if (ret == -1) { if (ret <= 0) {
sdsfree(line); sdsfree(line);
return NULL; return NULL;
} else if ((ret == 0) || (c == '\n')) { } else if ((ret == 0) || (c == '\n')) {
...@@ -120,7 +123,7 @@ static int cliReadSingleLineReply(int fd, int quiet) { ...@@ -120,7 +123,7 @@ static int cliReadSingleLineReply(int fd, int quiet) {
if (reply == NULL) return 1; if (reply == NULL) return 1;
if (!quiet) if (!quiet)
printf("%s\n", reply); printf("%s", reply);
sdsfree(reply); sdsfree(reply);
return 0; return 0;
} }
...@@ -147,7 +150,7 @@ static void printStringRepr(char *s, int len) { ...@@ -147,7 +150,7 @@ static void printStringRepr(char *s, int len) {
} }
s++; s++;
} }
printf("\"\n"); printf("\"");
} }
static int cliReadBulkReply(int fd) { static int cliReadBulkReply(int fd) {
...@@ -165,7 +168,7 @@ static int cliReadBulkReply(int fd) { ...@@ -165,7 +168,7 @@ static int cliReadBulkReply(int fd) {
reply = zmalloc(bulklen); reply = zmalloc(bulklen);
anetRead(fd,reply,bulklen); anetRead(fd,reply,bulklen);
anetRead(fd,crlf,2); anetRead(fd,crlf,2);
if (config.raw_output || !isatty(fileno(stdout))) { if (config.raw_output || !config.tty) {
if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) { if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
zfree(reply); zfree(reply);
return 1; return 1;
...@@ -182,6 +185,7 @@ static int cliReadBulkReply(int fd) { ...@@ -182,6 +185,7 @@ static int cliReadBulkReply(int fd) {
static int cliReadMultiBulkReply(int fd) { static int cliReadMultiBulkReply(int fd) {
sds replylen = cliReadLine(fd); sds replylen = cliReadLine(fd);
int elements, c = 1; int elements, c = 1;
int retval = 0;
if (replylen == NULL) return 1; if (replylen == NULL) return 1;
elements = atoi(replylen); elements = atoi(replylen);
...@@ -194,36 +198,45 @@ static int cliReadMultiBulkReply(int fd) { ...@@ -194,36 +198,45 @@ static int cliReadMultiBulkReply(int fd) {
printf("(empty list or set)\n"); printf("(empty list or set)\n");
} }
while(elements--) { while(elements--) {
printf("%d. ", c); if (config.tty) printf("%d. ", c);
if (cliReadReply(fd)) return 1; if (cliReadReply(fd)) retval = 1;
if (elements) printf("%c",config.mb_sep);
c++; c++;
} }
return 0; return retval;
} }
static int cliReadReply(int fd) { static int cliReadReply(int fd) {
char type; char type;
int nread;
if (anetRead(fd,&type,1) <= 0) { if ((nread = anetRead(fd,&type,1)) <= 0) {
if (config.shutdown) return 0; if (config.shutdown) return 0;
exit(1); if (config.interactive &&
(nread == 0 || (nread == -1 && errno == ECONNRESET)))
{
return ECONNRESET;
} else {
printf("I/O error while reading from socket: %s",strerror(errno));
exit(1);
}
} }
switch(type) { switch(type) {
case '-': case '-':
printf("(error) "); if (config.tty) printf("(error) ");
cliReadSingleLineReply(fd,0); cliReadSingleLineReply(fd,0);
return 1; return 1;
case '+': case '+':
return cliReadSingleLineReply(fd,0); return cliReadSingleLineReply(fd,0);
case ':': case ':':
printf("(integer) "); if (config.tty) printf("(integer) ");
return cliReadSingleLineReply(fd,0); return cliReadSingleLineReply(fd,0);
case '$': case '$':
return cliReadBulkReply(fd); return cliReadBulkReply(fd);
case '*': case '*':
return cliReadMultiBulkReply(fd); return cliReadMultiBulkReply(fd);
default: default:
printf("protocol error, got '%c' as reply type byte\n", type); printf("protocol error, got '%c' as reply type byte", type);
return 1; return 1;
} }
} }
...@@ -248,17 +261,37 @@ static int selectDb(int fd) { ...@@ -248,17 +261,37 @@ static int selectDb(int fd) {
return 0; return 0;
} }
static void showInteractiveHelp(void) {
printf(
"\n"
"Welcome to redis-cli " REDIS_VERSION "!\n"
"Just type any valid Redis command to see a pretty printed output.\n"
"\n"
"It is possible to quote strings, like in:\n"
" set \"my key\" \"some string \\xff\\n\"\n"
"\n"
"You can find a list of valid Redis commands at\n"
" http://code.google.com/p/redis/wiki/CommandReference\n"
"\n"
"Note: redis-cli supports line editing, use up/down arrows for history."
"\n\n");
}
static int cliSendCommand(int argc, char **argv, int repeat) { static int cliSendCommand(int argc, char **argv, int repeat) {
char *command = argv[0]; char *command = argv[0];
int fd, j, retval = 0; int fd, j, retval = 0;
sds cmd; sds cmd;
config.raw_output = !strcasecmp(command,"info"); config.raw_output = !strcasecmp(command,"info");
if (!strcasecmp(command,"help")) {
showInteractiveHelp();
return 0;
}
if (!strcasecmp(command,"shutdown")) config.shutdown = 1; if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
if (!strcasecmp(command,"subscribe") || if (!strcasecmp(command,"subscribe") ||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
if ((fd = cliConnect()) == -1) return 1; if ((fd = cliConnect(0)) == -1) return 1;
/* Select db number */ /* Select db number */
retval = selectDb(fd); retval = selectDb(fd);
...@@ -279,21 +312,21 @@ static int cliSendCommand(int argc, char **argv, int repeat) { ...@@ -279,21 +312,21 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
while(repeat--) { while(repeat--) {
anetWrite(fd,cmd,sdslen(cmd)); anetWrite(fd,cmd,sdslen(cmd));
while (config.monitor_mode) { while (config.monitor_mode) {
cliReadSingleLineReply(fd,0); if (cliReadSingleLineReply(fd,0)) exit(1);
printf("\n");
} }
if (config.pubsub_mode) { if (config.pubsub_mode) {
printf("Reading messages... (press Ctrl-c to quit)\n"); printf("Reading messages... (press Ctrl-c to quit)\n");
while (1) { while (1) {
cliReadReply(fd); cliReadReply(fd);
printf("\n"); printf("\n\n");
} }
} }
retval = cliReadReply(fd); retval = cliReadReply(fd);
if (retval) { if (!config.raw_output && config.tty) printf("\n");
return retval; if (retval) return retval;
}
} }
return 0; return 0;
} }
...@@ -314,6 +347,8 @@ static int parseOptions(int argc, char **argv) { ...@@ -314,6 +347,8 @@ static int parseOptions(int argc, char **argv) {
i++; i++;
} else if (!strcmp(argv[i],"-h") && lastarg) { } else if (!strcmp(argv[i],"-h") && lastarg) {
usage(); usage();
} else if (!strcmp(argv[i],"-x")) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) { } else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[i+1]); config.hostport = atoi(argv[i+1]);
i++; i++;
...@@ -330,11 +365,18 @@ static int parseOptions(int argc, char **argv) { ...@@ -330,11 +365,18 @@ static int parseOptions(int argc, char **argv) {
config.auth = argv[i+1]; config.auth = argv[i+1];
i++; i++;
} else if (!strcmp(argv[i],"-i")) { } else if (!strcmp(argv[i],"-i")) {
config.interactive = 1; fprintf(stderr,
"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
"by default when redis-cli is executed without a command to execute.\n"
);
} else if (!strcmp(argv[i],"-c")) { } else if (!strcmp(argv[i],"-c")) {
config.argn_from_stdin = 1; fprintf(stderr,
"Reading last argument from standard input using -c is deprecated.\n"
"When standard input is connected to a pipe or regular file, it is\n"
"automatically used as last argument.\n"
);
} else if (!strcmp(argv[i],"-v")) { } else if (!strcmp(argv[i],"-v")) {
printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION); printf("redis-cli shipped with Redis version %s\n", REDIS_VERSION);
exit(0); exit(0);
} else { } else {
break; break;
...@@ -362,9 +404,8 @@ static sds readArgFromStdin(void) { ...@@ -362,9 +404,8 @@ static sds readArgFromStdin(void) {
static void usage() { static void usage() {
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n"); fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n"); fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
fprintf(stderr, "example: redis-cli get my_passwd\n"); fprintf(stderr, "example: redis-cli get my_passwd\n");
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n"); fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n"); fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
...@@ -382,87 +423,39 @@ static char **convertToSds(int count, char** args) { ...@@ -382,87 +423,39 @@ static char **convertToSds(int count, char** args) {
return sds; return sds;
} }
static char **splitArguments(char *line, int *argc) {
char *p = line;
char *current = NULL;
char **vector = NULL;
*argc = 0;
while(1) {
/* skip blanks */
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
int done = 0;
if (current == NULL) current = sdsempty();
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1)) {
char c;
p++;
switch(*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'b': c = '\b'; break;
case 'a': c = '\a'; break;
default: c = *p; break;
}
current = sdscatlen(current,&c,1);
} else if (*p == '"') {
done = 1;
} else {
current = sdscatlen(current,p,1);
}
} else {
switch(*p) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\0':
done=1;
break;
case '"':
inq=1;
break;
default:
current = sdscatlen(current,p,1);
break;
}
}
if (*p) p++;
}
/* add the token to the vector */
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
vector[*argc] = current;
(*argc)++;
current = NULL;
} else {
return vector;
}
}
}
#define LINE_BUFLEN 4096 #define LINE_BUFLEN 4096
static void repl() { static void repl() {
int argc, j; int argc, j;
char *line, **argv; char *line;
sds *argv;
config.interactive = 1;
while((line = linenoise("redis> ")) != NULL) { while((line = linenoise("redis> ")) != NULL) {
if (line[0] != '\0') { if (line[0] != '\0') {
argv = splitArguments(line,&argc); argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line); linenoiseHistoryAdd(line);
if (config.historyfile) linenoiseHistorySave(config.historyfile); if (config.historyfile) linenoiseHistorySave(config.historyfile);
if (argc > 0) { if (argv == NULL) {
printf("Invalid argument(s)\n");
continue;
} else if (argc > 0) {
if (strcasecmp(argv[0],"quit") == 0 || if (strcasecmp(argv[0],"quit") == 0 ||
strcasecmp(argv[0],"exit") == 0) strcasecmp(argv[0],"exit") == 0)
exit(0); {
else exit(0);
cliSendCommand(argc, argv, 1); } else {
int err;
if ((err = cliSendCommand(argc, argv, 1)) != 0) {
if (err == ECONNRESET) {
printf("Reconnecting... ");
fflush(stdout);
if (cliConnect(1) == -1) exit(1);
printf("OK\n");
cliSendCommand(argc,argv,1);
}
}
}
} }
/* Free the argument vector */ /* Free the argument vector */
for (j = 0; j < argc; j++) for (j = 0; j < argc; j++)
...@@ -475,23 +468,37 @@ static void repl() { ...@@ -475,23 +468,37 @@ static void repl() {
exit(0); exit(0);
} }
static int noninteractive(int argc, char **argv) {
int retval = 0;
if (config.stdinarg) {
argv = zrealloc(argv, (argc+1)*sizeof(char*));
argv[argc] = readArgFromStdin();
retval = cliSendCommand(argc+1, argv, config.repeat);
} else {
/* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
retval = cliSendCommand(argc, argv, config.repeat);
}
return retval;
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
int firstarg; int firstarg;
char **argvcopy;
config.hostip = "127.0.0.1"; config.hostip = "127.0.0.1";
config.hostport = 6379; config.hostport = 6379;
config.hostsocket = NULL; config.hostsocket = NULL;
config.repeat = 1; config.repeat = 1;
config.dbnum = 0; config.dbnum = 0;
config.argn_from_stdin = 0;
config.shutdown = 0;
config.interactive = 0; config.interactive = 0;
config.shutdown = 0;
config.monitor_mode = 0; config.monitor_mode = 0;
config.pubsub_mode = 0; config.pubsub_mode = 0;
config.raw_output = 0; config.raw_output = 0;
config.stdinarg = 0;
config.auth = NULL; config.auth = NULL;
config.historyfile = NULL; config.historyfile = NULL;
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
config.mb_sep = '\n';
if (getenv("HOME") != NULL) { if (getenv("HOME") != NULL) {
config.historyfile = malloc(256); config.historyfile = malloc(256);
...@@ -505,19 +512,20 @@ int main(int argc, char **argv) { ...@@ -505,19 +512,20 @@ int main(int argc, char **argv) {
if (config.auth != NULL) { if (config.auth != NULL) {
char *authargv[2]; char *authargv[2];
int dbnum = config.dbnum;
/* We need to save the real configured database number and set it to
* zero here, otherwise cliSendCommand() will try to perform the
* SELECT command before the authentication, and it will fail. */
config.dbnum = 0;
authargv[0] = "AUTH"; authargv[0] = "AUTH";
authargv[1] = config.auth; authargv[1] = config.auth;
cliSendCommand(2, convertToSds(2, authargv), 1); cliSendCommand(2, convertToSds(2, authargv), 1);
config.dbnum = dbnum; /* restore the right DB number */
} }
if (argc == 0 || config.interactive == 1) repl(); /* Start interactive mode when no command is provided */
if (argc == 0) repl();
argvcopy = convertToSds(argc+1, argv); /* Otherwise, we have some arguments to execute */
if (config.argn_from_stdin) { return noninteractive(argc,convertToSds(argc,argv));
sds lastarg = readArgFromStdin();
argvcopy[argc] = lastarg;
argc++;
}
return cliSendCommand(argc, argvcopy, config.repeat);
} }
...@@ -51,6 +51,7 @@ ...@@ -51,6 +51,7 @@
#include <float.h> #include <float.h>
#include <math.h> #include <math.h>
#include <pthread.h> #include <pthread.h>
#include <sys/resource.h>
/* Our shared "common" objects */ /* Our shared "common" objects */
...@@ -170,6 +171,7 @@ struct redisCommand readonlyCommandTable[] = { ...@@ -170,6 +171,7 @@ struct redisCommand readonlyCommandTable[] = {
{"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
{"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
{"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"persist",persistCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0},
{"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, {"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
{"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0}, {"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0},
...@@ -338,7 +340,7 @@ dictType zsetDictType = { ...@@ -338,7 +340,7 @@ dictType zsetDictType = {
NULL, /* val dup */ NULL, /* val dup */
dictEncObjKeyCompare, /* key compare */ dictEncObjKeyCompare, /* key compare */
dictRedisObjectDestructor, /* key destructor */ dictRedisObjectDestructor, /* key destructor */
dictVanillaFree /* val destructor of malloc(sizeof(double)) */ NULL /* val destructor */
}; };
/* Db->dict, keys are sds strings, vals are Redis objects. */ /* Db->dict, keys are sds strings, vals are Redis objects. */
...@@ -435,6 +437,48 @@ void updateDictResizePolicy(void) { ...@@ -435,6 +437,48 @@ void updateDictResizePolicy(void) {
/* ======================= Cron: called every 100 ms ======================== */ /* ======================= Cron: called every 100 ms ======================== */
/* Try to expire a few timed out keys. The algorithm used is adaptive and
* will use few CPU cycles if there are few expiring keys, otherwise
* it will get more aggressive to avoid that too much memory is used by
* keys that can be removed from the keyspace. */
void activeExpireCycle(void) {
int j;
for (j = 0; j < server.dbnum; j++) {
int expired;
redisDb *db = server.db+j;
/* Continue to expire if at the end of the cycle more than 25%
* of the keys were expired. */
do {
long num = dictSize(db->expires);
time_t now = time(NULL);
expired = 0;
if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
num = REDIS_EXPIRELOOKUPS_PER_CRON;
while (num--) {
dictEntry *de;
time_t t;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
t = (time_t) dictGetEntryVal(de);
if (now > t) {
sds key = dictGetEntryKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
propagateExpire(db,keyobj);
dbDelete(db,keyobj);
decrRefCount(keyobj);
expired++;
server.stat_expiredkeys++;
}
}
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
}
}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j, loops = server.cronloops++; int j, loops = server.cronloops++;
REDIS_NOTUSED(eventLoop); REDIS_NOTUSED(eventLoop);
...@@ -533,41 +577,10 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -533,41 +577,10 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
} }
} }
/* Try to expire a few timed out keys. The algorithm used is adaptive and /* Expire a few keys per cycle, only if this is a master.
* will use few CPU cycles if there are few expiring keys, otherwise * On slaves we wait for DEL operations synthesized by the master
* it will get more aggressive to avoid that too much memory is used by * in order to guarantee a strict consistency. */
* keys that can be removed from the keyspace. */ if (server.masterhost == NULL) activeExpireCycle();
for (j = 0; j < server.dbnum; j++) {
int expired;
redisDb *db = server.db+j;
/* Continue to expire if at the end of the cycle more than 25%
* of the keys were expired. */
do {
long num = dictSize(db->expires);
time_t now = time(NULL);
expired = 0;
if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
num = REDIS_EXPIRELOOKUPS_PER_CRON;
while (num--) {
dictEntry *de;
time_t t;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
t = (time_t) dictGetEntryVal(de);
if (now > t) {
sds key = dictGetEntryKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
dbDelete(db,keyobj);
decrRefCount(keyobj);
expired++;
server.stat_expiredkeys++;
}
}
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
}
/* Swap a few keys on disk if we are over the memory limit and VM /* Swap a few keys on disk if we are over the memory limit and VM
* is enbled. Try to free objects from the free list first. */ * is enbled. Try to free objects from the free list first. */
...@@ -734,6 +747,7 @@ void initServerConfig() { ...@@ -734,6 +747,7 @@ void initServerConfig() {
server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE; server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES; server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE; server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
server.shutdown_asap = 0; server.shutdown_asap = 0;
resetServerSaveParams(); resetServerSaveParams();
...@@ -892,9 +906,6 @@ void call(redisClient *c, struct redisCommand *cmd) { ...@@ -892,9 +906,6 @@ void call(redisClient *c, struct redisCommand *cmd) {
int processCommand(redisClient *c) { int processCommand(redisClient *c) {
struct redisCommand *cmd; struct redisCommand *cmd;
/* Free some memory if needed (maxmemory setting) */
if (server.maxmemory) freeMemoryIfNeeded();
/* Handle the multi bulk command type. This is an alternative protocol /* Handle the multi bulk command type. This is an alternative protocol
* supported by Redis in order to receive commands that are composed of * supported by Redis in order to receive commands that are composed of
* multiple binary-safe "bulk" arguments. The latency of processing is * multiple binary-safe "bulk" arguments. The latency of processing is
...@@ -913,15 +924,20 @@ int processCommand(redisClient *c) { ...@@ -913,15 +924,20 @@ int processCommand(redisClient *c) {
} else if (c->multibulk) { } else if (c->multibulk) {
if (c->bulklen == -1) { if (c->bulklen == -1) {
if (((char*)c->argv[0]->ptr)[0] != '$') { if (((char*)c->argv[0]->ptr)[0] != '$') {
addReplySds(c,sdsnew("-ERR multi bulk protocol error\r\n")); addReplyError(c,"multi bulk protocol error");
resetClient(c); resetClient(c);
return 1; return 1;
} else { } else {
int bulklen = atoi(((char*)c->argv[0]->ptr)+1); char *eptr;
long bulklen = strtol(((char*)c->argv[0]->ptr)+1,&eptr,10);
int perr = eptr[0] != '\0';
decrRefCount(c->argv[0]); decrRefCount(c->argv[0]);
if (bulklen < 0 || bulklen > 1024*1024*1024) { if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
bulklen < 0 || bulklen > 1024*1024*1024)
{
c->argc--; c->argc--;
addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n")); addReplyError(c,"invalid bulk write count");
resetClient(c); resetClient(c);
return 1; return 1;
} }
...@@ -974,27 +990,28 @@ int processCommand(redisClient *c) { ...@@ -974,27 +990,28 @@ int processCommand(redisClient *c) {
* such wrong arity, bad command name and so forth. */ * such wrong arity, bad command name and so forth. */
cmd = lookupCommand(c->argv[0]->ptr); cmd = lookupCommand(c->argv[0]->ptr);
if (!cmd) { if (!cmd) {
addReplySds(c, addReplyErrorFormat(c,"unknown command '%s'",
sdscatprintf(sdsempty(), "-ERR unknown command '%s'\r\n", (char*)c->argv[0]->ptr);
(char*)c->argv[0]->ptr));
resetClient(c); resetClient(c);
return 1; return 1;
} else if ((cmd->arity > 0 && cmd->arity != c->argc) || } else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
(c->argc < -cmd->arity)) { (c->argc < -cmd->arity)) {
addReplySds(c, addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
sdscatprintf(sdsempty(), cmd->name);
"-ERR wrong number of arguments for '%s' command\r\n",
cmd->name));
resetClient(c); resetClient(c);
return 1; return 1;
} else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) {
/* This is a bulk command, we have to read the last argument yet. */ /* This is a bulk command, we have to read the last argument yet. */
int bulklen = atoi(c->argv[c->argc-1]->ptr); char *eptr;
long bulklen = strtol(c->argv[c->argc-1]->ptr,&eptr,10);
int perr = eptr[0] != '\0';
decrRefCount(c->argv[c->argc-1]); decrRefCount(c->argv[c->argc-1]);
if (bulklen < 0 || bulklen > 1024*1024*1024) { if (perr || bulklen == LONG_MAX || bulklen == LONG_MIN ||
bulklen < 0 || bulklen > 1024*1024*1024)
{
c->argc--; c->argc--;
addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n")); addReplyError(c,"invalid bulk write count");
resetClient(c); resetClient(c);
return 1; return 1;
} }
...@@ -1021,16 +1038,21 @@ int processCommand(redisClient *c) { ...@@ -1021,16 +1038,21 @@ int processCommand(redisClient *c) {
/* Check if the user is authenticated */ /* Check if the user is authenticated */
if (server.requirepass && !c->authenticated && cmd->proc != authCommand) { if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
addReplySds(c,sdsnew("-ERR operation not permitted\r\n")); addReplyError(c,"operation not permitted");
resetClient(c); resetClient(c);
return 1; return 1;
} }
/* Handle the maxmemory directive */ /* Handle the maxmemory directive.
*
* First we try to free some memory if possible (if there are volatile
* keys in the dataset). If there are not the only thing we can do
* is returning an error. */
if (server.maxmemory) freeMemoryIfNeeded();
if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) && if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) &&
zmalloc_used_memory() > server.maxmemory) zmalloc_used_memory() > server.maxmemory)
{ {
addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n")); addReplyError(c,"command not allowed when used memory > 'maxmemory'");
resetClient(c); resetClient(c);
return 1; return 1;
} }
...@@ -1040,7 +1062,7 @@ int processCommand(redisClient *c) { ...@@ -1040,7 +1062,7 @@ int processCommand(redisClient *c) {
&& &&
cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand && cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand &&
cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) { cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) {
addReplySds(c,sdsnew("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context\r\n")); addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
resetClient(c); resetClient(c);
return 1; return 1;
} }
...@@ -1081,11 +1103,7 @@ int prepareForShutdown() { ...@@ -1081,11 +1103,7 @@ int prepareForShutdown() {
if (server.vm_enabled) unlink(server.vm_swap_file); if (server.vm_enabled) unlink(server.vm_swap_file);
} else { } else {
/* Snapshotting. Perform a SYNC SAVE and exit */ /* Snapshotting. Perform a SYNC SAVE and exit */
if (rdbSave(server.dbfilename) == REDIS_OK) { if (rdbSave(server.dbfilename) != REDIS_OK) {
if (server.daemonize)
unlink(server.pidfile);
redisLog(REDIS_WARNING,"%zu bytes used at exit",zmalloc_used_memory());
} else {
/* Ooops.. error saving! The best we can do is to continue /* Ooops.. error saving! The best we can do is to continue
* operating. Note that if there was a background saving process, * operating. Note that if there was a background saving process,
* in the next cron() Redis will be notified that the background * in the next cron() Redis will be notified that the background
...@@ -1095,6 +1113,7 @@ int prepareForShutdown() { ...@@ -1095,6 +1113,7 @@ int prepareForShutdown() {
return REDIS_ERR; return REDIS_ERR;
} }
} }
if (server.daemonize) unlink(server.pidfile);
redisLog(REDIS_WARNING,"Server exit now, bye bye..."); redisLog(REDIS_WARNING,"Server exit now, bye bye...");
return REDIS_OK; return REDIS_OK;
} }
...@@ -1107,7 +1126,7 @@ void authCommand(redisClient *c) { ...@@ -1107,7 +1126,7 @@ void authCommand(redisClient *c) {
addReply(c,shared.ok); addReply(c,shared.ok);
} else { } else {
c->authenticated = 0; c->authenticated = 0;
addReplySds(c,sdscatprintf(sdsempty(),"-ERR invalid password\r\n")); addReplyError(c,"invalid password");
} }
} }
...@@ -1148,6 +1167,10 @@ sds genRedisInfoString(void) { ...@@ -1148,6 +1167,10 @@ sds genRedisInfoString(void) {
time_t uptime = time(NULL)-server.stat_starttime; time_t uptime = time(NULL)-server.stat_starttime;
int j; int j;
char hmem[64]; char hmem[64];
struct rusage self_ru, c_ru;
getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru);
bytesToHuman(hmem,zmalloc_used_memory()); bytesToHuman(hmem,zmalloc_used_memory());
info = sdscatprintf(sdsempty(), info = sdscatprintf(sdsempty(),
...@@ -1159,11 +1182,16 @@ sds genRedisInfoString(void) { ...@@ -1159,11 +1182,16 @@ sds genRedisInfoString(void) {
"process_id:%ld\r\n" "process_id:%ld\r\n"
"uptime_in_seconds:%ld\r\n" "uptime_in_seconds:%ld\r\n"
"uptime_in_days:%ld\r\n" "uptime_in_days:%ld\r\n"
"used_cpu_sys:%.2f\r\n"
"used_cpu_user:%.2f\r\n"
"used_cpu_sys_childrens:%.2f\r\n"
"used_cpu_user_childrens:%.2f\r\n"
"connected_clients:%d\r\n" "connected_clients:%d\r\n"
"connected_slaves:%d\r\n" "connected_slaves:%d\r\n"
"blocked_clients:%d\r\n" "blocked_clients:%d\r\n"
"used_memory:%zu\r\n" "used_memory:%zu\r\n"
"used_memory_human:%s\r\n" "used_memory_human:%s\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
"changes_since_last_save:%lld\r\n" "changes_since_last_save:%lld\r\n"
"bgsave_in_progress:%d\r\n" "bgsave_in_progress:%d\r\n"
"last_save_time:%ld\r\n" "last_save_time:%ld\r\n"
...@@ -1185,11 +1213,16 @@ sds genRedisInfoString(void) { ...@@ -1185,11 +1213,16 @@ sds genRedisInfoString(void) {
(long) getpid(), (long) getpid(),
uptime, uptime,
uptime/(3600*24), uptime/(3600*24),
(float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
(float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
(float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
(float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
listLength(server.clients)-listLength(server.slaves), listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves), listLength(server.slaves),
server.blpop_blocked_clients, server.blpop_blocked_clients,
zmalloc_used_memory(), zmalloc_used_memory(),
hmem, hmem,
zmalloc_get_fragmentation_ratio(),
server.dirty, server.dirty,
server.bgsavechildpid != -1, server.bgsavechildpid != -1,
server.lastsave, server.lastsave,
...@@ -1319,7 +1352,8 @@ void freeMemoryIfNeeded(void) { ...@@ -1319,7 +1352,8 @@ void freeMemoryIfNeeded(void) {
if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue; if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue;
for (j = 0; j < server.dbnum; j++) { for (j = 0; j < server.dbnum; j++) {
int minttl = -1; int minttl = -1;
robj *minkey = NULL; sds minkey = NULL;
robj *keyobj = NULL;
struct dictEntry *de; struct dictEntry *de;
if (dictSize(server.db[j].expires)) { if (dictSize(server.db[j].expires)) {
...@@ -1336,7 +1370,10 @@ void freeMemoryIfNeeded(void) { ...@@ -1336,7 +1370,10 @@ void freeMemoryIfNeeded(void) {
minttl = t; minttl = t;
} }
} }
dbDelete(server.db+j,minkey); keyobj = createStringObject(minkey,sdslen(minkey));
dbDelete(server.db+j,keyobj);
server.stat_expiredkeys++;
decrRefCount(keyobj);
} }
} }
if (!freed) return; /* nothing to free... */ if (!freed) return; /* nothing to free... */
...@@ -1367,9 +1404,17 @@ void linuxOvercommitMemoryWarning(void) { ...@@ -1367,9 +1404,17 @@ void linuxOvercommitMemoryWarning(void) {
} }
#endif /* __linux__ */ #endif /* __linux__ */
void createPidFile(void) {
/* Try to write the pid file in a best-effort way. */
FILE *fp = fopen(server.pidfile,"w");
if (fp) {
fprintf(fp,"%d\n",getpid());
fclose(fp);
}
}
void daemonize(void) { void daemonize(void) {
int fd; int fd;
FILE *fp;
if (fork() != 0) exit(0); /* parent exits */ if (fork() != 0) exit(0); /* parent exits */
setsid(); /* create a new session */ setsid(); /* create a new session */
...@@ -1383,12 +1428,6 @@ void daemonize(void) { ...@@ -1383,12 +1428,6 @@ void daemonize(void) {
dup2(fd, STDERR_FILENO); dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd); if (fd > STDERR_FILENO) close(fd);
} }
/* Try to write the pid file */
fp = fopen(server.pidfile,"w");
if (fp) {
fprintf(fp,"%d\n",getpid());
fclose(fp);
}
} }
void version() { void version() {
...@@ -1421,6 +1460,7 @@ int main(int argc, char **argv) { ...@@ -1421,6 +1460,7 @@ int main(int argc, char **argv) {
} }
if (server.daemonize) daemonize(); if (server.daemonize) daemonize();
initServer(); initServer();
if (server.daemonize) createPidFile();
redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION); redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__ #ifdef __linux__
linuxOvercommitMemoryWarning(); linuxOvercommitMemoryWarning();
...@@ -1500,6 +1540,7 @@ void segvHandler(int sig, siginfo_t *info, void *secret) { ...@@ -1500,6 +1540,7 @@ void segvHandler(int sig, siginfo_t *info, void *secret) {
redisLog(REDIS_WARNING,"%s", messages[i]); redisLog(REDIS_WARNING,"%s", messages[i]);
/* free(messages); Don't call free() with possibly corrupted memory. */ /* free(messages); Don't call free() with possibly corrupted memory. */
if (server.daemonize) unlink(server.pidfile);
_exit(0); _exit(0);
} }
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "anet.h" /* Networking the easy way */ #include "anet.h" /* Networking the easy way */
#include "zipmap.h" /* Compact string -> string data structure */ #include "zipmap.h" /* Compact string -> string data structure */
#include "ziplist.h" /* Compact list data structure */ #include "ziplist.h" /* Compact list data structure */
#include "intset.h" /* Compact integer set structure */
#include "version.h" #include "version.h"
/* Error codes */ /* Error codes */
...@@ -46,6 +47,7 @@ ...@@ -46,6 +47,7 @@
#define REDIS_MAX_WRITE_PER_EVENT (1024*64) #define REDIS_MAX_WRITE_PER_EVENT (1024*64)
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */ #define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
#define REDIS_SHARED_INTEGERS 10000 #define REDIS_SHARED_INTEGERS 10000
#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
/* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */ /* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */
#define REDIS_WRITEV_THRESHOLD 3 #define REDIS_WRITEV_THRESHOLD 3
...@@ -82,6 +84,7 @@ ...@@ -82,6 +84,7 @@
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ #define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ #define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ #define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
/* Object types only used for dumping to disk */ /* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME 253 #define REDIS_EXPIRETIME 253
...@@ -188,6 +191,7 @@ ...@@ -188,6 +191,7 @@
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512 #define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024 #define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32 #define REDIS_LIST_MAX_ZIPLIST_VALUE 32
#define REDIS_SET_MAX_INTSET_ENTRIES 4096
/* Sets operations codes */ /* Sets operations codes */
#define REDIS_OP_UNION 0 #define REDIS_OP_UNION 0
...@@ -282,8 +286,9 @@ typedef struct redisClient { ...@@ -282,8 +286,9 @@ typedef struct redisClient {
int dictid; int dictid;
sds querybuf; sds querybuf;
robj **argv, **mbargv; robj **argv, **mbargv;
char *newline; /* pointing to the detected newline in querybuf */
int argc, mbargc; int argc, mbargc;
int bulklen; /* bulk read len. -1 if not in bulk read mode */ long bulklen; /* bulk read len. -1 if not in bulk read mode */
int multibulk; /* multi bulk command format active */ int multibulk; /* multi bulk command format active */
list *reply; list *reply;
int sentlen; int sentlen;
...@@ -306,6 +311,10 @@ typedef struct redisClient { ...@@ -306,6 +311,10 @@ typedef struct redisClient {
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */ list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */ dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */ list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
/* Response buffer */
int bufpos;
char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient; } redisClient;
struct saveparam { struct saveparam {
...@@ -335,6 +344,7 @@ struct redisServer { ...@@ -335,6 +344,7 @@ struct redisServer {
int sofd; int sofd;
redisDb *db; redisDb *db;
long long dirty; /* changes to DB from the last save */ long long dirty; /* changes to DB from the last save */
long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
list *clients; list *clients;
list *slaves, *monitors; list *slaves, *monitors;
char neterr[ANET_ERR_LEN]; char neterr[ANET_ERR_LEN];
...@@ -400,6 +410,7 @@ struct redisServer { ...@@ -400,6 +410,7 @@ struct redisServer {
size_t hash_max_zipmap_value; size_t hash_max_zipmap_value;
size_t list_max_ziplist_entries; size_t list_max_ziplist_entries;
size_t list_max_ziplist_value; size_t list_max_ziplist_value;
size_t set_max_intset_entries;
/* Virtual memory state */ /* Virtual memory state */
FILE *vm_fp; FILE *vm_fp;
int vm_fd; int vm_fd;
...@@ -482,13 +493,14 @@ typedef struct _redisSortOperation { ...@@ -482,13 +493,14 @@ typedef struct _redisSortOperation {
} redisSortOperation; } redisSortOperation;
/* ZSETs use a specialized version of Skiplists */ /* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode { typedef struct zskiplistNode {
struct zskiplistNode **forward;
struct zskiplistNode *backward;
unsigned int *span;
double score;
robj *obj; robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode; } zskiplistNode;
typedef struct zskiplist { typedef struct zskiplist {
...@@ -537,6 +549,14 @@ typedef struct { ...@@ -537,6 +549,14 @@ typedef struct {
listNode *ln; /* Entry in linked list */ listNode *ln; /* Entry in linked list */
} listTypeEntry; } listTypeEntry;
/* Structure to hold set iteration abstraction. */
typedef struct {
robj *subject;
int encoding;
int ii; /* intset iterator */
dictIterator *di;
} setTypeIterator;
/* Structure to hold hash iteration abstration. Note that iteration over /* Structure to hold hash iteration abstration. Note that iteration over
* hashes involves both fields and values. Because it is possible that * hashes involves both fields and values. Because it is possible that
* not both are required, store pointers in the iterator to avoid * not both are required, store pointers in the iterator to avoid
...@@ -577,6 +597,8 @@ void resetClient(redisClient *c); ...@@ -577,6 +597,8 @@ void resetClient(redisClient *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask); void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask); void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj); void addReply(redisClient *c, robj *obj);
void *addDeferredMultiBulkLength(redisClient *c);
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
void addReplySds(redisClient *c, sds s); void addReplySds(redisClient *c, sds s);
void processInputBuffer(redisClient *c); void processInputBuffer(redisClient *c);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
...@@ -587,11 +609,23 @@ void addReplyBulkCString(redisClient *c, char *s); ...@@ -587,11 +609,23 @@ void addReplyBulkCString(redisClient *c, char *s);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj); void addReply(redisClient *c, robj *obj);
void addReplySds(redisClient *c, sds s); void addReplySds(redisClient *c, sds s);
void addReplyError(redisClient *c, char *err);
void addReplyStatus(redisClient *c, char *status);
void addReplyDouble(redisClient *c, double d); void addReplyDouble(redisClient *c, double d);
void addReplyLongLong(redisClient *c, long long ll); void addReplyLongLong(redisClient *c, long long ll);
void addReplyUlong(redisClient *c, unsigned long ul); void addReplyMultiBulkLen(redisClient *c, long length);
void *dupClientReplyValue(void *o); void *dupClientReplyValue(void *o);
#ifdef __GNUC__
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
void addReplyStatusFormat(redisClient *c, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
void addReplyErrorFormat(redisClient *c, const char *fmt, ...);
void addReplyStatusFormat(redisClient *c, const char *fmt, ...);
#endif
/* List data type */ /* List data type */
void listTypeTryConversion(robj *subject, robj *value); void listTypeTryConversion(robj *subject, robj *value);
void listTypePush(robj *subject, robj *value, int where); void listTypePush(robj *subject, robj *value, int where);
...@@ -636,6 +670,7 @@ robj *createStringObjectFromLongLong(long long value); ...@@ -636,6 +670,7 @@ robj *createStringObjectFromLongLong(long long value);
robj *createListObject(void); robj *createListObject(void);
robj *createZiplistObject(void); robj *createZiplistObject(void);
robj *createSetObject(void); robj *createSetObject(void);
robj *createIntsetObject(void);
robj *createHashObject(void); robj *createHashObject(void);
robj *createZsetObject(void); robj *createZsetObject(void);
int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg); int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg);
...@@ -677,7 +712,7 @@ void backgroundRewriteDoneHandler(int statloc); ...@@ -677,7 +712,7 @@ void backgroundRewriteDoneHandler(int statloc);
/* Sorted sets data type */ /* Sorted sets data type */
zskiplist *zslCreate(void); zskiplist *zslCreate(void);
void zslFree(zskiplist *zsl); void zslFree(zskiplist *zsl);
void zslInsert(zskiplist *zsl, double score, robj *obj); zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
/* Core functions */ /* Core functions */
void freeMemoryIfNeeded(void); void freeMemoryIfNeeded(void);
...@@ -719,6 +754,18 @@ int dontWaitForSwappedKey(redisClient *c, robj *key); ...@@ -719,6 +754,18 @@ int dontWaitForSwappedKey(redisClient *c, robj *key);
void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key); void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
vmpointer *vmSwapObjectBlocking(robj *val); vmpointer *vmSwapObjectBlocking(robj *val);
/* Set data type */
robj *setTypeCreate(robj *value);
int setTypeAdd(robj *subject, robj *value);
int setTypeRemove(robj *subject, robj *value);
int setTypeIsMember(robj *subject, robj *value);
setTypeIterator *setTypeInitIterator(robj *subject);
void setTypeReleaseIterator(setTypeIterator *si);
robj *setTypeNext(setTypeIterator *si);
robj *setTypeRandomElement(robj *subject);
unsigned long setTypeSize(robj *subject);
void setTypeConvert(robj *subject, int enc);
/* Hash data type */ /* Hash data type */
void convertToRealHash(robj *o); void convertToRealHash(robj *o);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end); void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
...@@ -747,6 +794,8 @@ int stringmatch(const char *pattern, const char *string, int nocase); ...@@ -747,6 +794,8 @@ int stringmatch(const char *pattern, const char *string, int nocase);
long long memtoll(const char *p, int *err); long long memtoll(const char *p, int *err);
int ll2string(char *s, size_t len, long long value); int ll2string(char *s, size_t len, long long value);
int isStringRepresentableAsLong(sds s, long *longval); int isStringRepresentableAsLong(sds s, long *longval);
int isStringRepresentableAsLongLong(sds s, long long *longval);
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
/* Configuration */ /* Configuration */
void loadServerConfig(char *filename); void loadServerConfig(char *filename);
...@@ -755,10 +804,10 @@ void resetServerSaveParams(); ...@@ -755,10 +804,10 @@ void resetServerSaveParams();
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key); int removeExpire(redisDb *db, robj *key);
void propagateExpire(redisDb *db, robj *key);
int expireIfNeeded(redisDb *db, robj *key); int expireIfNeeded(redisDb *db, robj *key);
int deleteIfVolatile(redisDb *db, robj *key);
time_t getExpire(redisDb *db, robj *key); time_t getExpire(redisDb *db, robj *key);
int setExpire(redisDb *db, robj *key, time_t when); void setExpire(redisDb *db, robj *key, time_t when);
robj *lookupKey(redisDb *db, robj *key); robj *lookupKey(redisDb *db, robj *key);
robj *lookupKeyRead(redisDb *db, robj *key); robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key); robj *lookupKeyWrite(redisDb *db, robj *key);
...@@ -841,6 +890,7 @@ void expireCommand(redisClient *c); ...@@ -841,6 +890,7 @@ void expireCommand(redisClient *c);
void expireatCommand(redisClient *c); void expireatCommand(redisClient *c);
void getsetCommand(redisClient *c); void getsetCommand(redisClient *c);
void ttlCommand(redisClient *c); void ttlCommand(redisClient *c);
void persistCommand(redisClient *c);
void slaveofCommand(redisClient *c); void slaveofCommand(redisClient *c);
void debugCommand(redisClient *c); void debugCommand(redisClient *c);
void msetCommand(redisClient *c); void msetCommand(redisClient *c);
......
...@@ -138,7 +138,7 @@ int syncRead(int fd, char *ptr, ssize_t size, int timeout) { ...@@ -138,7 +138,7 @@ int syncRead(int fd, char *ptr, ssize_t size, int timeout) {
while(size) { while(size) {
if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) { if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
nread = read(fd,ptr,size); nread = read(fd,ptr,size);
if (nread == -1) return -1; if (nread <= 0) return -1;
ptr += nread; ptr += nread;
size -= nread; size -= nread;
totread += nread; totread += nread;
...@@ -176,12 +176,19 @@ void syncCommand(redisClient *c) { ...@@ -176,12 +176,19 @@ void syncCommand(redisClient *c) {
/* ignore SYNC if aleady slave or in monitor mode */ /* ignore SYNC if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return; if (c->flags & REDIS_SLAVE) return;
/* Refuse SYNC requests if we are a slave but the link with our master
* is not ok... */
if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {
addReplyError(c,"Can't SYNC while not connected with my master");
return;
}
/* SYNC can't be issued when the server has pending data to send to /* SYNC can't be issued when the server has pending data to send to
* the client about already issued commands. We need a fresh reply * the client about already issued commands. We need a fresh reply
* buffer registering the differences between the BGSAVE and the current * buffer registering the differences between the BGSAVE and the current
* dataset, so that we can copy to other slaves if needed. */ * dataset, so that we can copy to other slaves if needed. */
if (listLength(c->reply) != 0) { if (listLength(c->reply) != 0) {
addReplySds(c,sdsnew("-ERR SYNC is invalid with pending input\r\n")); addReplyError(c,"SYNC is invalid with pending input");
return; return;
} }
...@@ -219,7 +226,7 @@ void syncCommand(redisClient *c) { ...@@ -219,7 +226,7 @@ void syncCommand(redisClient *c) {
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC"); redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
if (rdbSaveBackground(server.dbfilename) != REDIS_OK) { if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE"); redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
addReplySds(c,sdsnew("-ERR Unalbe to perform background save\r\n")); addReplyError(c,"Unable to perform background save");
return; return;
} }
c->replstate = REDIS_REPL_WAIT_BGSAVE_END; c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
...@@ -392,7 +399,12 @@ int syncWithMaster(void) { ...@@ -392,7 +399,12 @@ int syncWithMaster(void) {
strerror(errno)); strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
if (buf[0] != '$') { if (buf[0] == '-') {
close(fd);
redisLog(REDIS_WARNING,"MASTER aborted replication with an error: %s",
buf+1);
return REDIS_ERR;
} else if (buf[0] != '$') {
close(fd); close(fd);
redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?"); redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?");
return REDIS_ERR; return REDIS_ERR;
...@@ -416,9 +428,9 @@ int syncWithMaster(void) { ...@@ -416,9 +428,9 @@ int syncWithMaster(void) {
int nread, nwritten; int nread, nwritten;
nread = read(fd,buf,(dumpsize < 1024)?dumpsize:1024); nread = read(fd,buf,(dumpsize < 1024)?dumpsize:1024);
if (nread == -1) { if (nread <= 0) {
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s", redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
strerror(errno)); (nread == -1) ? strerror(errno) : "connection lost");
close(fd); close(fd);
close(dfd); close(dfd);
return REDIS_ERR; return REDIS_ERR;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment