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 @@
</div>
<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>
<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>
&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">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>
<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>
<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>
......
......@@ -15,11 +15,11 @@ endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb
INSTALL_TOP= /usr/local
INSTALL_BIN= $(INSTALL_TOP)/bin
PREFIX= /usr/local
INSTALL_BIN= $(PREFIX)/bin
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
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
......@@ -33,6 +33,7 @@ CHECKAOFPRGNAME = redis-check-aof
all: redis-server redis-benchmark redis-cli redis-check-dump redis-check-aof
# Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h zmalloc.h
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
......@@ -40,22 +41,59 @@ ae_epoll.o: ae_epoll.c
ae_kqueue.o: ae_kqueue.c
ae_select.o: ae_select.c
anet.o: anet.c fmacros.h anet.h
aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
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
intset.o: intset.c intset.h zmalloc.h
linenoise.o: linenoise.c fmacros.h
lzf_c.o: lzf_c.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
pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h
redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h \
linenoise.h
redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
adlist.h zmalloc.h lzf.h pqsort.h zipmap.h ziplist.h sha1.h
redis-cli.o: redis-cli.c fmacros.h version.h anet.h sds.h adlist.h \
zmalloc.h linenoise.h
redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
release.o: release.c release.h
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
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
zipmap.o: zipmap.c zmalloc.h
zmalloc.o: zmalloc.c config.h
......@@ -115,6 +153,7 @@ noopt:
make PROF="-pg" ARCH="-arch i386"
install: all
mkdir -p $(INSTALL_BIN)
$(INSTALL) $(PRGNAME) $(INSTALL_BIN)
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
......
......@@ -189,6 +189,7 @@ struct redisClient *createFakeClient(void) {
c->querybuf = sdsempty();
c->argc = 0;
c->argv = NULL;
c->bufpos = 0;
c->flags = 0;
/* We set the fake client as a slave waiting for the synchronization
* so that Redis will not try to send replies to this client. */
......@@ -272,12 +273,14 @@ int loadAppendOnlyFile(char *filename) {
fakeClient->argc = argc;
fakeClient->argv = argv;
cmd->proc(fakeClient);
/* Discard the reply objects list from the fake client */
while(listLength(fakeClient->reply))
listDelNode(fakeClient->reply,listFirst(fakeClient->reply));
/* The fake client should not have a reply */
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* Clean up, ready for the next command */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv);
/* Handle swapping while loading big datasets when VM is on */
force_swapout = 0;
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
......@@ -307,7 +310,7 @@ readerr:
}
exit(1);
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);
}
......@@ -463,20 +466,30 @@ int rewriteAppendOnlyFile(char *filename) {
redisPanic("Unknown list encoding");
}
} else if (o->type == REDIS_SET) {
char cmd[]="*3\r\n$4\r\nSADD\r\n";
/* Emit the SADDs needed to rebuild the set */
dict *set = o->ptr;
dictIterator *di = dictGetIterator(set);
if (o->encoding == REDIS_ENCODING_INTSET) {
int ii = 0;
int64_t llval;
while(intsetGet(o->ptr,ii++,&llval)) {
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 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) {
char cmd[]="*3\r\n$4\r\nSADD\r\n";
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");
}
} else if (o->type == REDIS_ZSET) {
/* Emit the ZADDs needed to rebuild the sorted set */
zset *zs = o->ptr;
......@@ -620,12 +633,11 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(redisClient *c) {
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;
}
if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
char *status = "+Background append only file rewriting started\r\n";
addReplySds(c,sdsnew(status));
addReplyStatus(c,"Background append only file rewriting started");
} else {
addReply(c,shared.err);
}
......
......@@ -201,6 +201,8 @@ void loadServerConfig(char *filename) {
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2){
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 {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
......@@ -241,6 +243,7 @@ void configSetCommand(redisClient *c) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
server.maxmemory = ll;
if (server.maxmemory) freeMemoryIfNeeded();
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > LONG_MAX) goto badfmt;
......@@ -270,8 +273,8 @@ void configSetCommand(redisClient *c) {
stopAppendOnly();
} else {
if (startAppendOnly() == REDIS_ERR) {
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR Unable to turn on AOF. Check server logs.\r\n"));
addReplyError(c,
"Unable to turn on AOF. Check server logs.");
decrRefCount(o);
return;
}
......@@ -312,9 +315,8 @@ void configSetCommand(redisClient *c) {
}
sdsfreesplitres(v,vlen);
} else {
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR not supported CONFIG parameter %s\r\n",
(char*)c->argv[2]->ptr));
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr);
decrRefCount(o);
return;
}
......@@ -323,22 +325,18 @@ void configSetCommand(redisClient *c) {
return;
badfmt: /* Bad format errors */
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR invalid argument '%s' for CONFIG SET '%s'\r\n",
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
(char*)o->ptr,
(char*)c->argv[2]->ptr));
(char*)c->argv[2]->ptr);
decrRefCount(o);
}
void configGetCommand(redisClient *c) {
robj *o = getDecodedObject(c->argv[2]);
robj *lenobj = createObject(REDIS_STRING,NULL);
void *replylen = addDeferredMultiBulkLength(c);
char *pattern = o->ptr;
int matches = 0;
addReply(c,lenobj);
decrRefCount(lenobj);
if (stringmatch(pattern,"dbfilename",0)) {
addReplyBulkCString(c,"dbfilename");
addReplyBulkCString(c,server.dbfilename);
......@@ -410,7 +408,7 @@ void configGetCommand(redisClient *c) {
matches++;
}
decrRefCount(o);
lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2);
setDeferredMultiBulkLength(c,replylen,matches*2);
}
void configCommand(redisClient *c) {
......@@ -428,13 +426,12 @@ void configCommand(redisClient *c) {
server.stat_starttime = time(NULL);
addReply(c,shared.ok);
} else {
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR CONFIG subcommand must be one of GET, SET, RESETSTAT\r\n"));
addReplyError(c,
"CONFIG subcommand must be one of GET, SET, RESETSTAT");
}
return;
badarity:
addReplySds(c,sdscatprintf(sdsempty(),
"-ERR Wrong number of arguments for CONFIG %s\r\n",
(char*) c->argv[1]->ptr));
addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
(char*) c->argv[1]->ptr);
}
......@@ -21,6 +21,16 @@
#define redis_stat stat
#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() */
#if defined(__APPLE__) || defined(__linux__)
#define HAVE_BACKTRACE 1
......
......@@ -45,7 +45,7 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
}
robj *lookupKeyWrite(redisDb *db, robj *key) {
deleteIfVolatile(db,key);
expireIfNeeded(db,key);
return lookupKey(db,key);
}
......@@ -123,6 +123,11 @@ robj *dbRandomKey(redisDb *db) {
/* Delete a key, value, and associated expiration entry if any, from the DB */
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
* the key, because it is shared with the main dictionary. */
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
......@@ -199,7 +204,7 @@ void selectCommand(redisClient *c) {
int id = atoi(c->argv[1]->ptr);
if (selectDb(c,id) == REDIS_ERR) {
addReplySds(c,sdsnew("-ERR invalid DB index\r\n"));
addReplyError(c,"invalid DB index");
} else {
addReply(c,shared.ok);
}
......@@ -221,19 +226,17 @@ void keysCommand(redisClient *c) {
dictIterator *di;
dictEntry *de;
sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern);
int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
robj *lenobj = createObject(REDIS_STRING,NULL);
void *replylen = addDeferredMultiBulkLength(c);
di = dictGetIterator(c->db->dict);
addReply(c,lenobj);
decrRefCount(lenobj);
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
while((de = dictNext(di)) != NULL) {
sds key = dictGetEntryKey(de);
robj *keyobj;
if ((pattern[0] == '*' && pattern[1] == '\0') ||
stringmatchlen(pattern,plen,key,sdslen(key),0)) {
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key));
if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj);
......@@ -243,17 +246,15 @@ void keysCommand(redisClient *c) {
}
}
dictReleaseIterator(di);
lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",numkeys);
setDeferredMultiBulkLength(c,replylen,numkeys);
}
void dbsizeCommand(redisClient *c) {
addReplySds(c,
sdscatprintf(sdsempty(),":%lu\r\n",dictSize(c->db->dict)));
addReplyLongLong(c,dictSize(c->db->dict));
}
void lastsaveCommand(redisClient *c) {
addReplySds(c,
sdscatprintf(sdsempty(),":%lu\r\n",server.lastsave));
addReplyLongLong(c,server.lastsave);
}
void typeCommand(redisClient *c) {
......@@ -262,24 +263,23 @@ void typeCommand(redisClient *c) {
o = lookupKeyRead(c->db,c->argv[1]);
if (o == NULL) {
type = "+none";
type = "none";
} else {
switch(o->type) {
case REDIS_STRING: type = "+string"; break;
case REDIS_LIST: type = "+list"; break;
case REDIS_SET: type = "+set"; break;
case REDIS_ZSET: type = "+zset"; break;
case REDIS_HASH: type = "+hash"; break;
default: type = "+unknown"; break;
case REDIS_STRING: type = "string"; break;
case REDIS_LIST: type = "list"; break;
case REDIS_SET: type = "set"; break;
case REDIS_ZSET: type = "zset"; break;
case REDIS_HASH: type = "hash"; break;
default: type = "unknown"; break;
}
}
addReplySds(c,sdsnew(type));
addReply(c,shared.crlf);
addReplyStatus(c,type);
}
void saveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) {
addReplySds(c,sdsnew("-ERR background save in progress\r\n"));
addReplyError(c,"Background save already in progress");
return;
}
if (rdbSave(server.dbfilename) == REDIS_OK) {
......@@ -291,12 +291,11 @@ void saveCommand(redisClient *c) {
void bgsaveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) {
addReplySds(c,sdsnew("-ERR background save already in progress\r\n"));
addReplyError(c,"Background save already in progress");
return;
}
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
char *status = "+Background saving started\r\n";
addReplySds(c,sdsnew(status));
addReplyStatus(c,"Background saving started");
} else {
addReply(c,shared.err);
}
......@@ -305,7 +304,7 @@ void bgsaveCommand(redisClient *c) {
void shutdownCommand(redisClient *c) {
if (prepareForShutdown() == REDIS_OK)
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) {
......@@ -321,7 +320,6 @@ void renameGenericCommand(redisClient *c, int nx) {
return;
incrRefCount(o);
deleteIfVolatile(c->db,c->argv[2]);
if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
if (nx) {
decrRefCount(o);
......@@ -375,7 +373,6 @@ void moveCommand(redisClient *c) {
}
/* Try to add the element to the target DB */
deleteIfVolatile(dst,c->argv[1]);
if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
addReply(c,shared.czero);
return;
......@@ -396,23 +393,16 @@ int removeExpire(redisDb *db, robj *key) {
/* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */
redisAssert(dictFind(db->dict,key->ptr) != NULL);
if (dictDelete(db->expires,key->ptr) == DICT_OK) {
return 1;
} else {
return 0;
}
return dictDelete(db->expires,key->ptr) == DICT_OK;
}
int setExpire(redisDb *db, robj *key, time_t when) {
void setExpire(redisDb *db, robj *key, time_t when) {
dictEntry *de;
/* Reuse the sds from the main dict in the expire dict */
redisAssert((de = dictFind(db->dict,key->ptr)) != NULL);
if (dictAdd(db->expires,dictGetEntryKey(de),(void*)when) == DICT_ERR) {
return 0;
} else {
return 1;
}
de = dictFind(db->dict,key->ptr);
redisAssert(de != NULL);
dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
}
/* Return the expire time of the specified key, or -1 if no expire
......@@ -430,8 +420,46 @@ time_t getExpire(redisDb *db, robj *key) {
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) {
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;
/* Return when this key has not expired */
......@@ -440,15 +468,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
/* Delete the key */
server.stat_expiredkeys++;
server.dirty++;
return dbDelete(db,key);
}
int deleteIfVolatile(redisDb *db, robj *key) {
if (getExpire(db,key) < 0) return 0;
/* Delete the key */
server.stat_expiredkeys++;
server.dirty++;
propagateExpire(db,key);
return dbDelete(db,key);
}
......@@ -458,7 +478,7 @@ int deleteIfVolatile(redisDb *db, robj *key) {
void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
dictEntry *de;
time_t seconds;
long seconds;
if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
......@@ -476,13 +496,10 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
return;
} else {
time_t when = time(NULL)+seconds;
if (setExpire(c->db,key,when)) {
setExpire(c->db,key,when);
addReply(c,shared.cone);
touchWatchedKey(c->db,key);
server.dirty++;
} else {
addReply(c,shared.czero);
}
return;
}
}
......@@ -496,13 +513,28 @@ void expireatCommand(redisClient *c) {
}
void ttlCommand(redisClient *c) {
time_t expire;
int ttl = -1;
time_t expire, ttl = -1;
expire = getExpire(c->db,c->argv[1]);
if (expire != -1) {
ttl = (int) (expire-time(NULL));
ttl = (expire-time(NULL));
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) {
}
listTypeReleaseIterator(li);
} else if (o->type == REDIS_SET) {
dict *set = o->ptr;
dictIterator *di = dictGetIterator(set);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetEntryKey(de);
xorObjectDigest(digest,eleobj);
setTypeIterator *si = setTypeInitIterator(o);
robj *ele;
while((ele = setTypeNext(si)) != NULL) {
xorObjectDigest(digest,ele);
decrRefCount(ele);
}
dictReleaseIterator(di);
setTypeReleaseIterator(si);
} else if (o->type == REDIS_ZSET) {
zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict);
......@@ -214,18 +211,18 @@ void debugCommand(redisClient *c) {
char *strenc;
strenc = strEncoding(val->encoding);
addReplySds(c,sdscatprintf(sdsempty(),
"+Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld\r\n",
addReplyStatusFormat(c,
"Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld",
(void*)val, val->refcount,
strenc, (long long) rdbSavedObjectLen(val,NULL)));
strenc, (long long) rdbSavedObjectLen(val,NULL));
} else {
vmpointer *vp = (vmpointer*) val;
addReplySds(c,sdscatprintf(sdsempty(),
"+Value swapped at: page %llu "
"using %llu pages\r\n",
addReplyStatusFormat(c,
"Value swapped at: page %llu "
"using %llu pages",
(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) {
lookupKeyRead(c->db,c->argv[2]);
......@@ -236,7 +233,7 @@ void debugCommand(redisClient *c) {
vmpointer *vp;
if (!server.vm_enabled) {
addReplySds(c,sdsnew("-ERR Virtual Memory is disabled\r\n"));
addReplyError(c,"Virtual Memory is disabled");
return;
}
if (!de) {
......@@ -246,9 +243,9 @@ void debugCommand(redisClient *c) {
val = dictGetEntryVal(de);
/* Swap it */
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) {
addReplySds(c,sdsnew("-ERR Object is shared\r\n"));
addReplyError(c,"Object is shared");
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
dictGetEntryVal(de) = vp;
addReply(c,shared.ok);
......@@ -277,18 +274,17 @@ void debugCommand(redisClient *c) {
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
unsigned char digest[20];
sds d = sdsnew("+");
sds d = sdsempty();
int j;
computeDatasetDigest(digest);
for (j = 0; j < 20; j++)
d = sdscatprintf(d, "%02x",digest[j]);
d = sdscatlen(d,"\r\n",2);
addReplySds(c,d);
addReplyStatus(c,d);
sdsfree(d);
} else {
addReplySds(c,sdsnew(
"-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]\r\n"));
addReplyError(c,
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
}
}
......
......@@ -49,8 +49,13 @@
/* Using dictEnableResize() / dictDisableResize() we make possible to
* 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
* 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 unsigned int dict_force_resize_ratio = 5;
/* -------------------------- private prototypes ---------------------------- */
......@@ -125,7 +130,7 @@ int _dictInit(dict *d, dictType *type,
}
/* 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 minimal;
......@@ -493,14 +498,23 @@ dictEntry *dictGetRandomKey(dict *d)
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
/* If the hash table is empty expand it to the intial size,
* if the table is "full" dobule its size. */
/* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK;
if (d->ht[0].size == 0)
return dictExpand(d, DICT_HT_INITIAL_SIZE);
if (d->ht[0].used >= d->ht[0].size && dict_can_resize)
/* If the hash table is empty expand it to the intial size. */
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) ?
d->ht[0].size : d->ht[0].used)*2);
}
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) {
void multiCommand(redisClient *c) {
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;
}
c->flags |= REDIS_MULTI;
......@@ -51,7 +51,7 @@ void multiCommand(redisClient *c) {
void discardCommand(redisClient *c) {
if (!(c->flags & REDIS_MULTI)) {
addReplySds(c,sdsnew("-ERR DISCARD without MULTI\r\n"));
addReplyError(c,"DISCARD without MULTI");
return;
}
......@@ -82,7 +82,7 @@ void execCommand(redisClient *c) {
int orig_argc;
if (!(c->flags & REDIS_MULTI)) {
addReplySds(c,sdsnew("-ERR EXEC without MULTI\r\n"));
addReplyError(c,"EXEC without MULTI");
return;
}
......@@ -107,7 +107,7 @@ void execCommand(redisClient *c) {
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv;
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++) {
c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv;
......@@ -251,7 +251,7 @@ void watchCommand(redisClient *c) {
int j;
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;
}
for (j = 1; j < c->argc; j++)
......
#include "redis.h"
#include <sys/uio.h>
void *dupClientReplyValue(void *o) {
......@@ -12,14 +11,24 @@ int listMatchObjects(void *a, void *b) {
}
redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(*c));
redisClient *c = zmalloc(sizeof(redisClient));
c->bufpos = 0;
anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd);
if (!c) return NULL;
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
close(fd);
zfree(c);
return NULL;
}
selectDb(c,0);
c->fd = fd;
c->querybuf = sdsempty();
c->newline = NULL;
c->argc = 0;
c->argv = NULL;
c->bulklen = -1;
......@@ -43,80 +52,254 @@ redisClient *createClient(int fd) {
c->pubsub_patterns = listCreate();
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
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);
initClientMultiState(c);
return c;
}
void addReply(redisClient *c, robj *obj) {
if (listLength(c->reply) == 0 &&
int _installWriteEvent(redisClient *c) {
if (c->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
c->replstate == REDIS_REPL_ONLINE) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c) == AE_ERR) return;
sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
return REDIS_OK;
}
/* Create a duplicate of the last object in the reply list when
* it is not exclusively owned by the reply list. */
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;
}
return listNodeValue(ln);
}
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
size_t available = sizeof(c->buf)-c->bufpos;
if (server.vm_enabled && obj->storage != REDIS_VM_MEMORY) {
obj = dupStringObject(obj);
obj->refcount = 0; /* getDecodedObject() will increment the refcount */
/* 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 _addReplyObjectToList(redisClient *c, robj *o) {
robj *tail;
if (listLength(c->reply) == 0) {
incrRefCount(o);
listAddNodeTail(c->reply,o);
} else {
tail = listNodeValue(listLast(c->reply));
/* Append to this object when possible. */
if (tail->ptr != NULL &&
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);
}
}
listAddNodeTail(c->reply,getDecodedObject(obj));
}
void addReplySds(redisClient *c, sds s) {
robj *o = createObject(REDIS_STRING,s);
addReply(c,o);
decrRefCount(o);
/* This method takes responsibility over the sds. When it is no longer
* needed it will be free'd, otherwise it ends up in a robj. */
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));
/* Append to this object when possible. */
if (tail->ptr != NULL &&
sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES)
{
tail = dupLastObjectIfNeeded(c->reply);
tail->ptr = sdscatlen(tail->ptr,s,sdslen(s));
sdsfree(s);
} else {
listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
}
}
}
void addReplyDouble(redisClient *c, double d) {
char buf[128];
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));
snprintf(buf,sizeof(buf),"%.17g",d);
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n%s\r\n",
(unsigned long) strlen(buf),buf));
/* 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 addReplyLongLong(redisClient *c, long long ll) {
char buf[128];
size_t 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);
}
}
if (ll == 0) {
addReply(c,shared.czero);
return;
} else if (ll == 1) {
addReply(c,shared.cone);
void addReplySds(redisClient *c, sds s) {
if (_installWriteEvent(c) != REDIS_OK) {
/* The caller expects the sds to be free'd. */
sdsfree(s);
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);
buf[len+1] = '\r';
buf[len+2] = '\n';
addReplySds(c,sdsnewlen(buf,len+3));
addReplyString(c,buf,len+3);
}
void addReplyUlong(redisClient *c, unsigned long ul) {
char buf[128];
size_t len;
void addReplyLongLong(redisClient *c, long long ll) {
_addReplyLongLong(c,ll,':');
}
if (ul == 0) {
addReply(c,shared.czero);
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 addReplyMultiBulkLen(redisClient *c, long length) {
_addReplyLongLong(c,length,'*');
}
void addReplyBulkLen(redisClient *c, robj *obj) {
size_t len, intlen;
char buf[128];
size_t len;
if (obj->encoding == REDIS_ENCODING_RAW) {
len = sdslen(obj->ptr);
......@@ -133,11 +316,7 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
len++;
}
}
buf[0] = '$';
intlen = ll2string(buf+1,sizeof(buf)-1,(long long)len);
buf[intlen+1] = '\r';
buf[intlen+2] = '\n';
addReplySds(c,sdsnewlen(buf,intlen+3));
_addReplyLongLong(c,len,'$');
}
void addReplyBulk(redisClient *c, robj *obj) {
......@@ -275,7 +454,8 @@ void freeClient(redisClient *c) {
server.vm_blocked_clients--;
}
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->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
close(c->repldbfd);
......@@ -284,9 +464,20 @@ void freeClient(redisClient *c) {
redisAssert(ln != NULL);
listDelNode(l,ln);
}
/* Case 2: we lost the connection with the master. */
if (c->flags & REDIS_MASTER) {
server.master = NULL;
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 */
zfree(c->argv);
......@@ -295,34 +486,6 @@ void freeClient(redisClient *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) {
redisClient *c = privdata;
int nwritten = 0, totwritten = 0, objlen;
......@@ -339,10 +502,25 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
while(listLength(c->reply)) {
if (server.glueoutputbuf && listLength(c->reply) > 1)
glueReplyBuffersIfNeeded(c);
while(c->bufpos > 0 || listLength(c->reply)) {
if (c->bufpos > 0) {
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);
......@@ -355,16 +533,18 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Don't reply to a master */
nwritten = objlen - c->sentlen;
} else {
nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen);
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
* bytes, in a single threaded server it's a good idea to serve
* other clients as well, even if a very large request comes from
......@@ -472,6 +652,7 @@ void resetClient(redisClient *c) {
freeClientArgv(c);
c->bulklen = -1;
c->multibulk = 0;
c->newline = NULL;
}
void closeTimedoutClients(void) {
......@@ -486,6 +667,7 @@ void closeTimedoutClients(void) {
if (server.maxidletime &&
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
!(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 */
listLength(c->pubsub_patterns) == 0 &&
(now - c->lastinteraction > server.maxidletime))
......@@ -502,6 +684,8 @@ void closeTimedoutClients(void) {
}
void processInputBuffer(redisClient *c) {
int seeknewline = 0;
again:
/* Before to process the input buffer, make sure the client is not
* waitig for a blocking operation such as BLPOP. Note that the first
......@@ -510,15 +694,19 @@ 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. */
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) {
/* Read the first line of the query */
char *p = strchr(c->querybuf,'\n');
size_t querylen;
if (p) {
if (c->newline) {
char *p = c->newline;
sds query, *argv;
int argc, j;
c->newline = NULL;
query = c->querybuf;
c->querybuf = sdsempty();
querylen = 1+(p-(query));
......@@ -605,8 +793,14 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
if (nread) {
size_t oldlen = sdslen(c->querybuf);
c->querybuf = sdscatlen(c->querybuf, buf, nread);
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 {
return;
}
......
......@@ -74,7 +74,16 @@ robj *createZiplistObject(void) {
robj *createSetObject(void) {
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) {
......@@ -115,7 +124,16 @@ void freeListObject(robj *o) {
}
void freeSetObject(robj *o) {
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) {
......@@ -336,9 +354,9 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch
double value;
if (getDoubleFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
addReplyError(c,(char*)msg);
} else {
addReplySds(c, sdsnew("-ERR value is not a double\r\n"));
addReplyError(c,"value is not a double");
}
return REDIS_ERR;
}
......@@ -358,6 +376,8 @@ int getLongLongFromObject(robj *o, long long *target) {
if (o->encoding == REDIS_ENCODING_RAW) {
value = strtoll(o->ptr, &eptr, 10);
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) {
value = (long)o->ptr;
} else {
......@@ -365,7 +385,7 @@ int getLongLongFromObject(robj *o, long long *target) {
}
}
*target = value;
if (target) *target = value;
return REDIS_OK;
}
......@@ -373,9 +393,9 @@ int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, con
long long value;
if (getLongLongFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
addReplyError(c,(char*)msg);
} 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;
}
......@@ -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 (value < LONG_MIN || value > LONG_MAX) {
if (msg != NULL) {
addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
addReplyError(c,(char*)msg);
} else {
addReplySds(c, sdsnew("-ERR value is out of range\r\n"));
addReplyError(c,"value is out of range");
}
return REDIS_ERR;
}
......@@ -409,6 +429,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_ZIPMAP: return "zipmap";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset";
default: return "unknown";
}
}
......@@ -260,6 +260,7 @@ int rdbSaveObject(FILE *fp, robj *o) {
}
} else if (o->type == REDIS_SET) {
/* Save a set value */
if (o->encoding == REDIS_ENCODING_HT) {
dict *set = o->ptr;
dictIterator *di = dictGetIterator(set);
dictEntry *de;
......@@ -267,10 +268,21 @@ int rdbSaveObject(FILE *fp, robj *o) {
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");
}
} else if (o->type == REDIS_ZSET) {
/* Save a set value */
zset *zs = o->ptr;
......@@ -445,6 +457,7 @@ int rdbSaveBackground(char *filename) {
if (server.bgsavechildpid != -1) return REDIS_ERR;
if (server.vm_enabled) waitEmptyIOJobsQueue();
server.dirty_before_bgsave = server.dirty;
if ((childpid = fork()) == 0) {
/* Child */
if (server.vm_enabled) vmReopenSwapFile();
......@@ -629,6 +642,7 @@ int rdbLoadDoubleValue(FILE *fp, double *val) {
robj *rdbLoadObject(int type, FILE *fp) {
robj *o, *ele, *dec;
size_t len;
unsigned int i;
redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp));
if (type == REDIS_STRING) {
......@@ -670,16 +684,41 @@ robj *rdbLoadObject(int type, FILE *fp) {
} else if (type == REDIS_SET) {
/* Read list/set value */
if ((len = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL;
/* Use a regular set when there are too many entries. */
if (len > server.set_max_intset_entries) {
o = createSetObject();
/* 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 */
while(len--) {
for (i = 0; i < len; i++) {
long long llval;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele);
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) {
/* Read list/set value */
......@@ -692,13 +731,14 @@ robj *rdbLoadObject(int type, FILE *fp) {
/* Load every single element of the list/set */
while(zsetlen--) {
robj *ele;
double *score = zmalloc(sizeof(double));
double score;
zskiplistNode *znode;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele);
if (rdbLoadDoubleValue(fp,score) == -1) return NULL;
dictAdd(zs->dict,ele,score);
zslInsert(zs->zsl,*score,ele);
if (rdbLoadDoubleValue(fp,&score) == -1) return NULL;
znode = zslInsert(zs->zsl,score,ele);
dictAdd(zs->dict,ele,&znode->score);
incrRefCount(ele); /* added to skiplist */
}
} else if (type == REDIS_HASH) {
......@@ -876,7 +916,7 @@ void backgroundSaveDoneHandler(int statloc) {
if (!bysignal && exitcode == 0) {
redisLog(REDIS_NOTICE,
"Background saving terminated with success");
server.dirty = 0;
server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL);
} else if (!bysignal && exitcode != 0) {
redisLog(REDIS_WARNING, "Background saving error");
......
......@@ -76,6 +76,7 @@ static struct config {
long long start;
long long totlatency;
int *latency;
char *title;
list *clients;
int quiet;
int loop;
......@@ -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)
{
char buf[1024];
int nread;
char buf[1024], *p;
int nread, pos=0, len=0;
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
nread = read(c->fd, buf, 1024);
nread = read(c->fd,buf,sizeof(buf));
if (nread == -1) {
fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
freeClient(c);
......@@ -229,82 +241,89 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
}
c->totreceived += 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 ||
c->replytype == REPLY_RETCODE ||
(c->replytype == REPLY_BULK && c->readlen == -1) ||
(c->replytype == REPLY_MBULK && c->readlen == -1) ||
(c->replytype == REPLY_MBULK && c->mbulk == -1)) {
char *p;
/* Check if the first line is complete. This is only true if
* there is a newline inside the buffer. */
if ((p = strchr(c->ibuf,'\n')) != NULL) {
if (c->replytype == REPLY_BULK ||
(c->replytype == REPLY_MBULK && c->mbulk != -1))
c->replytype == REPLY_RETCODE)
{
/* Read the count of a bulk reply (being it a single bulk or
* a multi bulk reply). "$<count>" for the protocol spec. */
*p = '\0';
*(p-1) = '\0';
c->readlen = atoi(c->ibuf+1)+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;
/* Check if the first line is complete. This is everything we need
* when waiting for an integer or status code reply.*/
if ((p = strstr(c->ibuf,"\r\n")) != NULL)
goto done;
} else if (c->replytype == REPLY_BULK) {
int advance = 0;
if (c->readlen < 0) {
advance = readLen(c->ibuf+pos,&c->readlen);
if (advance) {
pos += advance;
if (c->readlen == -1) {
goto done;
} else {
/* include the trailing \r\n */
c->readlen += 2;
}
// 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 {
c->ibuf = sdstrim(c->ibuf,"\r\n");
clientDone(c);
return;
goto skip;
}
}
int canconsume;
if (c->readlen > 0) {
canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
c->readlen -= canconsume;
pos += canconsume;
}
/* bulk read, did we read everything? */
if (((c->replytype == REPLY_MBULK && c->mbulk != -1) ||
(c->replytype == REPLY_BULK)) && c->readlen != -1 &&
(unsigned)c->readlen <= sdslen(c->ibuf))
{
// printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n",
// c->mbulk,c->readlen,sdslen(c->ibuf));
if (c->replytype == REPLY_BULK) {
clientDone(c);
if (c->readlen == 0)
goto done;
} else if (c->replytype == REPLY_MBULK) {
// printf("%p) %d (%d)) ",c, c->mbulk, c->readlen);
// fwrite(c->ibuf,c->readlen,1,stdout);
// printf("\n");
if (--c->mbulk == 0) {
clientDone(c);
int advance = 0;
if (c->mbulk == -1) {
advance = readLen(c->ibuf+pos,&c->mbulk);
if (advance) {
pos += advance;
if (c->mbulk == -1)
goto done;
} else {
c->ibuf = sdsrange(c->ibuf,c->readlen,-1);
c->readlen = -1;
goto processdata;
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 {
advance = readLen(c->ibuf+pos,&c->readlen);
if (advance) {
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)
......@@ -376,13 +395,13 @@ static void createMissingClients(client c) {
}
}
static void showLatencyReport(char *title) {
static void showLatencyReport(void) {
int j, seen = 0;
float perc, reqpersec;
reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
if (!config.quiet) {
printf("====== %s ======\n", title);
printf("====== %s ======\n", config.title);
printf(" %d requests completed in %.2f seconds\n", config.donerequests,
(float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients);
......@@ -398,20 +417,20 @@ static void showLatencyReport(char *title) {
}
printf("%.2f requests per second\n\n", reqpersec);
} 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));
config.title = title;
config.start = mstime();
config.donerequests = 0;
}
static void endBenchmark(char *title) {
static void endBenchmark(void) {
config.totlatency = mstime()-config.start;
showLatencyReport(title);
showLatencyReport();
freeAllClients();
}
......@@ -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) {
client c;
......@@ -500,6 +531,7 @@ int main(int argc, char **argv) {
config.requests = 10000;
config.liveclients = 0;
config.el = aeCreateEventLoop();
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
......@@ -524,7 +556,7 @@ int main(int argc, char **argv) {
if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
prepareForBenchmark();
prepareForBenchmark("IDLE");
c = createClient();
if (!c) exit(1);
c->obuf = sdsempty();
......@@ -535,25 +567,25 @@ int main(int argc, char **argv) {
}
do {
prepareForBenchmark();
prepareForBenchmark("PING");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"PING\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("PING");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("PING (multi bulk)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("PING (multi bulk)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("SET");
c = createClient();
if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
......@@ -567,106 +599,106 @@ int main(int argc, char **argv) {
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("SET");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("GET");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("GET");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("INCR");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
endBenchmark("INCR");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LPUSH");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPUSH");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LPOP");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPOP");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("SADD");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("SADD");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("SPOP");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("SPOP");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPUSH (again, in order to bench LRANGE)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 100 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 100 elements)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 300 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 300 elements)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 450 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 450 elements)");
endBenchmark();
prepareForBenchmark();
prepareForBenchmark("LRANGE (first 600 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
endBenchmark("LRANGE (first 600 elements)");
endBenchmark();
printf("\n");
} while(config.loop);
......
......@@ -65,8 +65,8 @@
/* data type to hold offset in file and size */
typedef struct {
void *data;
unsigned long size;
unsigned long offset;
size_t size;
size_t offset;
} pos;
static unsigned char level = 0;
......@@ -77,8 +77,8 @@ static pos positions[16];
/* Hold a stack of errors */
typedef struct {
char error[16][1024];
unsigned long offset[16];
unsigned int level;
size_t offset[16];
size_t level;
} errors_t;
static errors_t errors;
......@@ -112,7 +112,7 @@ int readBytes(void *target, long num) {
if (p.offset + num > p.size) {
return 0;
} 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;
}
return 1;
......@@ -494,15 +494,17 @@ void printCentered(int indent, int width, char* body) {
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];
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);
}
void printSkipped(int bytes, int offset) {
void printSkipped(uint64_t bytes, uint64_t offset) {
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);
}
......@@ -541,7 +543,7 @@ void printErrorStack(entry *e) {
}
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;
processHeader();
......@@ -558,7 +560,9 @@ void process() {
num_valid_bytes = 0;
/* 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) {
positions[1].offset = offset;
......@@ -606,9 +610,10 @@ void process() {
}
/* print summary on errors */
if (num_errors > 0) {
if (num_errors) {
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) {
}
int fd;
unsigned long size;
off_t size;
struct stat stat;
void *data;
......@@ -634,6 +639,10 @@ int main(int argc, char **argv) {
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);
if (data == MAP_FAILED) {
ERROR("Cannot mmap: %s\n", argv[1]);
......
......@@ -36,6 +36,8 @@
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include "anet.h"
#include "sds.h"
......@@ -55,12 +57,14 @@ static struct config {
char *hostsocket;
long repeat;
int dbnum;
int argn_from_stdin;
int interactive;
int shutdown;
int monitor_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 *historyfile;
} config;
......@@ -68,19 +72,18 @@ static struct config {
static int cliReadReply(int fd);
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];
static int fd = ANET_ERR;
if (fd == ANET_ERR) {
if (fd == ANET_ERR || force) {
if (force) close(fd);
if (config.hostsocket == NULL) {
fd = anetTcpConnect(err,config.hostip,config.hostport);
} else {
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) {
fprintf(stderr,"Could not connect to Redis at ");
......@@ -103,7 +106,7 @@ static sds cliReadLine(int fd) {
ssize_t ret;
ret = read(fd,&c,1);
if (ret == -1) {
if (ret <= 0) {
sdsfree(line);
return NULL;
} else if ((ret == 0) || (c == '\n')) {
......@@ -120,7 +123,7 @@ static int cliReadSingleLineReply(int fd, int quiet) {
if (reply == NULL) return 1;
if (!quiet)
printf("%s\n", reply);
printf("%s", reply);
sdsfree(reply);
return 0;
}
......@@ -147,7 +150,7 @@ static void printStringRepr(char *s, int len) {
}
s++;
}
printf("\"\n");
printf("\"");
}
static int cliReadBulkReply(int fd) {
......@@ -165,7 +168,7 @@ static int cliReadBulkReply(int fd) {
reply = zmalloc(bulklen);
anetRead(fd,reply,bulklen);
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) {
zfree(reply);
return 1;
......@@ -182,6 +185,7 @@ static int cliReadBulkReply(int fd) {
static int cliReadMultiBulkReply(int fd) {
sds replylen = cliReadLine(fd);
int elements, c = 1;
int retval = 0;
if (replylen == NULL) return 1;
elements = atoi(replylen);
......@@ -194,36 +198,45 @@ static int cliReadMultiBulkReply(int fd) {
printf("(empty list or set)\n");
}
while(elements--) {
printf("%d. ", c);
if (cliReadReply(fd)) return 1;
if (config.tty) printf("%d. ", c);
if (cliReadReply(fd)) retval = 1;
if (elements) printf("%c",config.mb_sep);
c++;
}
return 0;
return retval;
}
static int cliReadReply(int fd) {
char type;
int nread;
if (anetRead(fd,&type,1) <= 0) {
if ((nread = anetRead(fd,&type,1)) <= 0) {
if (config.shutdown) return 0;
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) {
case '-':
printf("(error) ");
if (config.tty) printf("(error) ");
cliReadSingleLineReply(fd,0);
return 1;
case '+':
return cliReadSingleLineReply(fd,0);
case ':':
printf("(integer) ");
if (config.tty) printf("(integer) ");
return cliReadSingleLineReply(fd,0);
case '$':
return cliReadBulkReply(fd);
case '*':
return cliReadMultiBulkReply(fd);
default:
printf("protocol error, got '%c' as reply type byte\n", type);
printf("protocol error, got '%c' as reply type byte", type);
return 1;
}
}
......@@ -248,17 +261,37 @@ static int selectDb(int fd) {
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) {
char *command = argv[0];
int fd, j, retval = 0;
sds cmd;
config.raw_output = !strcasecmp(command,"info");
if (!strcasecmp(command,"help")) {
showInteractiveHelp();
return 0;
}
if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
if (!strcasecmp(command,"subscribe") ||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
if ((fd = cliConnect()) == -1) return 1;
if ((fd = cliConnect(0)) == -1) return 1;
/* Select db number */
retval = selectDb(fd);
......@@ -279,21 +312,21 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
while(repeat--) {
anetWrite(fd,cmd,sdslen(cmd));
while (config.monitor_mode) {
cliReadSingleLineReply(fd,0);
if (cliReadSingleLineReply(fd,0)) exit(1);
printf("\n");
}
if (config.pubsub_mode) {
printf("Reading messages... (press Ctrl-c to quit)\n");
while (1) {
cliReadReply(fd);
printf("\n");
printf("\n\n");
}
}
retval = cliReadReply(fd);
if (retval) {
return retval;
}
if (!config.raw_output && config.tty) printf("\n");
if (retval) return retval;
}
return 0;
}
......@@ -314,6 +347,8 @@ static int parseOptions(int argc, char **argv) {
i++;
} else if (!strcmp(argv[i],"-h") && lastarg) {
usage();
} else if (!strcmp(argv[i],"-x")) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[i+1]);
i++;
......@@ -330,11 +365,18 @@ static int parseOptions(int argc, char **argv) {
config.auth = argv[i+1];
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")) {
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")) {
printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
printf("redis-cli shipped with Redis version %s\n", REDIS_VERSION);
exit(0);
} else {
break;
......@@ -362,9 +404,8 @@ static sds readArgFromStdin(void) {
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: 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, "\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 set my_passwd\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set 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, "\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) {
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
static void repl() {
int argc, j;
char *line, **argv;
char *line;
sds *argv;
config.interactive = 1;
while((line = linenoise("redis> ")) != NULL) {
if (line[0] != '\0') {
argv = splitArguments(line,&argc);
argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line);
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 ||
strcasecmp(argv[0],"exit") == 0)
{
exit(0);
else
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 */
for (j = 0; j < argc; j++)
......@@ -475,23 +468,37 @@ static void repl() {
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 firstarg;
char **argvcopy;
config.hostip = "127.0.0.1";
config.hostport = 6379;
config.hostsocket = NULL;
config.repeat = 1;
config.dbnum = 0;
config.argn_from_stdin = 0;
config.shutdown = 0;
config.interactive = 0;
config.shutdown = 0;
config.monitor_mode = 0;
config.pubsub_mode = 0;
config.raw_output = 0;
config.stdinarg = 0;
config.auth = NULL;
config.historyfile = NULL;
config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
config.mb_sep = '\n';
if (getenv("HOME") != NULL) {
config.historyfile = malloc(256);
......@@ -505,19 +512,20 @@ int main(int argc, char **argv) {
if (config.auth != NULL) {
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[1] = config.auth;
cliSendCommand(2, convertToSds(2, authargv), 1);
config.dbnum = dbnum; /* restore the right DB number */
}
if (argc == 0 || config.interactive == 1) repl();
argvcopy = convertToSds(argc+1, argv);
if (config.argn_from_stdin) {
sds lastarg = readArgFromStdin();
argvcopy[argc] = lastarg;
argc++;
}
return cliSendCommand(argc, argvcopy, config.repeat);
/* Start interactive mode when no command is provided */
if (argc == 0) repl();
/* Otherwise, we have some arguments to execute */
return noninteractive(argc,convertToSds(argc,argv));
}
......@@ -51,6 +51,7 @@
#include <float.h>
#include <math.h>
#include <pthread.h>
#include <sys/resource.h>
/* Our shared "common" objects */
......@@ -170,6 +171,7 @@ struct redisCommand readonlyCommandTable[] = {
{"info",infoCommand,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},
{"persist",persistCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"slaveof",slaveofCommand,3,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},
......@@ -338,7 +340,7 @@ dictType zsetDictType = {
NULL, /* val dup */
dictEncObjKeyCompare, /* key compare */
dictRedisObjectDestructor, /* key destructor */
dictVanillaFree /* val destructor of malloc(sizeof(double)) */
NULL /* val destructor */
};
/* Db->dict, keys are sds strings, vals are Redis objects. */
......@@ -435,6 +437,48 @@ void updateDictResizePolicy(void) {
/* ======================= 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 j, loops = server.cronloops++;
REDIS_NOTUSED(eventLoop);
......@@ -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
* 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. */
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);
}
/* Expire a few keys per cycle, only if this is a master.
* On slaves we wait for DEL operations synthesized by the master
* in order to guarantee a strict consistency. */
if (server.masterhost == NULL) activeExpireCycle();
/* 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. */
......@@ -734,6 +747,7 @@ void initServerConfig() {
server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
server.shutdown_asap = 0;
resetServerSaveParams();
......@@ -892,9 +906,6 @@ void call(redisClient *c, struct redisCommand *cmd) {
int processCommand(redisClient *c) {
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
* supported by Redis in order to receive commands that are composed of
* multiple binary-safe "bulk" arguments. The latency of processing is
......@@ -913,15 +924,20 @@ int processCommand(redisClient *c) {
} else if (c->multibulk) {
if (c->bulklen == -1) {
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);
return 1;
} 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]);
if (bulklen < 0 || bulklen > 1024*1024*1024) {
if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
bulklen < 0 || bulklen > 1024*1024*1024)
{
c->argc--;
addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
addReplyError(c,"invalid bulk write count");
resetClient(c);
return 1;
}
......@@ -974,27 +990,28 @@ int processCommand(redisClient *c) {
* such wrong arity, bad command name and so forth. */
cmd = lookupCommand(c->argv[0]->ptr);
if (!cmd) {
addReplySds(c,
sdscatprintf(sdsempty(), "-ERR unknown command '%s'\r\n",
(char*)c->argv[0]->ptr));
addReplyErrorFormat(c,"unknown command '%s'",
(char*)c->argv[0]->ptr);
resetClient(c);
return 1;
} else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
(c->argc < -cmd->arity)) {
addReplySds(c,
sdscatprintf(sdsempty(),
"-ERR wrong number of arguments for '%s' command\r\n",
cmd->name));
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
cmd->name);
resetClient(c);
return 1;
} else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) {
/* 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]);
if (bulklen < 0 || bulklen > 1024*1024*1024) {
if (perr || bulklen == LONG_MAX || bulklen == LONG_MIN ||
bulklen < 0 || bulklen > 1024*1024*1024)
{
c->argc--;
addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
addReplyError(c,"invalid bulk write count");
resetClient(c);
return 1;
}
......@@ -1021,16 +1038,21 @@ int processCommand(redisClient *c) {
/* Check if the user is authenticated */
if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
addReplySds(c,sdsnew("-ERR operation not permitted\r\n"));
addReplyError(c,"operation not permitted");
resetClient(c);
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) &&
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);
return 1;
}
......@@ -1040,7 +1062,7 @@ int processCommand(redisClient *c) {
&&
cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand &&
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);
return 1;
}
......@@ -1081,11 +1103,7 @@ int prepareForShutdown() {
if (server.vm_enabled) unlink(server.vm_swap_file);
} else {
/* Snapshotting. Perform a SYNC SAVE and exit */
if (rdbSave(server.dbfilename) == REDIS_OK) {
if (server.daemonize)
unlink(server.pidfile);
redisLog(REDIS_WARNING,"%zu bytes used at exit",zmalloc_used_memory());
} else {
if (rdbSave(server.dbfilename) != REDIS_OK) {
/* Ooops.. error saving! The best we can do is to continue
* operating. Note that if there was a background saving process,
* in the next cron() Redis will be notified that the background
......@@ -1095,6 +1113,7 @@ int prepareForShutdown() {
return REDIS_ERR;
}
}
if (server.daemonize) unlink(server.pidfile);
redisLog(REDIS_WARNING,"Server exit now, bye bye...");
return REDIS_OK;
}
......@@ -1107,7 +1126,7 @@ void authCommand(redisClient *c) {
addReply(c,shared.ok);
} else {
c->authenticated = 0;
addReplySds(c,sdscatprintf(sdsempty(),"-ERR invalid password\r\n"));
addReplyError(c,"invalid password");
}
}
......@@ -1148,6 +1167,10 @@ sds genRedisInfoString(void) {
time_t uptime = time(NULL)-server.stat_starttime;
int j;
char hmem[64];
struct rusage self_ru, c_ru;
getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru);
bytesToHuman(hmem,zmalloc_used_memory());
info = sdscatprintf(sdsempty(),
......@@ -1159,11 +1182,16 @@ sds genRedisInfoString(void) {
"process_id:%ld\r\n"
"uptime_in_seconds:%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_slaves:%d\r\n"
"blocked_clients:%d\r\n"
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
"changes_since_last_save:%lld\r\n"
"bgsave_in_progress:%d\r\n"
"last_save_time:%ld\r\n"
......@@ -1185,11 +1213,16 @@ sds genRedisInfoString(void) {
(long) getpid(),
uptime,
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.slaves),
server.blpop_blocked_clients,
zmalloc_used_memory(),
hmem,
zmalloc_get_fragmentation_ratio(),
server.dirty,
server.bgsavechildpid != -1,
server.lastsave,
......@@ -1319,7 +1352,8 @@ void freeMemoryIfNeeded(void) {
if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue;
for (j = 0; j < server.dbnum; j++) {
int minttl = -1;
robj *minkey = NULL;
sds minkey = NULL;
robj *keyobj = NULL;
struct dictEntry *de;
if (dictSize(server.db[j].expires)) {
......@@ -1336,7 +1370,10 @@ void freeMemoryIfNeeded(void) {
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... */
......@@ -1367,9 +1404,17 @@ void linuxOvercommitMemoryWarning(void) {
}
#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) {
int fd;
FILE *fp;
if (fork() != 0) exit(0); /* parent exits */
setsid(); /* create a new session */
......@@ -1383,12 +1428,6 @@ void daemonize(void) {
dup2(fd, STDERR_FILENO);
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() {
......@@ -1421,6 +1460,7 @@ int main(int argc, char **argv) {
}
if (server.daemonize) daemonize();
initServer();
if (server.daemonize) createPidFile();
redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__
linuxOvercommitMemoryWarning();
......@@ -1500,6 +1540,7 @@ void segvHandler(int sig, siginfo_t *info, void *secret) {
redisLog(REDIS_WARNING,"%s", messages[i]);
/* free(messages); Don't call free() with possibly corrupted memory. */
if (server.daemonize) unlink(server.pidfile);
_exit(0);
}
......
......@@ -26,6 +26,7 @@
#include "anet.h" /* Networking the easy way */
#include "zipmap.h" /* Compact string -> string data structure */
#include "ziplist.h" /* Compact list data structure */
#include "intset.h" /* Compact integer set structure */
#include "version.h"
/* Error codes */
......@@ -46,6 +47,7 @@
#define REDIS_MAX_WRITE_PER_EVENT (1024*64)
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
#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 */
#define REDIS_WRITEV_THRESHOLD 3
......@@ -82,6 +84,7 @@
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME 253
......@@ -188,6 +191,7 @@
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
#define REDIS_SET_MAX_INTSET_ENTRIES 4096
/* Sets operations codes */
#define REDIS_OP_UNION 0
......@@ -282,8 +286,9 @@ typedef struct redisClient {
int dictid;
sds querybuf;
robj **argv, **mbargv;
char *newline; /* pointing to the detected newline in querybuf */
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 */
list *reply;
int sentlen;
......@@ -306,6 +311,10 @@ typedef struct redisClient {
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels 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;
struct saveparam {
......@@ -335,6 +344,7 @@ struct redisServer {
int sofd;
redisDb *db;
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 *slaves, *monitors;
char neterr[ANET_ERR_LEN];
......@@ -400,6 +410,7 @@ struct redisServer {
size_t hash_max_zipmap_value;
size_t list_max_ziplist_entries;
size_t list_max_ziplist_value;
size_t set_max_intset_entries;
/* Virtual memory state */
FILE *vm_fp;
int vm_fd;
......@@ -482,13 +493,14 @@ typedef struct _redisSortOperation {
} redisSortOperation;
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
struct zskiplistNode **forward;
struct zskiplistNode *backward;
unsigned int *span;
double score;
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
} zskiplistNode;
typedef struct zskiplist {
......@@ -537,6 +549,14 @@ typedef struct {
listNode *ln; /* Entry in linked list */
} 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
* hashes involves both fields and values. Because it is possible that
* not both are required, store pointers in the iterator to avoid
......@@ -577,6 +597,8 @@ void resetClient(redisClient *c);
void sendReplyToClient(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 *addDeferredMultiBulkLength(redisClient *c);
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
void addReplySds(redisClient *c, sds s);
void processInputBuffer(redisClient *c);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
......@@ -587,11 +609,23 @@ void addReplyBulkCString(redisClient *c, char *s);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
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 addReplyLongLong(redisClient *c, long long ll);
void addReplyUlong(redisClient *c, unsigned long ul);
void addReplyMultiBulkLen(redisClient *c, long length);
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 */
void listTypeTryConversion(robj *subject, robj *value);
void listTypePush(robj *subject, robj *value, int where);
......@@ -636,6 +670,7 @@ robj *createStringObjectFromLongLong(long long value);
robj *createListObject(void);
robj *createZiplistObject(void);
robj *createSetObject(void);
robj *createIntsetObject(void);
robj *createHashObject(void);
robj *createZsetObject(void);
int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg);
......@@ -677,7 +712,7 @@ void backgroundRewriteDoneHandler(int statloc);
/* Sorted sets data type */
zskiplist *zslCreate(void);
void zslFree(zskiplist *zsl);
void zslInsert(zskiplist *zsl, double score, robj *obj);
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
/* Core functions */
void freeMemoryIfNeeded(void);
......@@ -719,6 +754,18 @@ int dontWaitForSwappedKey(redisClient *c, robj *key);
void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
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 */
void convertToRealHash(robj *o);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
......@@ -747,6 +794,8 @@ int stringmatch(const char *pattern, const char *string, int nocase);
long long memtoll(const char *p, int *err);
int ll2string(char *s, size_t len, long long value);
int isStringRepresentableAsLong(sds s, long *longval);
int isStringRepresentableAsLongLong(sds s, long long *longval);
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
/* Configuration */
void loadServerConfig(char *filename);
......@@ -755,10 +804,10 @@ void resetServerSaveParams();
/* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key);
void propagateExpire(redisDb *db, robj *key);
int expireIfNeeded(redisDb *db, robj *key);
int deleteIfVolatile(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 *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key);
......@@ -841,6 +890,7 @@ void expireCommand(redisClient *c);
void expireatCommand(redisClient *c);
void getsetCommand(redisClient *c);
void ttlCommand(redisClient *c);
void persistCommand(redisClient *c);
void slaveofCommand(redisClient *c);
void debugCommand(redisClient *c);
void msetCommand(redisClient *c);
......
......@@ -138,7 +138,7 @@ int syncRead(int fd, char *ptr, ssize_t size, int timeout) {
while(size) {
if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
nread = read(fd,ptr,size);
if (nread == -1) return -1;
if (nread <= 0) return -1;
ptr += nread;
size -= nread;
totread += nread;
......@@ -176,12 +176,19 @@ void syncCommand(redisClient *c) {
/* ignore SYNC if aleady slave or in monitor mode */
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
* the client about already issued commands. We need a fresh reply
* buffer registering the differences between the BGSAVE and the current
* dataset, so that we can copy to other slaves if needed. */
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;
}
......@@ -219,7 +226,7 @@ void syncCommand(redisClient *c) {
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
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;
}
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
......@@ -392,7 +399,12 @@ int syncWithMaster(void) {
strerror(errno));
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);
redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?");
return REDIS_ERR;
......@@ -416,9 +428,9 @@ int syncWithMaster(void) {
int nread, nwritten;
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",
strerror(errno));
(nread == -1) ? strerror(errno) : "connection lost");
close(fd);
close(dfd);
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