From: antirez Date: Thu, 28 Oct 2010 20:59:47 +0000 (+0200) Subject: merge conflict resolved X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/21dbc6499a538af07f52a41742cf1683f3fc9c23?hp=-c merge conflict resolved --- 21dbc6499a538af07f52a41742cf1683f3fc9c23 diff --combined src/Makefile index 6f09200e,3add2925..ba6ecf4d --- a/src/Makefile +++ b/src/Makefile @@@ -12,11 -12,6 +12,11 @@@ els CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF) CCLINK?= -lm -pthread endif + +ifeq ($(USE_TCMALLOC),yes) + CCLINK+= -ltcmalloc + CFLAGS+= -DUSE_TCMALLOC +endif CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF) DEBUG?= -g -rdynamic -ggdb @@@ -24,7 -19,7 +24,7 @@@ PREFIX= /usr/loca 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 intset.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 syncio.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 @@@ -38,6 -33,7 +38,6 @@@ CHECKAOFPRGNAME = redis-check-ao 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 @@@ -47,7 -43,6 +47,7 @@@ ae_select.o: ae_select. anet.o: anet.c fmacros.h anet.h aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h +chprgname.o: chprgname.c config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \ @@@ -85,7 -80,6 +85,7 @@@ sds.o: sds.c sds.h zmalloc. sha1.o: sha1.c sha1.h sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h +syncio.o: syncio.c 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 \ @@@ -132,7 -126,7 +132,7 @@@ dep $(CC) -MM *.c test: - (cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}") + (cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}") bench: ./redis-benchmark diff --combined src/aof.c index f7a0c453,36d97e70..4dbce394 --- a/src/aof.c +++ b/src/aof.c @@@ -266,9 -266,6 +266,6 @@@ int loadAppendOnlyFile(char *filename) redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr); exit(1); } - /* Try object encoding */ - if (cmd->flags & REDIS_CMD_BULK) - argv[argc-1] = tryObjectEncoding(argv[argc-1]); /* Run the command in the context of a fake client */ fakeClient->argc = argc; fakeClient->argv = argv; @@@ -314,6 -311,55 +311,6 @@@ fmterr exit(1); } -/* Write binary-safe string into a file in the bulkformat - * $\r\n\r\n */ -int fwriteBulkString(FILE *fp, char *s, unsigned long len) { - char cbuf[128]; - int clen; - cbuf[0] = '$'; - clen = 1+ll2string(cbuf+1,sizeof(cbuf)-1,len); - cbuf[clen++] = '\r'; - cbuf[clen++] = '\n'; - if (fwrite(cbuf,clen,1,fp) == 0) return 0; - if (len > 0 && fwrite(s,len,1,fp) == 0) return 0; - if (fwrite("\r\n",2,1,fp) == 0) return 0; - return 1; -} - -/* Write a double value in bulk format $\r\n\r\n */ -int fwriteBulkDouble(FILE *fp, double d) { - char buf[128], dbuf[128]; - - snprintf(dbuf,sizeof(dbuf),"%.17g\r\n",d); - snprintf(buf,sizeof(buf),"$%lu\r\n",(unsigned long)strlen(dbuf)-2); - if (fwrite(buf,strlen(buf),1,fp) == 0) return 0; - if (fwrite(dbuf,strlen(dbuf),1,fp) == 0) return 0; - return 1; -} - -/* Write a long value in bulk format $\r\n\r\n */ -int fwriteBulkLongLong(FILE *fp, long long l) { - char bbuf[128], lbuf[128]; - unsigned int blen, llen; - llen = ll2string(lbuf,32,l); - blen = snprintf(bbuf,sizeof(bbuf),"$%u\r\n%s\r\n",llen,lbuf); - if (fwrite(bbuf,blen,1,fp) == 0) return 0; - return 1; -} - -/* Delegate writing an object to writing a bulk string or bulk long long. */ -int fwriteBulkObject(FILE *fp, robj *obj) { - /* Avoid using getDecodedObject to help copy-on-write (we are often - * in a child process when this function is called). */ - if (obj->encoding == REDIS_ENCODING_INT) { - return fwriteBulkLongLong(fp,(long)obj->ptr); - } else if (obj->encoding == REDIS_ENCODING_RAW) { - return fwriteBulkString(fp,obj->ptr,sdslen(obj->ptr)); - } else { - redisPanic("Unknown string encoding"); - } -} - /* Write a sequence of commands able to fully rebuild the dataset into * "filename". Used both by REWRITEAOF and BGREWRITEAOF. */ int rewriteAppendOnlyFile(char *filename) { diff --combined src/config.c index 9655917c,c979162b..db58a236 --- a/src/config.c +++ b/src/config.c @@@ -123,27 -123,6 +123,27 @@@ void loadServerConfig(char *filename) server.maxclients = atoi(argv[1]); } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) { server.maxmemory = memtoll(argv[1],NULL); + } else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) { + if (!strcasecmp(argv[1],"volatile-lru")) { + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU; + } else if (!strcasecmp(argv[1],"volatile-random")) { + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM; + } else if (!strcasecmp(argv[1],"volatile-ttl")) { + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL; + } else if (!strcasecmp(argv[1],"allkeys-lru")) { + server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU; + } else if (!strcasecmp(argv[1],"allkeys-random")) { + server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM; + } else { + err = "Invalid maxmemory policy"; + goto loaderr; + } + } else if (!strcasecmp(argv[0],"maxmemory-samples") && argc == 2) { + server.maxmemory_samples = atoi(argv[1]); + if (server.maxmemory_samples <= 0) { + err = "maxmemory-samples must be 1 or greater"; + goto loaderr; + } } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) { server.masterhost = sdsnew(argv[1]); server.masterport = atoi(argv[2]); @@@ -246,8 -225,11 +246,11 @@@ loaderr *----------------------------------------------------------------------------*/ void configSetCommand(redisClient *c) { - robj *o = getDecodedObject(c->argv[3]); + robj *o; long long ll; + redisAssert(c->argv[2]->encoding == REDIS_ENCODING_RAW); + redisAssert(c->argv[3]->encoding == REDIS_ENCODING_RAW); + o = c->argv[3]; if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) { zfree(server.dbfilename); @@@ -263,24 -245,6 +266,24 @@@ ll < 0) goto badfmt; server.maxmemory = ll; if (server.maxmemory) freeMemoryIfNeeded(); + } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) { + if (!strcasecmp(o->ptr,"volatile-lru")) { + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU; + } else if (!strcasecmp(o->ptr,"volatile-random")) { + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM; + } else if (!strcasecmp(o->ptr,"volatile-ttl")) { + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL; + } else if (!strcasecmp(o->ptr,"allkeys-lru")) { + server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU; + } else if (!strcasecmp(o->ptr,"allkeys-random")) { + server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM; + } else { + goto badfmt; + } + } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) { + if (getLongLongFromObject(o,&ll) == REDIS_ERR || + ll <= 0) goto badfmt; + server.maxmemory_samples = ll; } else if (!strcasecmp(c->argv[2]->ptr,"timeout")) { if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0 || ll > LONG_MAX) goto badfmt; @@@ -312,7 -276,6 +315,6 @@@ if (startAppendOnly() == REDIS_ERR) { addReplyError(c, "Unable to turn on AOF. Check server logs."); - decrRefCount(o); return; } } @@@ -354,10 -317,8 +356,8 @@@ } else { addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", (char*)c->argv[2]->ptr); - decrRefCount(o); return; } - decrRefCount(o); addReply(c,shared.ok); return; @@@ -365,15 -326,14 +365,15 @@@ badfmt: /* Bad format errors * addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'", (char*)o->ptr, (char*)c->argv[2]->ptr); - decrRefCount(o); } void configGetCommand(redisClient *c) { - robj *o = getDecodedObject(c->argv[2]); + robj *o = c->argv[2]; void *replylen = addDeferredMultiBulkLength(c); char *pattern = o->ptr; + char buf[128]; int matches = 0; + redisAssert(o->encoding == REDIS_ENCODING_RAW); if (stringmatch(pattern,"dbfilename",0)) { addReplyBulkCString(c,"dbfilename"); @@@ -391,34 -351,17 +391,34 @@@ matches++; } if (stringmatch(pattern,"maxmemory",0)) { - char buf[128]; - - ll2string(buf,128,server.maxmemory); + ll2string(buf,sizeof(buf),server.maxmemory); addReplyBulkCString(c,"maxmemory"); addReplyBulkCString(c,buf); matches++; } - if (stringmatch(pattern,"timeout",0)) { - char buf[128]; + if (stringmatch(pattern,"maxmemory-policy",0)) { + char *s; - ll2string(buf,128,server.maxidletime); + switch(server.maxmemory_policy) { + case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break; + case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break; + case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break; + case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break; + case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break; + default: s = "unknown"; break; /* too harmless to panic */ + } + addReplyBulkCString(c,"maxmemory-policy"); + addReplyBulkCString(c,s); + matches++; + } + if (stringmatch(pattern,"maxmemory-samples",0)) { + ll2string(buf,sizeof(buf),server.maxmemory_samples); + addReplyBulkCString(c,"maxmemory-samples"); + addReplyBulkCString(c,buf); + matches++; + } + if (stringmatch(pattern,"timeout",0)) { + ll2string(buf,sizeof(buf),server.maxidletime); addReplyBulkCString(c,"timeout"); addReplyBulkCString(c,buf); matches++; @@@ -462,7 -405,6 +462,6 @@@ sdsfree(buf); matches++; } - decrRefCount(o); setDeferredMultiBulkLength(c,replylen,matches*2); } @@@ -475,11 -417,10 +474,11 @@@ void configCommand(redisClient *c) configGetCommand(c); } else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) { if (c->argc != 2) goto badarity; + server.stat_keyspace_hits = 0; + server.stat_keyspace_misses = 0; server.stat_numcommands = 0; server.stat_numconnections = 0; server.stat_expiredkeys = 0; - server.stat_starttime = time(NULL); addReply(c,shared.ok); } else { addReplyError(c, diff --combined src/redis.c index 08bba542,1f8d71a7..4aa19560 --- a/src/redis.c +++ b/src/redis.c @@@ -69,120 -69,119 +69,120 @@@ double R_Zero, R_PosInf, R_NegInf, R_Na struct redisServer server; /* server global state */ struct redisCommand *commandTable; struct redisCommand readonlyCommandTable[] = { - {"get",getCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, - {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, - {"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, - {"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"strlen",strlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"mget",mgetCommand,-2,REDIS_CMD_INLINE,NULL,1,-1,1}, - {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"rpushx",rpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"lpushx",lpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"linsert",linsertCommand,5,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"rpop",rpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lpop",lpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"brpop",brpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"blpop",blpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"llen",llenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lindex",lindexCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"lrange",lrangeCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lrem",lremCommand,4,REDIS_CMD_BULK,NULL,1,1,1}, - {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,2,1}, - {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"srem",sremCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"smove",smoveCommand,4,REDIS_CMD_BULK,NULL,1,2,1}, - {"sismember",sismemberCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"scard",scardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"spop",spopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"srandmember",srandmemberCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, - {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, - {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, - {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, - {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, - {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, - {"smembers",sinterCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"zincrby",zincrbyCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"zrem",zremCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zremrangebyrank",zremrangebyrankCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, - {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, - {"zrange",zrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zrangebyscore",zrangebyscoreCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zrevrangebyscore",zrevrangebyscoreCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zcount",zcountCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zrevrange",zrevrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zcard",zcardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zscore",zscoreCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"zrank",zrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hsetnx",hsetnxCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hmset",hmsetCommand,-4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hmget",hmgetCommand,-3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hincrby",hincrbyCommand,4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hdel",hdelCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hlen",hlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hkeys",hkeysCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hvals",hvalsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hgetall",hgetallCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hexists",hexistsCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"mset",msetCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2}, - {"msetnx",msetnxCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2}, - {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"select",selectCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"move",moveCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"rename",renameCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"expire",expireCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, - {"expireat",expireatCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, - {"keys",keysCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"auth",authCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"ping",pingCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"echo",echoCommand,2,REDIS_CMD_BULK,NULL,0,0,0}, - {"save",saveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"bgsave",bgsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"bgrewriteaof",bgrewriteaofCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"shutdown",shutdownCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"type",typeCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"multi",multiCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"exec",execCommand,1,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0}, - {"discard",discardCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"sync",syncCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"flushall",flushallCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"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}, - {"subscribe",subscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"unsubscribe",unsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"psubscribe",psubscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"punsubscribe",punsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"publish",publishCommand,3,REDIS_CMD_BULK|REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0}, - {"watch",watchCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"unwatch",unwatchCommand,1,REDIS_CMD_INLINE,NULL,0,0,0} + {"get",getCommand,2,0,NULL,1,1,1}, + {"set",setCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"substr",substrCommand,4,0,NULL,1,1,1}, + {"strlen",strlenCommand,2,0,NULL,1,1,1}, + {"del",delCommand,-2,0,NULL,0,0,0}, + {"exists",existsCommand,2,0,NULL,1,1,1}, + {"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"mget",mgetCommand,-2,0,NULL,1,-1,1}, + {"rpush",rpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lpush",lpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"rpushx",rpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lpushx",lpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"linsert",linsertCommand,5,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"rpop",rpopCommand,2,0,NULL,1,1,1}, + {"lpop",lpopCommand,2,0,NULL,1,1,1}, + {"brpop",brpopCommand,-3,0,NULL,1,1,1}, + {"blpop",blpopCommand,-3,0,NULL,1,1,1}, + {"llen",llenCommand,2,0,NULL,1,1,1}, + {"lindex",lindexCommand,3,0,NULL,1,1,1}, + {"lset",lsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lrange",lrangeCommand,4,0,NULL,1,1,1}, + {"ltrim",ltrimCommand,4,0,NULL,1,1,1}, + {"lrem",lremCommand,4,0,NULL,1,1,1}, + {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1}, + {"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"srem",sremCommand,3,0,NULL,1,1,1}, + {"smove",smoveCommand,4,0,NULL,1,2,1}, + {"sismember",sismemberCommand,3,0,NULL,1,1,1}, + {"scard",scardCommand,2,0,NULL,1,1,1}, + {"spop",spopCommand,2,0,NULL,1,1,1}, + {"srandmember",srandmemberCommand,2,0,NULL,1,1,1}, + {"sinter",sinterCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"sunion",sunionCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"sdiff",sdiffCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"smembers",sinterCommand,2,0,NULL,1,1,1}, + {"zadd",zaddCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zincrby",zincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zrem",zremCommand,3,0,NULL,1,1,1}, + {"zremrangebyscore",zremrangebyscoreCommand,4,0,NULL,1,1,1}, + {"zremrangebyrank",zremrangebyrankCommand,4,0,NULL,1,1,1}, + {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, + {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, + {"zrange",zrangeCommand,-4,0,NULL,1,1,1}, + {"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1}, ++ {"zrevrangebyscore",zrevrangebyscoreCommand,-4,0,NULL,1,1,1}, + {"zcount",zcountCommand,4,0,NULL,1,1,1}, + {"zrevrange",zrevrangeCommand,-4,0,NULL,1,1,1}, + {"zcard",zcardCommand,2,0,NULL,1,1,1}, + {"zscore",zscoreCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zrank",zrankCommand,3,0,NULL,1,1,1}, + {"zrevrank",zrevrankCommand,3,0,NULL,1,1,1}, + {"hset",hsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hsetnx",hsetnxCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hget",hgetCommand,3,0,NULL,1,1,1}, + {"hmset",hmsetCommand,-4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hmget",hmgetCommand,-3,0,NULL,1,1,1}, + {"hincrby",hincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hdel",hdelCommand,3,0,NULL,1,1,1}, + {"hlen",hlenCommand,2,0,NULL,1,1,1}, + {"hkeys",hkeysCommand,2,0,NULL,1,1,1}, + {"hvals",hvalsCommand,2,0,NULL,1,1,1}, + {"hgetall",hgetallCommand,2,0,NULL,1,1,1}, + {"hexists",hexistsCommand,3,0,NULL,1,1,1}, + {"incrby",incrbyCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"decrby",decrbyCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"getset",getsetCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"mset",msetCommand,-3,REDIS_CMD_DENYOOM,NULL,1,-1,2}, + {"msetnx",msetnxCommand,-3,REDIS_CMD_DENYOOM,NULL,1,-1,2}, + {"randomkey",randomkeyCommand,1,0,NULL,0,0,0}, + {"select",selectCommand,2,0,NULL,0,0,0}, + {"move",moveCommand,3,0,NULL,1,1,1}, + {"rename",renameCommand,3,0,NULL,1,1,1}, + {"renamenx",renamenxCommand,3,0,NULL,1,1,1}, + {"expire",expireCommand,3,0,NULL,0,0,0}, + {"expireat",expireatCommand,3,0,NULL,0,0,0}, + {"keys",keysCommand,2,0,NULL,0,0,0}, + {"dbsize",dbsizeCommand,1,0,NULL,0,0,0}, + {"auth",authCommand,2,0,NULL,0,0,0}, + {"ping",pingCommand,1,0,NULL,0,0,0}, + {"echo",echoCommand,2,0,NULL,0,0,0}, + {"save",saveCommand,1,0,NULL,0,0,0}, + {"bgsave",bgsaveCommand,1,0,NULL,0,0,0}, + {"bgrewriteaof",bgrewriteaofCommand,1,0,NULL,0,0,0}, + {"shutdown",shutdownCommand,1,0,NULL,0,0,0}, + {"lastsave",lastsaveCommand,1,0,NULL,0,0,0}, + {"type",typeCommand,2,0,NULL,1,1,1}, + {"multi",multiCommand,1,0,NULL,0,0,0}, + {"exec",execCommand,1,REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0}, + {"discard",discardCommand,1,0,NULL,0,0,0}, + {"sync",syncCommand,1,0,NULL,0,0,0}, + {"flushdb",flushdbCommand,1,0,NULL,0,0,0}, + {"flushall",flushallCommand,1,0,NULL,0,0,0}, + {"sort",sortCommand,-2,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"info",infoCommand,1,0,NULL,0,0,0}, + {"monitor",monitorCommand,1,0,NULL,0,0,0}, + {"ttl",ttlCommand,2,0,NULL,1,1,1}, + {"persist",persistCommand,2,0,NULL,1,1,1}, + {"slaveof",slaveofCommand,3,0,NULL,0,0,0}, + {"debug",debugCommand,-2,0,NULL,0,0,0}, + {"config",configCommand,-2,0,NULL,0,0,0}, + {"subscribe",subscribeCommand,-2,0,NULL,0,0,0}, + {"unsubscribe",unsubscribeCommand,-1,0,NULL,0,0,0}, + {"psubscribe",psubscribeCommand,-2,0,NULL,0,0,0}, + {"punsubscribe",punsubscribeCommand,-1,0,NULL,0,0,0}, + {"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0}, + {"watch",watchCommand,-2,0,NULL,0,0,0}, + {"unwatch",unwatchCommand,1,0,NULL,0,0,0} }; /*============================ Utility functions ============================ */ @@@ -479,10 -478,6 +479,10 @@@ void activeExpireCycle(void) } } +void updateLRUClock(void) { + server.lruclock = (time(NULL)/REDIS_LRU_CLOCK_RESOLUTION) & + REDIS_LRU_CLOCK_MAX; +} int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { int j, loops = server.cronloops++; @@@ -495,19 -490,19 +495,19 @@@ * in objects at every object access, and accuracy is not needed. * To access a global var is faster than calling time(NULL) */ server.unixtime = time(NULL); - /* We have just 21 bits per object for LRU information. - * So we use an (eventually wrapping) LRU clock with minutes resolution. + /* We have just 22 bits per object for LRU information. + * So we use an (eventually wrapping) LRU clock with 10 seconds resolution. + * 2^22 bits with 10 seconds resoluton is more or less 1.5 years. * - * When we need to select what object to swap, we compute the minimum - * time distance between the current lruclock and the object last access - * lruclock info. Even if clocks will wrap on overflow, there is - * the interesting property that we are sure that at least - * ABS(A-B) minutes passed between current time and timestamp B. + * Note that even if this will wrap after 1.5 years it's not a problem, + * everything will still work but just some object will appear younger + * to Redis. But for this to happen a given object should never be touched + * for 1.5 years. * - * This is not precise but we don't need at all precision, but just - * something statistically reasonable. + * Note that you can change the resolution altering the + * REDIS_LRU_CLOCK_RESOLUTION define. */ - server.lruclock = (time(NULL)/60)&((1<<21)-1); + updateLRUClock(); /* We received a SIGTERM, shutting down here in a safe way, as it is * not ok doing so inside the signal handler. */ @@@ -738,8 -733,6 +738,8 @@@ void initServerConfig() server.maxclients = 0; server.blpop_blocked_clients = 0; server.maxmemory = 0; + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU; + server.maxmemory_samples = 3; server.vm_enabled = 0; server.vm_swap_file = zstrdup("/tmp/redis-%p.vm"); server.vm_page_size = 256; /* 256 bytes per page */ @@@ -754,7 -747,6 +754,7 @@@ server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES; server.shutdown_asap = 0; + updateLRUClock(); resetServerSaveParams(); appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */ @@@ -824,8 -816,6 +824,8 @@@ void initServer() server.stat_numconnections = 0; server.stat_expiredkeys = 0; server.stat_starttime = time(NULL); + server.stat_keyspace_misses = 0; + server.stat_keyspace_hits = 0; server.unixtime = time(NULL); aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); if (aeCreateFileEvent(server.el, server.fd, AE_READABLE, @@@ -899,84 -889,14 +899,14 @@@ void call(redisClient *c, struct redisC int processCommand(redisClient *c) { struct redisCommand *cmd; - /* 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 - * a bit higher but this allows things like multi-sets, so if this - * protocol is used only for MSET and similar commands this is a big win. */ - if (c->multibulk == 0 && c->argc == 1 && ((char*)(c->argv[0]->ptr))[0] == '*') { - c->multibulk = atoi(((char*)c->argv[0]->ptr)+1); - if (c->multibulk <= 0) { - resetClient(c); - return 1; - } else { - decrRefCount(c->argv[c->argc-1]); - c->argc--; - return 1; - } - } else if (c->multibulk) { - if (c->bulklen == -1) { - if (((char*)c->argv[0]->ptr)[0] != '$') { - addReplyError(c,"multi bulk protocol error"); - resetClient(c); - return 1; - } else { - char *eptr; - long bulklen = strtol(((char*)c->argv[0]->ptr)+1,&eptr,10); - int perr = eptr[0] != '\0'; - - decrRefCount(c->argv[0]); - if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX || - bulklen < 0 || bulklen > 1024*1024*1024) - { - c->argc--; - addReplyError(c,"invalid bulk write count"); - resetClient(c); - return 1; - } - c->argc--; - c->bulklen = bulklen+2; /* add two bytes for CR+LF */ - return 1; - } - } else { - c->mbargv = zrealloc(c->mbargv,(sizeof(robj*))*(c->mbargc+1)); - c->mbargv[c->mbargc] = c->argv[0]; - c->mbargc++; - c->argc--; - c->multibulk--; - if (c->multibulk == 0) { - robj **auxargv; - int auxargc; - - /* Here we need to swap the multi-bulk argc/argv with the - * normal argc/argv of the client structure. */ - auxargv = c->argv; - c->argv = c->mbargv; - c->mbargv = auxargv; - - auxargc = c->argc; - c->argc = c->mbargc; - c->mbargc = auxargc; - - /* We need to set bulklen to something different than -1 - * in order for the code below to process the command without - * to try to read the last argument of a bulk command as - * a special argument. */ - c->bulklen = 0; - /* continue below and process the command */ - } else { - c->bulklen = -1; - return 1; - } - } - } - /* -- end of multi bulk commands processing -- */ - - /* The QUIT command is handled as a special case. Normal command - * procs are unable to close the client connection safely */ + /* The QUIT command is handled separately. Normal command procs will + * go through checking for replication and QUIT will cause trouble + * when FORCE_REPLICATION is enabled and would be implemented in + * a regular command proc. */ if (!strcasecmp(c->argv[0]->ptr,"quit")) { - freeClient(c); - return 0; + addReply(c,shared.ok); + c->flags |= REDIS_CLOSE_AFTER_REPLY; + return REDIS_ERR; } /* Now lookup the command and check ASAP about trivial error conditions @@@ -985,55 -905,18 +915,18 @@@ if (!cmd) { addReplyErrorFormat(c,"unknown command '%s'", (char*)c->argv[0]->ptr); - resetClient(c); - return 1; + return REDIS_OK; } else if ((cmd->arity > 0 && cmd->arity != c->argc) || (c->argc < -cmd->arity)) { 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. */ - 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 (perr || bulklen == LONG_MAX || bulklen == LONG_MIN || - bulklen < 0 || bulklen > 1024*1024*1024) - { - c->argc--; - addReplyError(c,"invalid bulk write count"); - resetClient(c); - return 1; - } - c->argc--; - c->bulklen = bulklen+2; /* add two bytes for CR+LF */ - /* It is possible that the bulk read is already in the - * buffer. Check this condition and handle it accordingly. - * This is just a fast path, alternative to call processInputBuffer(). - * It's a good idea since the code is small and this condition - * happens most of the times. */ - if ((signed)sdslen(c->querybuf) >= c->bulklen) { - c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2); - c->argc++; - c->querybuf = sdsrange(c->querybuf,c->bulklen,-1); - } else { - /* Otherwise return... there is to read the last argument - * from the socket. */ - return 1; - } + return REDIS_OK; } - /* Let's try to encode the bulk object to save space. */ - if (cmd->flags & REDIS_CMD_BULK) - c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); /* Check if the user is authenticated */ if (server.requirepass && !c->authenticated && cmd->proc != authCommand) { addReplyError(c,"operation not permitted"); - resetClient(c); - return 1; + return REDIS_OK; } /* Handle the maxmemory directive. @@@ -1046,8 -929,7 +939,7 @@@ zmalloc_used_memory() > server.maxmemory) { addReplyError(c,"command not allowed when used memory > 'maxmemory'"); - resetClient(c); - return 1; + return REDIS_OK; } /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ @@@ -1056,8 -938,7 +948,7 @@@ cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand && cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) { addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context"); - resetClient(c); - return 1; + return REDIS_OK; } /* Exec the command */ @@@ -1069,13 -950,10 +960,10 @@@ addReply(c,shared.queued); } else { if (server.vm_enabled && server.vm_max_threads > 0 && - blockClientOnSwappedKeys(c,cmd)) return 1; + blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR; call(c,cmd); } - - /* Prepare the client for the next command */ - resetClient(c); - return 1; + return REDIS_OK; } /*================================== Shutdown =============================== */ @@@ -1094,7 -972,7 +982,7 @@@ int prepareForShutdown() /* Append only file: fsync() the AOF and exit */ aof_fsync(server.appendfd); if (server.vm_enabled) unlink(server.vm_swap_file); - } else { + } else if (server.saveparamslen > 0) { /* Snapshotting. Perform a SYNC SAVE and exit */ if (rdbSave(server.dbfilename) != REDIS_OK) { /* Ooops.. error saving! The best we can do is to continue @@@ -1105,8 -983,6 +993,8 @@@ redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit"); return REDIS_ERR; } + } else { + redisLog(REDIS_WARNING,"Not saving DB."); } if (server.daemonize) unlink(server.pidfile); redisLog(REDIS_WARNING,"Server exit now, bye bye..."); @@@ -1177,7 -1053,6 +1065,7 @@@ sds genRedisInfoString(void) "process_id:%ld\r\n" "uptime_in_seconds:%ld\r\n" "uptime_in_days:%ld\r\n" + "lru_clock:%ld\r\n" "used_cpu_sys:%.2f\r\n" "used_cpu_user:%.2f\r\n" "used_cpu_sys_childrens:%.2f\r\n" @@@ -1188,7 -1063,6 +1076,7 @@@ "used_memory:%zu\r\n" "used_memory_human:%s\r\n" "mem_fragmentation_ratio:%.2f\r\n" + "use_tcmalloc:%d\r\n" "changes_since_last_save:%lld\r\n" "bgsave_in_progress:%d\r\n" "last_save_time:%ld\r\n" @@@ -1196,8 -1070,6 +1084,8 @@@ "total_connections_received:%lld\r\n" "total_commands_processed:%lld\r\n" "expired_keys:%lld\r\n" + "keyspace_hits:%lld\r\n" + "keyspace_misses:%lld\r\n" "hash_max_zipmap_entries:%zu\r\n" "hash_max_zipmap_value:%zu\r\n" "pubsub_channels:%ld\r\n" @@@ -1212,7 -1084,6 +1100,7 @@@ (long) getpid(), uptime, uptime/(3600*24), + (unsigned long) server.lruclock, (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, @@@ -1223,11 -1094,6 +1111,11 @@@ zmalloc_used_memory(), hmem, zmalloc_get_fragmentation_ratio(), +#ifdef USE_TCMALLOC + 1, +#else + 0, +#endif server.dirty, server.bgsavechildpid != -1, server.lastsave, @@@ -1235,8 -1101,6 +1123,8 @@@ server.stat_numconnections, server.stat_numcommands, server.stat_expiredkeys, + server.stat_keyspace_hits, + server.stat_keyspace_misses, server.hash_max_zipmap_entries, server.hash_max_zipmap_value, dictSize(server.pubsub_channels), @@@ -1353,93 -1217,10 +1241,93 @@@ int tryFreeOneObjectFromFreelist(void) * memory usage. */ void freeMemoryIfNeeded(void) { + /* Remove keys accordingly to the active policy as long as we are + * over the memory limit. */ while (server.maxmemory && zmalloc_used_memory() > server.maxmemory) { int j, k, freed = 0; + /* Basic strategy -- remove objects from the free list. */ if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue; + + for (j = 0; j < server.dbnum; j++) { + long bestval; + sds bestkey = NULL; + struct dictEntry *de; + redisDb *db = server.db+j; + dict *dict; + + if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || + server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) + { + dict = server.db[j].dict; + } else { + dict = server.db[j].expires; + } + if (dictSize(dict) == 0) continue; + + /* volatile-random and allkeys-random policy */ + if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM || + server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM) + { + de = dictGetRandomKey(dict); + bestkey = dictGetEntryKey(de); + } + + /* volatile-lru and allkeys-lru policy */ + else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || + server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) + { + for (k = 0; k < server.maxmemory_samples; k++) { + sds thiskey; + long thisval; + robj *o; + + de = dictGetRandomKey(dict); + thiskey = dictGetEntryKey(de); + o = dictGetEntryVal(de); + thisval = estimateObjectIdleTime(o); + + /* Higher idle time is better candidate for deletion */ + if (bestkey == NULL || thisval > bestval) { + bestkey = thiskey; + bestval = thisval; + } + } + } + + /* volatile-ttl */ + else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) { + for (k = 0; k < server.maxmemory_samples; k++) { + sds thiskey; + long thisval; + + de = dictGetRandomKey(dict); + thiskey = dictGetEntryKey(de); + thisval = (long) dictGetEntryVal(de); + + /* Expire sooner (minor expire unix timestamp) is better + * candidate for deletion */ + if (bestkey == NULL || thisval < bestval) { + bestkey = thiskey; + bestval = thisval; + } + } + } + + /* Finally remove the selected key. */ + if (bestkey) { + robj *keyobj = createStringObject(bestkey,sdslen(bestkey)); + dbDelete(db,keyobj); + server.stat_expiredkeys++; + decrRefCount(keyobj); + freed++; + } + } + if (!freed) return; /* nothing to free... */ + } + + while(0) { + int j, k, freed = 0; for (j = 0; j < server.dbnum; j++) { int minttl = -1; sds minkey = NULL; @@@ -1607,7 -1388,6 +1495,7 @@@ void segvHandler(int sig, siginfo_t *in int i, trace_size = 0; ucontext_t *uc = (ucontext_t*) secret; sds infostring; + struct sigaction act; REDIS_NOTUSED(info); redisLog(REDIS_WARNING, @@@ -1629,16 -1409,7 +1517,16 @@@ /* free(messages); Don't call free() with possibly corrupted memory. */ if (server.daemonize) unlink(server.pidfile); - _exit(0); + + /* Make sure we exit with the right signal at the end. So for instance + * the core will be dumped if enabled. */ + sigemptyset (&act.sa_mask); + /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction + * is used. Otherwise, sa_handler is used */ + act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND; + act.sa_handler = SIG_DFL; + sigaction (sig, &act, NULL); + kill(getpid(),sig); } void sigtermHandler(int sig) { diff --combined src/redis.h index bc961a71,44857569..cf21447d --- a/src/redis.h +++ b/src/redis.h @@@ -57,15 -57,15 +57,15 @@@ /* Hash table parameters */ #define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */ - /* Command flags */ - #define REDIS_CMD_BULK 1 /* Bulk write command */ - #define REDIS_CMD_INLINE 2 /* Inline command */ - /* REDIS_CMD_DENYOOM reserves a longer comment: all the commands marked with - this flags will return an error when the 'maxmemory' option is set in the - config file and the server is using more than maxmemory bytes of memory. - In short this commands are denied on low memory conditions. */ - #define REDIS_CMD_DENYOOM 4 - #define REDIS_CMD_FORCE_REPLICATION 8 /* Force replication even if dirty is 0 */ + /* Command flags: + * REDIS_CMD_DENYOOM: + * Commands marked with this flag will return an error when 'maxmemory' is + * set and the server is using more than 'maxmemory' bytes of memory. + * In short: commands with this flag are denied on low memory conditions. + * REDIS_CMD_FORCE_REPLICATION: + * Force replication even if dirty is 0. */ + #define REDIS_CMD_DENYOOM 4 + #define REDIS_CMD_FORCE_REPLICATION 8 /* Object types */ #define REDIS_STRING 0 @@@ -144,6 -144,11 +144,11 @@@ #define REDIS_BLOCKED 16 /* The client is waiting in a blocking operation */ #define REDIS_IO_WAIT 32 /* The client is waiting for Virtual Memory I/O */ #define REDIS_DIRTY_CAS 64 /* Watched keys modified. EXEC will fail. */ + #define REDIS_CLOSE_AFTER_REPLY 128 /* Close after writing entire reply. */ + + /* Client request types */ + #define REDIS_REQ_INLINE 1 + #define REDIS_REQ_MULTIBULK 2 /* Slave replication state - slave side */ #define REDIS_REPL_NONE 0 /* No active replication */ @@@ -198,13 -203,6 +203,13 @@@ #define REDIS_OP_DIFF 1 #define REDIS_OP_INTER 2 +/* Redis maxmemory strategies */ +#define REDIS_MAXMEMORY_VOLATILE_LRU 0 +#define REDIS_MAXMEMORY_VOLATILE_TTL 1 +#define REDIS_MAXMEMORY_VOLATILE_RANDOM 2 +#define REDIS_MAXMEMORY_ALLKEYS_LRU 3 +#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4 + /* We can print the stacktrace, so our assert is defined this way: */ #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) #define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1) @@@ -218,8 -216,6 +223,8 @@@ void _redisPanic(char *msg, char *file /* A redis object, that is a type able to hold a string / list / set */ /* The actual Redis Object */ +#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */ +#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */ typedef struct redisObject { unsigned type:4; unsigned storage:2; /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */ @@@ -294,11 -290,11 +299,11 @@@ typedef struct redisClient redisDb *db; int dictid; sds querybuf; - robj **argv, **mbargv; - char *newline; /* pointing to the detected newline in querybuf */ - int argc, mbargc; - long bulklen; /* bulk read len. -1 if not in bulk read mode */ - int multibulk; /* multi bulk command format active */ + int argc; + robj **argv; + int reqtype; + int multibulklen; /* number of multi bulk arguments left to read */ + long bulklen; /* length of bulk argument in multi bulk request */ list *reply; int sentlen; time_t lastinteraction; /* time of the last interaction, used for timeout */ @@@ -357,14 -353,12 +362,14 @@@ struct redisServer aeEventLoop *el; int cronloops; /* number of times the cron function run */ list *objfreelist; /* A list of freed objects to avoid malloc() */ - time_t lastsave; /* Unix time of last save succeeede */ + time_t lastsave; /* Unix time of last save succeeede */ /* Fields used only for stats */ - time_t stat_starttime; /* server start time */ - long long stat_numcommands; /* number of processed commands */ - long long stat_numconnections; /* number of connections received */ - long long stat_expiredkeys; /* number of expired keys */ + time_t stat_starttime; /* server start time */ + long long stat_numcommands; /* number of processed commands */ + long long stat_numconnections; /* number of connections received */ + long long stat_expiredkeys; /* number of expired keys */ + long long stat_keyspace_hits; /* number of successful lookups of keys */ + long long stat_keyspace_misses; /* number of failed lookups of keys */ /* Configuration */ int verbosity; int glueoutputbuf; @@@ -401,8 -395,6 +406,8 @@@ int replstate; unsigned int maxclients; unsigned long long maxmemory; + int maxmemory_policy; + int maxmemory_samples; unsigned int blpop_blocked_clients; unsigned int vm_blocked_clients; /* Sort parameters - qsort_r() is only available under BSD so we @@@ -691,16 -683,6 +696,16 @@@ int getLongLongFromObject(robj *o, lon char *strEncoding(int encoding); int compareStringObjects(robj *a, robj *b); int equalStringObjects(robj *a, robj *b); +unsigned long estimateObjectIdleTime(robj *o); + +/* Synchronous I/O with timeout */ +int syncWrite(int fd, char *ptr, ssize_t size, int timeout); +int syncRead(int fd, char *ptr, ssize_t size, int timeout); +int syncReadLine(int fd, char *ptr, ssize_t size, int timeout); +int fwriteBulkString(FILE *fp, char *s, unsigned long len); +int fwriteBulkDouble(FILE *fp, double d); +int fwriteBulkLongLong(FILE *fp, long long l); +int fwriteBulkObject(FILE *fp, robj *obj); /* Replication */ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc); @@@ -919,7 -901,6 +924,7 @@@ void zaddCommand(redisClient *c) void zincrbyCommand(redisClient *c); void zrangeCommand(redisClient *c); void zrangebyscoreCommand(redisClient *c); +void zrevrangebyscoreCommand(redisClient *c); void zcountCommand(redisClient *c); void zrevrangeCommand(redisClient *c); void zcardCommand(redisClient *c); diff --combined src/t_zset.c index d45e9369,ba05c278..8139b53d --- a/src/t_zset.c +++ b/src/t_zset.c @@@ -174,35 -174,25 +174,35 @@@ int zslDelete(zskiplist *zsl, double sc return 0; /* not found */ } +/* Struct to hold a inclusive/exclusive range spec. */ +typedef struct { + double min, max; + int minex, maxex; /* are min or max exclusive? */ +} zrangespec; + /* Delete all the elements with score between min and max from the skiplist. * Min and mx are inclusive, so a score >= min || score <= max is deleted. * Note that this function takes the reference to the hash table view of the * sorted set, in order to remove the elements from the hash table too. */ -unsigned long zslDeleteRangeByScore(zskiplist *zsl, double min, double max, dict *dict) { +unsigned long zslDeleteRangeByScore(zskiplist *zsl, zrangespec range, dict *dict) { zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; unsigned long removed = 0; int i; x = zsl->header; for (i = zsl->level-1; i >= 0; i--) { - while (x->level[i].forward && x->level[i].forward->score < min) - x = x->level[i].forward; + while (x->level[i].forward && (range.minex ? + x->level[i].forward->score <= range.min : + x->level[i].forward->score < range.min)) + x = x->level[i].forward; update[i] = x; } - /* We may have multiple elements with the same score, what we need - * is to find the element with both the right score and object. */ + + /* Current node is the last with score < or <= min. */ x = x->level[0].forward; - while (x && x->score <= max) { + + /* Delete nodes while in range. */ + while (x && (range.maxex ? x->score < range.max : x->score <= range.max)) { zskiplistNode *next = x->level[0].forward; zslDeleteNode(zsl,x,update); dictDelete(dict,x->obj); @@@ -210,7 -200,7 +210,7 @@@ removed++; x = next; } - return removed; /* not found */ + return removed; } /* Delete all the elements with rank between start and end from the skiplist. @@@ -306,44 -296,6 +306,44 @@@ zskiplistNode* zslistTypeGetElementByRa return NULL; } +/* Populate the rangespec according to the objects min and max. */ +static int zslParseRange(robj *min, robj *max, zrangespec *spec) { + char *eptr; + spec->minex = spec->maxex = 0; + + /* Parse the min-max interval. If one of the values is prefixed + * by the "(" character, it's considered "open". For instance + * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max + * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */ + if (min->encoding == REDIS_ENCODING_INT) { + spec->min = (long)min->ptr; + } else { + if (((char*)min->ptr)[0] == '(') { + spec->min = strtod((char*)min->ptr+1,&eptr); + if (eptr[0] != '\0' || isnan(spec->min)) return REDIS_ERR; + spec->minex = 1; + } else { + spec->min = strtod((char*)min->ptr,&eptr); + if (eptr[0] != '\0' || isnan(spec->min)) return REDIS_ERR; + } + } + if (max->encoding == REDIS_ENCODING_INT) { + spec->max = (long)max->ptr; + } else { + if (((char*)max->ptr)[0] == '(') { + spec->max = strtod((char*)max->ptr+1,&eptr); + if (eptr[0] != '\0' || isnan(spec->max)) return REDIS_ERR; + spec->maxex = 1; + } else { + spec->max = strtod((char*)max->ptr,&eptr); + if (eptr[0] != '\0' || isnan(spec->max)) return REDIS_ERR; + } + } + + return REDIS_OK; +} + + /*----------------------------------------------------------------------------- * Sorted set commands *----------------------------------------------------------------------------*/ @@@ -440,12 -392,14 +440,14 @@@ void zaddGenericCommand(redisClient *c void zaddCommand(redisClient *c) { double scoreval; if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return; + c->argv[3] = tryObjectEncoding(c->argv[3]); zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0); } void zincrbyCommand(redisClient *c) { double scoreval; if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return; + c->argv[3] = tryObjectEncoding(c->argv[3]); zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1); } @@@ -460,6 -414,7 +462,7 @@@ void zremCommand(redisClient *c) checkType(c,zsetobj,REDIS_ZSET)) return; zs = zsetobj->ptr; + c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (de == NULL) { addReply(c,shared.czero); @@@ -480,22 -435,20 +483,22 @@@ } void zremrangebyscoreCommand(redisClient *c) { - double min; - double max; + zrangespec range; long deleted; - robj *zsetobj; + robj *o; zset *zs; - if ((getDoubleFromObjectOrReply(c, c->argv[2], &min, NULL) != REDIS_OK) || - (getDoubleFromObjectOrReply(c, c->argv[3], &max, NULL) != REDIS_OK)) return; + /* Parse the range arguments. */ + if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { + addReplyError(c,"min or max is not a double"); + return; + } - if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || - checkType(c,zsetobj,REDIS_ZSET)) return; + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_ZSET)) return; - zs = zsetobj->ptr; - deleted = zslDeleteRangeByScore(zs->zsl,min,max,zs->dict); + zs = o->ptr; + deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict); if (htNeedsResize(zs->dict)) dictResize(zs->dict); if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]); if (deleted) touchWatchedKey(c->db,c->argv[1]); @@@ -682,13 -635,13 +685,13 @@@ void zunionInterGenericCommand(redisCli dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (other) { value = src[j].weight * zunionInterDictValue(other); - zunionInterAggregate(&score, value, aggregate); + zunionInterAggregate(&score,value,aggregate); } else { break; } } - /* accept entry only when present in every source dict */ + /* Only continue when present in every source dict. */ if (j == setnum) { robj *o = dictGetEntryKey(de); znode = zslInsert(dstzset->zsl,score,o); @@@ -710,8 -663,6 +713,8 @@@ /* skip key when already processed */ if (dictFind(dstzset->dict,dictGetEntryKey(de)) != NULL) continue; + + /* initialize score */ score = src[i].weight * zunionInterDictValue(de); /* because the zsets are sorted by size, its only possible @@@ -720,7 -671,7 +723,7 @@@ dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de)); if (other) { value = src[j].weight * zunionInterDictValue(other); - zunionInterAggregate(&score, value, aggregate); + zunionInterAggregate(&score,value,aggregate); } } @@@ -832,156 -783,125 +835,156 @@@ void zrevrangeCommand(redisClient *c) zrangeGenericCommand(c,1); } -/* This command implements both ZRANGEBYSCORE and ZCOUNT. - * If justcount is non-zero, just the count is returned. */ -void genericZrangebyscoreCommand(redisClient *c, int justcount) { - robj *o; - double min, max; - int minex = 0, maxex = 0; /* are min or max exclusive? */ +/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE and ZCOUNT. + * If "justcount", only the number of elements in the range is returned. */ +void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) { + zrangespec range; + robj *o, *emptyreply; + zset *zsetobj; + zskiplist *zsl; + zskiplistNode *ln; int offset = 0, limit = -1; int withscores = 0; - int badsyntax = 0; + unsigned long rangelen = 0; + void *replylen = NULL; - /* Parse the min-max interval. If one of the values is prefixed - * by the "(" character, it's considered "open". For instance - * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max - * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */ - if (((char*)c->argv[2]->ptr)[0] == '(') { - min = strtod((char*)c->argv[2]->ptr+1,NULL); - minex = 1; - } else { - min = strtod(c->argv[2]->ptr,NULL); - } - if (((char*)c->argv[3]->ptr)[0] == '(') { - max = strtod((char*)c->argv[3]->ptr+1,NULL); - maxex = 1; - } else { - max = strtod(c->argv[3]->ptr,NULL); + /* Parse the range arguments. */ + if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) { + addReplyError(c,"min or max is not a double"); + return; } - /* Parse "WITHSCORES": note that if the command was called with - * the name ZCOUNT then we are sure that c->argc == 4, so we'll never - * enter the following paths to parse WITHSCORES and LIMIT. */ - if (c->argc == 5 || c->argc == 8) { - if (strcasecmp(c->argv[c->argc-1]->ptr,"withscores") == 0) - withscores = 1; - else - badsyntax = 1; + /* Parse optional extra arguments. Note that ZCOUNT will exactly have + * 4 arguments, so we'll never enter the following code path. */ + if (c->argc > 4) { + int remaining = c->argc - 4; + int pos = 4; + + while (remaining) { + if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) { + pos++; remaining--; + withscores = 1; + } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) { + offset = atoi(c->argv[pos+1]->ptr); + limit = atoi(c->argv[pos+2]->ptr); + pos += 3; remaining -= 3; + } else { + addReply(c,shared.syntaxerr); + return; + } + } } - if (c->argc != (4 + withscores) && c->argc != (7 + withscores)) - badsyntax = 1; - if (badsyntax) { - addReplyError(c,"wrong number of arguments for ZRANGEBYSCORE"); - return; + + /* Ok, lookup the key and get the range */ + emptyreply = justcount ? shared.czero : shared.emptymultibulk; + if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL || + checkType(c,o,REDIS_ZSET)) return; + zsetobj = o->ptr; + zsl = zsetobj->zsl; + + /* If reversed, assume the elements are sorted from high to low score. */ + ln = zslFirstWithScore(zsl,range.min); + if (reverse) { + /* If range.min is out of range, ln will be NULL and we need to use + * the tail of the skiplist as first node of the range. */ + if (ln == NULL) ln = zsl->tail; + + /* zslFirstWithScore returns the first element with where with + * score >= range.min, so backtrack to make sure the element we use + * here has score <= range.min. */ + while (ln && ln->score > range.min) ln = ln->backward; + + /* Move to the right element according to the range spec. */ + if (range.minex) { + /* Find last element with score < range.min */ + while (ln && ln->score == range.min) ln = ln->backward; + } else { + /* Find last element with score <= range.min */ + while (ln && ln->level[0].forward && + ln->level[0].forward->score == range.min) + ln = ln->level[0].forward; + } + } else { + if (range.minex) { + /* Find first element with score > range.min */ + while (ln && ln->score == range.min) ln = ln->level[0].forward; + } } - /* Parse "LIMIT" */ - if (c->argc == (7 + withscores) && strcasecmp(c->argv[4]->ptr,"limit")) { - addReply(c,shared.syntaxerr); + /* No "first" element in the specified interval. */ + if (ln == NULL) { + addReply(c,emptyreply); return; - } else if (c->argc == (7 + withscores)) { - offset = atoi(c->argv[5]->ptr); - limit = atoi(c->argv[6]->ptr); - if (offset < 0) offset = 0; } - /* Ok, lookup the key and get the range */ - o = lookupKeyRead(c->db,c->argv[1]); - if (o == NULL) { - addReply(c,justcount ? shared.czero : shared.emptymultibulk); - } else { - if (o->type != REDIS_ZSET) { - addReply(c,shared.wrongtypeerr); - } else { - zset *zsetobj = o->ptr; - zskiplist *zsl = zsetobj->zsl; - zskiplistNode *ln; - robj *ele; - void *replylen = NULL; - unsigned long rangelen = 0; - - /* Get the first node with the score >= min, or with - * score > min if 'minex' is true. */ - ln = zslFirstWithScore(zsl,min); - while (minex && ln && ln->score == min) ln = ln->level[0].forward; - - if (ln == NULL) { - /* No element matching the speciifed interval */ - addReply(c,justcount ? shared.czero : shared.emptymultibulk); - return; - } + /* We don't know in advance how many matching elements there + * are in the list, so we push this object that will represent + * the multi-bulk length in the output buffer, and will "fix" + * it later */ + if (!justcount) + replylen = addDeferredMultiBulkLength(c); - /* We don't know in advance how many matching elements there - * are in the list, so we push this object that will represent - * the multi-bulk length in the output buffer, and will "fix" - * it later */ - if (!justcount) - replylen = addDeferredMultiBulkLength(c); - - while(ln && (maxex ? (ln->score < max) : (ln->score <= max))) { - if (offset) { - offset--; - ln = ln->level[0].forward; - continue; - } - if (limit == 0) break; - if (!justcount) { - ele = ln->obj; - addReplyBulk(c,ele); - if (withscores) - addReplyDouble(c,ln->score); - } - ln = ln->level[0].forward; - rangelen++; - if (limit > 0) limit--; + /* If there is an offset, just traverse the number of elements without + * checking the score because that is done in the next loop. */ + while(ln && offset--) { + if (reverse) + ln = ln->backward; + else + ln = ln->level[0].forward; + } + + while (ln && limit--) { + /* Check if this this element is in range. */ + if (reverse) { + if (range.maxex) { + /* Element should have score > range.max */ + if (ln->score <= range.max) break; + } else { + /* Element should have score >= range.max */ + if (ln->score < range.max) break; } - if (justcount) { - addReplyLongLong(c,(long)rangelen); + } else { + if (range.maxex) { + /* Element should have score < range.max */ + if (ln->score >= range.max) break; } else { - setDeferredMultiBulkLength(c,replylen, - withscores ? (rangelen*2) : rangelen); + /* Element should have score <= range.max */ + if (ln->score > range.max) break; } } + + /* Do our magic */ + rangelen++; + if (!justcount) { + addReplyBulk(c,ln->obj); + if (withscores) + addReplyDouble(c,ln->score); + } + + if (reverse) + ln = ln->backward; + else + ln = ln->level[0].forward; + } + + if (justcount) { + addReplyLongLong(c,(long)rangelen); + } else { + setDeferredMultiBulkLength(c,replylen, + withscores ? (rangelen*2) : rangelen); } } void zrangebyscoreCommand(redisClient *c) { - genericZrangebyscoreCommand(c,0); + genericZrangebyscoreCommand(c,0,0); +} + +void zrevrangebyscoreCommand(redisClient *c) { + genericZrangebyscoreCommand(c,1,0); } void zcountCommand(redisClient *c) { - genericZrangebyscoreCommand(c,1); + genericZrangebyscoreCommand(c,0,1); } void zcardCommand(redisClient *c) { @@@ -1004,6 -924,7 +1007,7 @@@ void zscoreCommand(redisClient *c) checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; + c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk); @@@ -1027,6 -948,7 +1031,7 @@@ void zrankGenericCommand(redisClient *c zs = o->ptr; zsl = zs->zsl; + c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk);