% make test
+Buliding using tcmalloc
+-----------------------
+
+tcmalloc is a fast and space efficient implementation (for little objects)
+of malloc(). Compiling Redis with it can improve performances and memeory
+usage. You can read more about it here:
+
+http://goog-perftools.sourceforge.net/doc/tcmalloc.html
+
+In order to compile Redis with tcmalloc support install tcmalloc on your system
+and then use:
+
+ % make USE_TCMALLOC=yes
+
+Note that you can pass any other target to make, as long as you append
+USE_TCMALLOC=yes at the end.
+
Running Redis
-------------
#
# maxmemory <bytes>
+# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
+# is reached? You can select among five behavior:
+#
+# volatile-lru -> remove the key with an expire set using an LRU algorithm
+# allkeys-lru -> remove any key accordingly to the LRU algorithm
+# volatile-random -> remove a random key with an expire set
+# allkeys->random -> remove a random key, any key
+# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
+#
+# maxmemory-policy volatile-lru
+
+# LRU and minimal TTL algorithms are not precise algorithms but approximated
+# algorithms (in order to save memory), so you can select as well the sample
+# size to check. For instance for default Redis will check three keys and
+# pick the one that was used less recently, you can change the sample size
+# using the following configuration directive.
+#
+# maxmemory-samples 3
+
############################## APPEND ONLY MODE ###############################
# By default Redis asynchronously dumps the dataset on disk. If you can live
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
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
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
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 \
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 \
$(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
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;
exit(1);
}
-/* Write binary-safe string into a file in the bulkformat
- * $<count>\r\n<payload>\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 $<count>\r\n<payload>\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 $<count>\r\n<payload>\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) {
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]);
*----------------------------------------------------------------------------*/
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);
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;
if (startAppendOnly() == REDIS_ERR) {
addReplyError(c,
"Unable to turn on AOF. Check server logs.");
- decrRefCount(o);
return;
}
}
} else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr);
- decrRefCount(o);
return;
}
- decrRefCount(o);
addReply(c,shared.ok);
return;
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");
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++;
sdsfree(buf);
matches++;
}
- decrRefCount(o);
setDeferredMultiBulkLength(c,replylen,matches*2);
}
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,
#include <AvailabilityMacros.h>
#endif
-/* test for malloc_size() */
-#ifdef __APPLE__
+/* Use tcmalloc's malloc_size() when available.
+ * When tcmalloc is used, native OSX malloc_size() may never be used because
+ * this expects a different allocation scheme. Therefore, *exclusively* use
+ * either tcmalloc or OSX's malloc_size()! */
+#if defined(USE_TCMALLOC)
+#include <google/tcmalloc.h>
+#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6
+#define HAVE_MALLOC_SIZE 1
+#define redis_malloc_size(p) tc_malloc_size(p)
+#endif
+#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) malloc_size(p)
if (de) {
robj *val = dictGetEntryVal(de);
+ /* Update the access time for the aging algorithm.
+ * Don't do it if we have a saving child, as this will trigger
+ * a copy on write madness. */
+ if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1)
+ val->lru = server.lruclock;
+
if (server.vm_enabled) {
if (val->storage == REDIS_VM_MEMORY ||
val->storage == REDIS_VM_SWAPPING)
/* If we were swapping the object out, cancel the operation */
if (val->storage == REDIS_VM_SWAPPING)
vmCancelThreadedIOJob(val);
- /* Update the access time for the aging algorithm. */
- val->lru = server.lruclock;
} else {
int notify = (val->storage == REDIS_VM_LOADING);
if (notify) handleClientsBlockedOnSwappedKey(db,key);
}
}
+ server.stat_keyspace_hits++;
return val;
} else {
+ server.stat_keyspace_misses++;
return NULL;
}
}
/* Delete the key */
server.stat_expiredkeys++;
- server.dirty++;
propagateExpire(db,key);
return dbDelete(db,key);
}
strenc = strEncoding(val->encoding);
addReplyStatusFormat(c,
"Value at:%p refcount:%d "
- "encoding:%s serializedlength:%lld",
+ "encoding:%s serializedlength:%lld "
+ "lru:%d lru_seconds_idle:%lu",
(void*)val, val->refcount,
- strenc, (long long) rdbSavedObjectLen(val,NULL));
+ strenc, (long long) rdbSavedObjectLen(val,NULL),
+ val->lru, estimateObjectIdleTime(val));
} else {
vmpointer *vp = (vmpointer*) val;
addReplyStatusFormat(c,
selectDb(c,0);
c->fd = fd;
c->querybuf = sdsempty();
- c->newline = NULL;
+ c->reqtype = 0;
c->argc = 0;
c->argv = NULL;
+ c->multibulklen = 0;
c->bulklen = -1;
- c->multibulk = 0;
- c->mbargc = 0;
- c->mbargv = NULL;
c->sentlen = 0;
c->flags = 0;
c->lastinteraction = time(NULL);
return c;
}
+/* Set the event loop to listen for write events on the client's socket.
+ * Typically gets called every time a reply is built. */
int _installWriteEvent(redisClient *c) {
+ /* When CLOSE_AFTER_REPLY is set, no more replies may be added! */
+ redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY));
+
if (c->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
static void freeClientArgv(redisClient *c) {
int j;
-
for (j = 0; j < c->argc; j++)
decrRefCount(c->argv[j]);
- for (j = 0; j < c->mbargc; j++)
- decrRefCount(c->mbargv[j]);
c->argc = 0;
- c->mbargc = 0;
}
void freeClient(redisClient *c) {
}
/* Release memory */
zfree(c->argv);
- zfree(c->mbargv);
freeClientMultiState(c);
zfree(c);
}
if (listLength(c->reply) == 0) {
c->sentlen = 0;
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
+
+ /* Close connection after entire reply has been sent. */
+ if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c);
}
}
/* resetClient prepare the client to process the next command */
void resetClient(redisClient *c) {
freeClientArgv(c);
+ c->reqtype = 0;
+ c->multibulklen = 0;
c->bulklen = -1;
- c->multibulk = 0;
- c->newline = NULL;
}
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
- * iteration the client is never blocked, otherwise the processInputBuffer
- * would not be called at all, but after the execution of the first commands
- * 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 */
- size_t querylen;
-
- 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));
- if (sdslen(query) > querylen) {
- /* leave data after the first line of the query in the buffer */
- c->querybuf = sdscatlen(c->querybuf,query+querylen,sdslen(query)-querylen);
- }
- *p = '\0'; /* remove "\n" */
- if (*(p-1) == '\r') *(p-1) = '\0'; /* and "\r" if any */
- sdsupdatelen(query);
-
- /* Now we can split the query in arguments */
- argv = sdssplitlen(query,sdslen(query)," ",1,&argc);
- sdsfree(query);
-
- if (c->argv) zfree(c->argv);
- c->argv = zmalloc(sizeof(robj*)*argc);
-
- for (j = 0; j < argc; j++) {
- if (sdslen(argv[j])) {
- c->argv[c->argc] = createObject(REDIS_STRING,argv[j]);
- c->argc++;
- } else {
- sdsfree(argv[j]);
+int processInlineBuffer(redisClient *c) {
+ char *newline = strstr(c->querybuf,"\r\n");
+ int argc, j;
+ sds *argv;
+ size_t querylen;
+
+ /* Nothing to do without a \r\n */
+ if (newline == NULL)
+ return REDIS_ERR;
+
+ /* Split the input buffer up to the \r\n */
+ querylen = newline-(c->querybuf);
+ argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
+
+ /* Leave data after the first line of the query in the buffer */
+ c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
+
+ /* Setup argv array on client structure */
+ if (c->argv) zfree(c->argv);
+ c->argv = zmalloc(sizeof(robj*)*argc);
+
+ /* Create redis objects for all arguments. */
+ for (c->argc = 0, j = 0; j < argc; j++) {
+ if (sdslen(argv[j])) {
+ c->argv[c->argc] = createObject(REDIS_STRING,argv[j]);
+ c->argc++;
+ } else {
+ sdsfree(argv[j]);
+ }
+ }
+ zfree(argv);
+ return REDIS_OK;
+}
+
+/* Helper function. Trims query buffer to make the function that processes
+ * multi bulk requests idempotent. */
+static void setProtocolError(redisClient *c, int pos) {
+ c->flags |= REDIS_CLOSE_AFTER_REPLY;
+ c->querybuf = sdsrange(c->querybuf,pos,-1);
+}
+
+int processMultibulkBuffer(redisClient *c) {
+ char *newline = NULL;
+ char *eptr;
+ int pos = 0, tolerr;
+ long bulklen;
+
+ if (c->multibulklen == 0) {
+ /* The client should have been reset */
+ redisAssert(c->argc == 0);
+
+ /* Multi bulk length cannot be read without a \r\n */
+ newline = strstr(c->querybuf,"\r\n");
+ if (newline == NULL)
+ return REDIS_ERR;
+
+ /* We know for sure there is a whole line since newline != NULL,
+ * so go ahead and find out the multi bulk length. */
+ redisAssert(c->querybuf[0] == '*');
+ c->multibulklen = strtol(c->querybuf+1,&eptr,10);
+ pos = (newline-c->querybuf)+2;
+ if (c->multibulklen <= 0) {
+ c->querybuf = sdsrange(c->querybuf,pos,-1);
+ return REDIS_OK;
+ } else if (c->multibulklen > 1024*1024) {
+ addReplyError(c,"Protocol error: invalid multibulk length");
+ setProtocolError(c,pos);
+ return REDIS_ERR;
+ }
+
+ /* Setup argv array on client structure */
+ if (c->argv) zfree(c->argv);
+ c->argv = zmalloc(sizeof(robj*)*c->multibulklen);
+
+ /* Search new newline */
+ newline = strstr(c->querybuf+pos,"\r\n");
+ }
+
+ redisAssert(c->multibulklen > 0);
+ while(c->multibulklen) {
+ /* Read bulk length if unknown */
+ if (c->bulklen == -1) {
+ newline = strstr(c->querybuf+pos,"\r\n");
+ if (newline != NULL) {
+ if (c->querybuf[pos] != '$') {
+ addReplyErrorFormat(c,
+ "Protocol error: expected '$', got '%c'",
+ c->querybuf[pos]);
+ setProtocolError(c,pos);
+ return REDIS_ERR;
}
+
+ bulklen = strtol(c->querybuf+pos+1,&eptr,10);
+ tolerr = (eptr[0] != '\r');
+ if (tolerr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
+ bulklen < 0 || bulklen > 1024*1024*1024)
+ {
+ addReplyError(c,"Protocol error: invalid bulk length");
+ setProtocolError(c,pos);
+ return REDIS_ERR;
+ }
+ pos += eptr-(c->querybuf+pos)+2;
+ c->bulklen = bulklen;
+ } else {
+ /* No newline in current buffer, so wait for more data */
+ break;
}
- zfree(argv);
- if (c->argc) {
- /* Execute the command. If the client is still valid
- * after processCommand() return and there is something
- * on the query buffer try to process the next command. */
- if (processCommand(c) && sdslen(c->querybuf)) goto again;
+ }
+
+ /* Read bulk argument */
+ if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {
+ /* Not enough data (+2 == trailing \r\n) */
+ break;
+ } else {
+ c->argv[c->argc++] = createStringObject(c->querybuf+pos,c->bulklen);
+ pos += c->bulklen+2;
+ c->bulklen = -1;
+ c->multibulklen--;
+ }
+ }
+
+ /* Trim to pos */
+ c->querybuf = sdsrange(c->querybuf,pos,-1);
+
+ /* We're done when c->multibulk == 0 */
+ if (c->multibulklen == 0) {
+ return REDIS_OK;
+ }
+ return REDIS_ERR;
+}
+
+void processInputBuffer(redisClient *c) {
+ /* Keep processing while there is something in the input buffer */
+ while(sdslen(c->querybuf)) {
+ /* Immediately abort if the client is in the middle of something. */
+ if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return;
+
+ /* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is
+ * written to the client. Make sure to not let the reply grow after
+ * this flag has been set (i.e. don't process more commands). */
+ if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
+
+ /* Determine request type when unknown. */
+ if (!c->reqtype) {
+ if (c->querybuf[0] == '*') {
+ c->reqtype = REDIS_REQ_MULTIBULK;
} else {
- /* Nothing to process, argc == 0. Just process the query
- * buffer if it's not empty or return to the caller */
- if (sdslen(c->querybuf)) goto again;
+ c->reqtype = REDIS_REQ_INLINE;
}
- return;
- } else if (sdslen(c->querybuf) >= REDIS_REQUEST_MAX_SIZE) {
- redisLog(REDIS_VERBOSE, "Client protocol error");
- freeClient(c);
- return;
}
- } else {
- /* Bulk read handling. Note that if we are at this point
- the client already sent a command terminated with a newline,
- we are reading the bulk data that is actually the last
- argument of the command. */
- int qbl = sdslen(c->querybuf);
-
- if (c->bulklen <= qbl) {
- /* Copy everything but the final CRLF as final argument */
- c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2);
- c->argc++;
- c->querybuf = sdsrange(c->querybuf,c->bulklen,-1);
- /* Process the command. If the client is still valid after
- * the processing and there is more data in the buffer
- * try to parse it. */
- if (processCommand(c) && sdslen(c->querybuf)) goto again;
- return;
+
+ if (c->reqtype == REDIS_REQ_INLINE) {
+ if (processInlineBuffer(c) != REDIS_OK) break;
+ } else if (c->reqtype == REDIS_REQ_MULTIBULK) {
+ if (processMultibulkBuffer(c) != REDIS_OK) break;
+ } else {
+ redisPanic("Unknown request type");
+ }
+
+ /* Multibulk processing could see a <= 0 length. */
+ if (c->argc == 0) {
+ resetClient(c);
+ } else {
+ /* Only reset the client when the command was executed. */
+ if (processCommand(c) == REDIS_OK)
+ resetClient(c);
}
}
}
return;
}
if (nread) {
- size_t oldlen = sdslen(c->querybuf);
- c->querybuf = sdscatlen(c->querybuf, buf, nread);
+ c->querybuf = sdscatlen(c->querybuf,buf,nread);
c->lastinteraction = time(NULL);
- /* 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;
}
o->encoding = REDIS_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
- if (server.vm_enabled) {
- /* Note that this code may run in the context of an I/O thread
- * and accessing server.lruclock in theory is an error
- * (no locks). But in practice this is safe, and even if we read
- * garbage Redis will not fail. */
- o->lru = server.lruclock;
- o->storage = REDIS_VM_MEMORY;
- }
+ /* Set the LRU to the current lruclock (minutes resolution).
+ * We do this regardless of the fact VM is active as LRU is also
+ * used for the maxmemory directive when Redis is used as cache.
+ *
+ * Note that this code may run in the context of an I/O thread
+ * and accessing server.lruclock in theory is an error
+ * (no locks). But in practice this is safe, and even if we read
+ * garbage Redis will not fail. */
+ o->lru = server.lruclock;
+ /* The following is only needed if VM is active, but since the conditional
+ * is probably more costly than initializing the field it's better to
+ * have every field properly initialized anyway. */
+ o->storage = REDIS_VM_MEMORY;
return o;
}
* range and if this is the main thread, since when VM is enabled we
* have the constraint that I/O thread should only handle non-shared
* objects, in order to avoid race conditions (we don't have per-object
- * locking). */
- if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
+ * locking).
+ *
+ * Note that we also avoid using shared integers when maxmemory is used
+ * because very object needs to have a private LRU field for the LRU
+ * algorithm to work well. */
+ if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
pthread_equal(pthread_self(),server.mainthread)) {
decrRefCount(o);
incrRefCount(shared.integers[value]);
default: return "unknown";
}
}
+
+/* Given an object returns the min number of seconds the object was never
+ * requested, using an approximated LRU algorithm. */
+unsigned long estimateObjectIdleTime(robj *o) {
+ if (server.lruclock >= o->lru) {
+ return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
+ } else {
+ return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
+ REDIS_LRU_CLOCK_RESOLUTION;
+ }
+}
aeMain(config.el);
endBenchmark();
+ prepareForBenchmark("MSET (10 keys, multi bulk)");
+ c = createClient();
+ if (!c) exit(1);
+ c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11);
+ {
+ int i;
+ char *data = zmalloc(config.datasize+2);
+ memset(data,'x',config.datasize);
+ for (i = 0; i < 10; i++) {
+ c->obuf = sdscatprintf(c->obuf,"$%d\r\n%s\r\n",config.datasize,data);
+ }
+ zfree(data);
+ }
+ prepareClientForReply(c,REPLY_RETCODE);
+ createMissingClients(c);
+ aeMain(config.el);
+ endBenchmark();
+
prepareForBenchmark("SET");
c = createClient();
if (!c) exit(1);
- c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
+ c->obuf = sdscat(c->obuf,"SET foo_rand000000000000 ");
{
char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize);
prepareForBenchmark("LPUSH");
c = createClient();
if (!c) exit(1);
- c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
+ c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
prepareForBenchmark("SADD");
c = createClient();
if (!c) exit(1);
- c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
+ c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
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");
+ c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
/* display error stack */
for (i = 0; i < errors.level; i++) {
- printf("0x%08lx - %s\n", errors.offset[i], errors.error[i]);
+ printf("0x%08lx - %s\n",
+ (unsigned long) errors.offset[i], errors.error[i]);
}
}
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include "anet.h"
#include "sds.h"
#include "zmalloc.h"
#include "linenoise.h"
-#define REDIS_CMD_INLINE 1
-#define REDIS_CMD_BULK 2
-#define REDIS_CMD_MULTIBULK 4
-
#define REDIS_NOTUSED(V) ((void) V)
static struct config {
static int cliReadReply(int fd);
static void usage();
+/*------------------------------------------------------------------------------
+ * Utility functions
+ *--------------------------------------------------------------------------- */
+
+static long long mstime(void) {
+ struct timeval tv;
+ long long mst;
+
+ gettimeofday(&tv, NULL);
+ mst = ((long)tv.tv_sec)*1000;
+ mst += tv.tv_usec/1000;
+ return mst;
+}
+
+static void printStringRepr(char *s, int len) {
+ printf("\"");
+ while(len--) {
+ switch(*s) {
+ case '\\':
+ case '"':
+ printf("\\%c",*s);
+ break;
+ case '\n': printf("\\n"); break;
+ case '\r': printf("\\r"); break;
+ case '\t': printf("\\t"); break;
+ case '\a': printf("\\a"); break;
+ case '\b': printf("\\b"); break;
+ default:
+ if (isprint(*s))
+ printf("%c",*s);
+ else
+ printf("\\x%02x",(unsigned char)*s);
+ break;
+ }
+ s++;
+ }
+ printf("\"");
+}
+
+/*------------------------------------------------------------------------------
+ * Networking / parsing
+ *--------------------------------------------------------------------------- */
+
/* 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) {
return 0;
}
-static void printStringRepr(char *s, int len) {
- printf("\"");
- while(len--) {
- switch(*s) {
- case '\\':
- case '"':
- printf("\\%c",*s);
- break;
- case '\n': printf("\\n"); break;
- case '\r': printf("\\r"); break;
- case '\t': printf("\\t"); break;
- case '\a': printf("\\a"); break;
- case '\b': printf("\\b"); break;
- default:
- if (isprint(*s))
- printf("%c",*s);
- else
- printf("\\x%02x",(unsigned char)*s);
- break;
- }
- s++;
- }
- printf("\"");
-}
-
static int cliReadBulkReply(int fd) {
sds replylen = cliReadLine(fd);
char *reply, crlf[2];
return 0;
}
+/*------------------------------------------------------------------------------
+ * User interface
+ *--------------------------------------------------------------------------- */
+
static int parseOptions(int argc, char **argv) {
int i;
exit(0);
} else {
int err;
+ long long start_time = mstime(), elapsed;
if ((err = cliSendCommand(argc, argv, 1)) != 0) {
if (err == ECONNRESET) {
cliSendCommand(argc,argv,1);
}
}
+ elapsed = mstime()-start_time;
+ if (elapsed > 500) printf("%.2f seconds\n",
+ (double)elapsed/1000);
}
}
/* Free the argument vector */
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},
- {"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 ============================ */
}
}
+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++;
* 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. */
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 */
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 */
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 (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,
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
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.
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 */
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 */
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 =============================== */
/* 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
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...");
"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"
"blocked_clients:%d\r\n"
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
+ "used_memory_rss:%zu\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"
"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"
(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,
server.blpop_blocked_clients,
zmalloc_used_memory(),
hmem,
+ zmalloc_get_rss(),
zmalloc_get_fragmentation_ratio(),
+#ifdef USE_TCMALLOC
+ 1,
+#else
+ 0,
+#endif
server.dirty,
server.bgsavechildpid != -1,
server.lastsave,
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),
* 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 = 0; /* just to prevent warning */
+ 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;
int i, trace_size = 0;
ucontext_t *uc = (ucontext_t*) secret;
sds infostring;
+ struct sigaction act;
REDIS_NOTUSED(info);
redisLog(REDIS_WARNING,
/* 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) {
/* 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
#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 */
#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)
/* 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 */
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 */
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;
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
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);
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);
decrRefCount(cmdobj);
}
-int syncWrite(int fd, char *ptr, ssize_t size, int timeout) {
- ssize_t nwritten, ret = size;
- time_t start = time(NULL);
-
- timeout++;
- while(size) {
- if (aeWait(fd,AE_WRITABLE,1000) & AE_WRITABLE) {
- nwritten = write(fd,ptr,size);
- if (nwritten == -1) return -1;
- ptr += nwritten;
- size -= nwritten;
- }
- if ((time(NULL)-start) > timeout) {
- errno = ETIMEDOUT;
- return -1;
- }
- }
- return ret;
-}
-
-int syncRead(int fd, char *ptr, ssize_t size, int timeout) {
- ssize_t nread, totread = 0;
- time_t start = time(NULL);
-
- timeout++;
- while(size) {
- if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
- nread = read(fd,ptr,size);
- if (nread <= 0) return -1;
- ptr += nread;
- size -= nread;
- totread += nread;
- }
- if ((time(NULL)-start) > timeout) {
- errno = ETIMEDOUT;
- return -1;
- }
- }
- return totread;
-}
-
-int syncReadLine(int fd, char *ptr, ssize_t size, int timeout) {
- ssize_t nread = 0;
-
- size--;
- while(size) {
- char c;
-
- if (syncRead(fd,&c,1,timeout) == -1) return -1;
- if (c == '\n') {
- *ptr = '\0';
- if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0';
- return nread;
- } else {
- *ptr++ = c;
- *ptr = '\0';
- nread++;
- }
- }
- return nread;
-}
-
void syncCommand(redisClient *c) {
/* ignore SYNC if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return;
--- /dev/null
+/* Synchronous socket and file I/O operations useful across the core.
+ *
+ * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "redis.h"
+
+/* ----------------- Blocking sockets I/O with timeouts --------------------- */
+
+/* Redis performs most of the I/O in a nonblocking way, with the exception
+ * of the SYNC command where the slave does it in a blocking way, and
+ * the MIGRATE command that must be blocking in order to be atomic from the
+ * point of view of the two instances (one migrating the key and one receiving
+ * the key). This is why need the following blocking I/O functions. */
+
+int syncWrite(int fd, char *ptr, ssize_t size, int timeout) {
+ ssize_t nwritten, ret = size;
+ time_t start = time(NULL);
+
+ timeout++;
+ while(size) {
+ if (aeWait(fd,AE_WRITABLE,1000) & AE_WRITABLE) {
+ nwritten = write(fd,ptr,size);
+ if (nwritten == -1) return -1;
+ ptr += nwritten;
+ size -= nwritten;
+ }
+ if ((time(NULL)-start) > timeout) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ }
+ return ret;
+}
+
+int syncRead(int fd, char *ptr, ssize_t size, int timeout) {
+ ssize_t nread, totread = 0;
+ time_t start = time(NULL);
+
+ timeout++;
+ while(size) {
+ if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
+ nread = read(fd,ptr,size);
+ if (nread <= 0) return -1;
+ ptr += nread;
+ size -= nread;
+ totread += nread;
+ }
+ if ((time(NULL)-start) > timeout) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ }
+ return totread;
+}
+
+int syncReadLine(int fd, char *ptr, ssize_t size, int timeout) {
+ ssize_t nread = 0;
+
+ size--;
+ while(size) {
+ char c;
+
+ if (syncRead(fd,&c,1,timeout) == -1) return -1;
+ if (c == '\n') {
+ *ptr = '\0';
+ if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0';
+ return nread;
+ } else {
+ *ptr++ = c;
+ *ptr = '\0';
+ nread++;
+ }
+ }
+ return nread;
+}
+
+/* ----------------- Blocking sockets I/O with timeouts --------------------- */
+
+/* Write binary-safe string into a file in the bulkformat
+ * $<count>\r\n<payload>\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 $<count>\r\n<payload>\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 $<count>\r\n<payload>\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");
+ }
+}
+
+
o = lookupKeyRead(c->db,c->argv[1]);
if (o != NULL && o->type != REDIS_HASH) {
addReply(c,shared.wrongtypeerr);
+ return;
}
/* Note the check for o != NULL happens inside the loop. This is
void pushGenericCommand(redisClient *c, int where) {
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
if (lobj == NULL) {
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
addReply(c,shared.cone);
}
void lpushxCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD);
}
void rpushxCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL);
}
void linsertCommand(redisClient *c) {
+ c->argv[4] = tryObjectEncoding(c->argv[4]);
if (strcasecmp(c->argv[2]->ptr,"after") == 0) {
pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL);
} else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
int index = atoi(c->argv[2]->ptr);
- robj *value = c->argv[3];
+ robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
listTypeTryConversion(o,value);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
}
void lremCommand(redisClient *c) {
- robj *subject, *obj = c->argv[3];
+ robj *subject, *obj;
+ obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
int toremove = atoi(c->argv[2]->ptr);
int removed = 0;
listTypeEntry entry;
robj *set;
set = lookupKeyWrite(c->db,c->argv[1]);
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
if (set == NULL) {
set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set);
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
if (setTypeRemove(set,c->argv[2])) {
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
robj *srcset, *dstset, *ele;
srcset = lookupKeyWrite(c->db,c->argv[1]);
dstset = lookupKeyWrite(c->db,c->argv[2]);
- ele = c->argv[3];
+ ele = c->argv[3] = tryObjectEncoding(c->argv[3]);
/* If the source key does not exist return 0 */
if (srcset == NULL) {
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
if (setTypeIsMember(set,c->argv[2]))
addReply(c,shared.cone);
else
}
void setCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
}
void setnxCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
}
void setexCommand(redisClient *c) {
+ c->argv[3] = tryObjectEncoding(c->argv[3]);
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
}
void getsetCommand(redisClient *c) {
if (getGenericCommand(c) == REDIS_ERR) return;
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
dbReplace(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
touchWatchedKey(c->db,c->argv[1]);
robj *o;
o = lookupKeyWrite(c->db,c->argv[1]);
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
if (o == NULL) {
/* Create the key */
retval = dbAdd(c->db,c->argv[1],c->argv[2]);
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);
removed++;
x = next;
}
- return removed; /* not found */
+ return removed;
}
/* Delete all the elements with rank between start and end from the skiplist.
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
*----------------------------------------------------------------------------*/
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);
}
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);
}
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]);
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);
/* 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
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
- zunionInterAggregate(&score, value, aggregate);
+ zunionInterAggregate(&score,value,aggregate);
}
}
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);
+
+ /* 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;
+ }
- /* 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--;
+ 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) {
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);
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);
-#define REDIS_VERSION "2.1.4"
+#define REDIS_VERSION "2.1.5"
double computeObjectSwappability(robj *o) {
/* actual age can be >= minage, but not < minage. As we use wrapping
* 21 bit clocks with minutes resolution for the LRU. */
- time_t minage = abs(server.lruclock - o->lru);
+ time_t minage = estimateObjectIdleTime(o);
long asize = 0, elesize;
robj *ele;
list *l;
/* Advance the cursor */
p += rawlen;
+ curlen += extra;
} else {
if (next.prevrawlensize > rawlensize) {
/* This would result in shrinking, which we want to avoid.
"pls: %2u, "
"payload %5u"
"} ",
- (long unsigned int)p,
+ (long unsigned)p,
index,
- p-zl,
+ (unsigned long) (p-zl),
entry.headersize+entry.len,
entry.headersize,
entry.prevrawlen,
p += entry.headersize;
if (ZIP_IS_STR(entry.encoding)) {
if (entry.len > 40) {
- fwrite(p,40,1,stdout);
+ if (fwrite(p,40,1,stdout) == 0) perror("fwrite");
printf("...");
} else {
- fwrite(p,entry.len,1,stdout);
+ if (entry.len &&
+ fwrite(p,entry.len,1,stdout) == 0) perror("fwrite");
}
} else {
printf("%lld", (long long) zipLoadInteger(p,entry.encoding));
#ifdef ZIPLIST_TEST_MAIN
#include <sys/time.h>
+#include "adlist.h"
+#include "sds.h"
+
+#define debug(f, ...) { if (DEBUG) printf(f, __VA_ARGS__); }
unsigned char *createList() {
unsigned char *zl = ziplistNew();
printf("Pop tail: ");
if (vstr)
- fwrite(vstr,vlen,1,stdout);
+ if (vlen && fwrite(vstr,vlen,1,stdout) == 0) perror("fwrite");
else
printf("%lld", vlong);
}
}
+void randstring(char *target, unsigned int min, unsigned int max) {
+ int p, len = min+rand()%(max-min+1);
+ int minval, maxval;
+ switch(rand() % 3) {
+ case 0:
+ minval = 0;
+ maxval = 255;
+ break;
+ case 1:
+ minval = 48;
+ maxval = 122;
+ break;
+ case 2:
+ minval = 48;
+ maxval = 52;
+ break;
+ default:
+ assert(NULL);
+ }
+
+ while(p < len)
+ target[p++] = minval+rand()%(maxval-minval+1);
+ return;
+}
+
+
int main(int argc, char **argv) {
unsigned char *zl, *p;
unsigned char *entry;
return 1;
}
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
printf("\n");
} else {
printf("%lld\n", value);
return 1;
}
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
printf("\n");
} else {
printf("%lld\n", value);
return 1;
}
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
printf("\n");
} else {
printf("%lld\n", value);
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
} else {
printf("%lld", value);
}
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
} else {
printf("%lld", value);
}
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
} else {
printf("%lld", value);
}
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
} else {
printf("%lld", value);
}
while (ziplistGet(p, &entry, &elen, &value)) {
printf("Entry: ");
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0) perror("fwrite");
} else {
printf("%lld", value);
}
} else {
printf("Entry: ");
if (entry) {
- fwrite(entry,elen,1,stdout);
+ if (elen && fwrite(entry,elen,1,stdout) == 0)
+ perror("fwrite");
} else {
printf("%lld",value);
}
/* Pop values again and compare their value. */
p = ziplistIndex(zl,0);
assert(ziplistGet(p,&entry,&elen,&value));
- assert(strncmp(v1,entry,elen) == 0);
+ assert(strncmp(v1,(char*)entry,elen) == 0);
p = ziplistIndex(zl,1);
assert(ziplistGet(p,&entry,&elen,&value));
- assert(strncmp(v2,entry,elen) == 0);
+ assert(strncmp(v2,(char*)entry,elen) == 0);
printf("SUCCESS\n\n");
}
printf("Stress with random payloads of different encoding:\n");
{
- int i, idx, where, len;
- long long v;
+ int i,j,len,where;
unsigned char *p;
- char buf[0x4041]; /* max length of generated string */
- zl = ziplistNew();
- for (i = 0; i < 100000; i++) {
- where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
- if (rand() & 1) {
- /* equally likely create a 16, 32 or 64 bit int */
- v = (rand() & INT16_MAX) + ((1ll << 32) >> ((rand() % 3)*16));
- v *= 2*(rand() & 1)-1; /* randomly flip sign */
- sprintf(buf, "%lld", v);
+ char buf[1024];
+ list *ref;
+ listNode *refnode;
+
+ /* Hold temp vars from ziplist */
+ unsigned char *sstr;
+ unsigned int slen;
+ long long sval;
+
+ /* In the regression for the cascade bug, it was triggered
+ * with a random seed of 2. */
+ srand(2);
+
+ for (i = 0; i < 20000; i++) {
+ zl = ziplistNew();
+ ref = listCreate();
+ listSetFreeMethod(ref,sdsfree);
+ len = rand() % 256;
+
+ /* Create lists */
+ for (j = 0; j < len; j++) {
+ where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
+ switch(rand() % 4) {
+ case 0:
+ sprintf(buf,"%lld",(0LL + rand()) >> 20);
+ break;
+ case 1:
+ sprintf(buf,"%lld",(0LL + rand()));
+ break;
+ case 2:
+ sprintf(buf,"%lld",(0LL + rand()) << 20);
+ break;
+ case 3:
+ randstring(buf,0,256);
+ break;
+ default:
+ assert(NULL);
+ }
+
+ /* Add to ziplist */
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), where);
- } else {
- /* equally likely generate 6, 14 or >14 bit length */
- v = rand() & 0x3f;
- v += 0x4000 >> ((rand() % 3)*8);
- memset(buf, 'x', v);
- zl = ziplistPush(zl, (unsigned char*)buf, v, where);
- }
- /* delete a random element */
- if ((len = ziplistLen(zl)) >= 10) {
- idx = rand() % len;
- // printf("Delete index %d\n", idx);
- // ziplistRepr(zl);
- ziplistDeleteRange(zl, idx, 1);
- // ziplistRepr(zl);
- len--;
+ /* Add to reference list */
+ if (where == ZIPLIST_HEAD) {
+ listAddNodeHead(ref,sdsnew(buf));
+ } else if (where == ZIPLIST_TAIL) {
+ listAddNodeTail(ref,sdsnew(buf));
+ } else {
+ assert(NULL);
+ }
}
- /* iterate from front to back */
- idx = 0;
- p = ziplistIndex(zl, 0);
- while((p = ziplistNext(zl,p)))
- idx++;
- assert(len == idx+1);
-
- /* iterate from back to front */
- idx = 0;
- p = ziplistIndex(zl, -1);
- while((p = ziplistPrev(zl,p)))
- idx++;
- assert(len == idx+1);
+ assert(listLength(ref) == ziplistLen(zl));
+ for (j = 0; j < len; j++) {
+ /* Naive way to get elements, but similar to the stresser
+ * executed from the Tcl test suite. */
+ p = ziplistIndex(zl,j);
+ refnode = listIndex(ref,j);
+
+ assert(ziplistGet(p,&sstr,&slen,&sval));
+ if (sstr == NULL) {
+ sprintf(buf,"%lld",sval);
+ } else {
+ memcpy(buf,sstr,slen);
+ buf[slen] = '\0';
+ }
+ assert(strcmp(buf,listNodeValue(refnode)) == 0);
+ }
+ zfree(zl);
+ listRelease(ref);
}
printf("SUCCESS\n\n");
}
l = zipmapDecodeLength(p);
printf("{key %u}",l);
p += zipmapEncodeLength(NULL,l);
- fwrite(p,l,1,stdout);
+ if (l != 0 && fwrite(p,l,1,stdout) == 0) perror("fwrite");
p += l;
l = zipmapDecodeLength(p);
printf("{value %u}",l);
p += zipmapEncodeLength(NULL,l);
e = *p++;
- fwrite(p,l,1,stdout);
+ if (l != 0 && fwrite(p,l,1,stdout) == 0) perror("fwrite");
p += l+e;
if (e) {
printf("[");
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
-
#include "config.h"
+#ifdef HAVE_MALLOC_SIZE
+#define PREFIX_SIZE (0)
+#else
#if defined(__sun)
-#define PREFIX_SIZE sizeof(long long)
+#define PREFIX_SIZE (sizeof(long long))
#else
-#define PREFIX_SIZE sizeof(size_t)
+#define PREFIX_SIZE (sizeof(size_t))
+#endif
+#endif
+
+/* Explicitly override malloc/free etc when using tcmalloc. */
+#if defined(USE_TCMALLOC)
+#define malloc(size) tc_malloc(size)
+#define calloc(count,size) tc_calloc(count,size)
+#define realloc(ptr,size) tc_realloc(ptr,size)
+#define free(ptr) tc_free(ptr)
#endif
#define increment_used_memory(__n) do { \
zmalloc_thread_safe = 1;
}
-/* Fragmentation = RSS / allocated-bytes */
+/* Get the RSS information in an OS-specific way.
+ *
+ * WARNING: the function zmalloc_get_rss() is not designed to be fast
+ * and may not be called in the busy loops where Redis tries to release
+ * memory expiring or swapping out objects.
+ *
+ * For this kind of "fast RSS reporting" usages use instead the
+ * function RedisEstimateRSS() that is a much faster (and less precise)
+ * version of the funciton. */
#if defined(HAVE_PROCFS)
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
-float zmalloc_get_fragmentation_ratio(void) {
- size_t allocated = zmalloc_used_memory();
+size_t zmalloc_get_rss(void) {
int page = sysconf(_SC_PAGESIZE);
size_t rss;
char buf[4096];
rss = strtoll(p,NULL,10);
rss *= page;
- return (float)rss/allocated;
+ return rss;
}
#elif defined(HAVE_TASKINFO)
#include <unistd.h>
#include <mach/task.h>
#include <mach/mach_init.h>
-float zmalloc_get_fragmentation_ratio(void) {
+size_t zmalloc_get_rss(void) {
task_t task = MACH_PORT_NULL;
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
return 0;
task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
- return (float)t_info.resident_size/zmalloc_used_memory();
+ return t_info.resident_size;
}
#else
-float zmalloc_get_fragmentation_ratio(void) {
- return 0;
+float zmalloc_get_rss(void) {
+ /* If we can't get the RSS in an OS-specific way for this system just
+ * return the memory usage we estimated in zmalloc()..
+ *
+ * Fragmentation will appear to be always 1 (no fragmentation)
+ * of course... */
+ return zmalloc_used_memory();
}
#endif
+
+/* Fragmentation = RSS / allocated-bytes */
+float zmalloc_get_fragmentation_ratio(void) {
+ return (float)zmalloc_get_rss()/zmalloc_used_memory();
+}
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
float zmalloc_get_fragmentation_ratio(void);
+size_t zmalloc_get_rss(void);
#endif /* _ZMALLOC_H */
array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks
-array set ::redis::bulkarg {}
-array set ::redis::multibulkarg {}
-
-# Flag commands requiring last argument as a bulk write operation
-foreach redis_bulk_cmd {
- set setnx rpush lpush rpushx lpushx linsert lset lrem sadd srem sismember echo getset smove zadd zrem zscore zincrby append zrank zrevrank hget hdel hexists setex publish
-} {
- set ::redis::bulkarg($redis_bulk_cmd) {}
-}
-
-# Flag commands requiring last argument as a bulk write operation
-foreach redis_multibulk_cmd {
- mset msetnx hset hsetnx hmset hmget
-} {
- set ::redis::multibulkarg($redis_multibulk_cmd) {}
-}
-
-unset redis_bulk_cmd
-unset redis_multibulk_cmd
proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set fd [socket $server $port]
set args [lrange $args 0 end-1]
}
if {[info command ::redis::__method__$method] eq {}} {
- if {[info exists ::redis::bulkarg($method)]} {
- set cmd "$method "
- append cmd [join [lrange $args 0 end-1]]
- append cmd " [string length [lindex $args end]]\r\n"
- append cmd [lindex $args end]
- ::redis::redis_writenl $fd $cmd
- } elseif {[info exists ::redis::multibulkarg($method)]} {
- set cmd "*[expr {[llength $args]+1}]\r\n"
- append cmd "$[string length $method]\r\n$method\r\n"
- foreach a $args {
- append cmd "$[string length $a]\r\n$a\r\n"
- }
- ::redis::redis_write $fd $cmd
- flush $fd
- } else {
- set cmd "$method "
- append cmd [join $args]
- ::redis::redis_writenl $fd $cmd
+ set cmd "*[expr {[llength $args]+1}]\r\n"
+ append cmd "$[string length $method]\r\n$method\r\n"
+ foreach a $args {
+ append cmd "$[string length $a]\r\n$a\r\n"
}
+ ::redis::redis_write $fd $cmd
+ flush $fd
+
if {!$deferred} {
if {$blocking} {
::redis::redis_read_reply $fd
::redis::redis_read_reply $fd
}
+proc ::redis::__method__write {id fd buf} {
+ ::redis::redis_write $fd $buf
+}
+
+proc ::redis::__method__flush {id fd} {
+ flush $fd
+}
+
proc ::redis::__method__close {id fd} {
catch {close $fd}
catch {unset ::redis::fd($id)}
if {[dict exists $config port]} { set port [dict get $config port] }
# setup config dict
- dict set srv "config" $config_file
+ dict set srv "config_file" $config_file
+ dict set srv "config" $config
dict set srv "pid" $pid
dict set srv "host" $host
dict set srv "port" $port
after 10
}
- set client [redis $host $port]
- dict set srv "client" $client
-
- # select the right db when we don't have to authenticate
- if {![dict exists $config requirepass]} {
- $client select 9
- }
-
# append the server to the stack
lappend ::servers $srv
-
+
+ # connect client (after server dict is put on the stack)
+ reconnect
+
# execute provided block
set curnum $::testnum
if {![catch { uplevel 1 $code } err]} {
}
}
if {$::traceleaks} {
- if {![string match {*0 leaks*} [exec leaks redis-server]]} {
+ set output [exec leaks redis-server]
+ if {![string match {*0 leaks*} $output]} {
puts "--------- Test $::testnum LEAKED! --------"
+ puts $output
exit 1
}
}
set ::denytags {}
set ::allowtags {}
set ::external 0; # If "1" this means, we are running against external instance
+set ::file ""; # If set, runs only the tests in this comma separated list
proc execute_tests name {
source "tests/$name.tcl"
[srv $level "client"] {*}$args
}
+proc reconnect {args} {
+ set level [lindex $args 0]
+ if {[string length $level] == 0 || ![string is integer $level]} {
+ set level 0
+ }
+
+ set srv [lindex $::servers end+$level]
+ set host [dict get $srv "host"]
+ set port [dict get $srv "port"]
+ set config [dict get $srv "config"]
+ set client [redis $host $port]
+ dict set srv "client" $client
+
+ # select the right db when we don't have to authenticate
+ if {![dict exists $config "requirepass"]} {
+ $client select 9
+ }
+
+ # re-set $srv in the servers list
+ set ::servers [lreplace $::servers end+$level 1 $srv]
+}
+
proc redis_deferring_client {args} {
set level 0
if {[llength $args] > 0 && [string is integer [lindex $args 0]]} {
catch {exec rm -rf {*}[glob tests/tmp/server.*]}
}
-proc main {} {
- cleanup
+proc execute_everything {} {
execute_tests "unit/auth"
execute_tests "unit/protocol"
execute_tests "unit/basic"
execute_tests "unit/expire"
execute_tests "unit/other"
execute_tests "unit/cas"
+ execute_tests "unit/quit"
execute_tests "integration/replication"
execute_tests "integration/aof"
# execute_tests "integration/redis-cli"
execute_tests "unit/expire"
execute_tests "unit/other"
execute_tests "unit/cas"
+}
+
+proc main {} {
+ cleanup
+
+ if {[string length $::file] > 0} {
+ foreach {file} [split $::file ,] {
+ execute_tests $file
+ }
+ } else {
+ execute_everything
+ }
cleanup
puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
}
}
incr j
+ } elseif {$opt eq {--file}} {
+ set ::file $arg
+ incr j
} elseif {$opt eq {--host}} {
set ::external 1
set ::host $arg
test {Commands pipelining} {
set fd [r channel]
- puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n"
+ puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n"
flush $fd
set res {}
append res [string match OK* [::redis::redis_read_reply $fd]]
for {set i 0} {$i < 100000} {incr i} {
set q {}
set val "0000${i}0000"
- append q "SET key:$i [string length $val]\r\n$val\r\n"
+ append q "SET key:$i $val\r\n"
puts -nonewline $fd2 $q
set q {}
append q "GET key:$i\r\n"
start_server {tags {"protocol"}} {
- test {Handle an empty query well} {
- set fd [r channel]
- puts -nonewline $fd "\r\n"
- flush $fd
- r ping
- } {PONG}
-
- test {Negative multi bulk command does not create problems} {
- set fd [r channel]
- puts -nonewline $fd "*-10\r\n"
- flush $fd
- r ping
- } {PONG}
-
- test {Negative multi bulk payload} {
- set fd [r channel]
- puts -nonewline $fd "SET x -10\r\n"
- flush $fd
- gets $fd
- } {*invalid bulk*}
-
- test {Too big bulk payload} {
- set fd [r channel]
- puts -nonewline $fd "SET x 2000000000\r\n"
- flush $fd
- gets $fd
- } {*invalid bulk*count*}
-
- test {bulk payload is not a number} {
- set fd [r channel]
- puts -nonewline $fd "SET x blabla\r\n"
- flush $fd
- gets $fd
- } {*invalid bulk*count*}
-
- test {Multi bulk request not followed by bulk args} {
- set fd [r channel]
- puts -nonewline $fd "*1\r\nfoo\r\n"
- flush $fd
- gets $fd
- } {*protocol error*}
-
- test {Generic wrong number of args} {
- catch {r ping x y z} err
- set _ $err
- } {*wrong*arguments*ping*}
+ test "Handle an empty query" {
+ reconnect
+ r write "\r\n"
+ r flush
+ assert_equal "PONG" [r ping]
+ }
+
+ test "Negative multibulk length" {
+ reconnect
+ r write "*-10\r\n"
+ r flush
+ assert_equal PONG [r ping]
+ }
+
+ test "Out of range multibulk length" {
+ reconnect
+ r write "*20000000\r\n"
+ r flush
+ assert_error "*invalid multibulk length*" {r read}
+ }
+
+ test "Wrong multibulk payload header" {
+ reconnect
+ r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\nfooz\r\n"
+ r flush
+ assert_error "*expected '$', got 'f'*" {r read}
+ }
+
+ test "Negative multibulk payload length" {
+ reconnect
+ r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$-10\r\n"
+ r flush
+ assert_error "*invalid bulk length*" {r read}
+ }
+
+ test "Out of range multibulk payload length" {
+ reconnect
+ r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$2000000000\r\n"
+ r flush
+ assert_error "*invalid bulk length*" {r read}
+ }
+
+ test "Non-number multibulk payload length" {
+ reconnect
+ r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n"
+ r flush
+ assert_error "*invalid bulk length*" {r read}
+ }
+
+ test "Multi bulk request not followed by bulk arguments" {
+ reconnect
+ r write "*1\r\nfoo\r\n"
+ r flush
+ assert_error "*expected '$', got 'f'*" {r read}
+ }
+
+ test "Generic wrong number of args" {
+ reconnect
+ assert_error "*wrong*arguments*ping*" {r ping x y z}
+ }
}
--- /dev/null
+start_server {tags {"quit"}} {
+ proc format_command {args} {
+ set cmd "*[llength $args]\r\n"
+ foreach a $args {
+ append cmd "$[string length $a]\r\n$a\r\n"
+ }
+ set _ $cmd
+ }
+
+ test "QUIT returns OK" {
+ reconnect
+ assert_equal OK [r quit]
+ assert_error * {r ping}
+ }
+
+ test "Pipelined commands after QUIT must not be executed" {
+ reconnect
+ r write [format_command quit]
+ r write [format_command set foo bar]
+ r flush
+ assert_equal OK [r read]
+ assert_error * {r read}
+
+ reconnect
+ assert_equal {} [r get foo]
+ }
+
+ test "Pipelined commands after QUIT that exceed read buffer size" {
+ reconnect
+ r write [format_command quit]
+ r write [format_command set foo [string repeat "x" 1024]]
+ r flush
+ assert_equal OK [r read]
+ assert_error * {r read}
+
+ reconnect
+ assert_equal {} [r get foo]
+
+ }
+}
assert_encoding $enc tosort
test "$title: SORT BY key" {
- assert_equal $result [r sort tosort {BY weight_*}]
+ assert_equal $result [r sort tosort BY weight_*]
}
test "$title: SORT BY hash field" {
- assert_equal $result [r sort tosort {BY wobj_*->weight}]
+ assert_equal $result [r sort tosort BY wobj_*->weight]
}
}
}
test "SORT BY key STORE" {
- r sort tosort {BY weight_*} store sort-res
+ r sort tosort BY weight_* store sort-res
assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res
}
test "SORT BY hash field STORE" {
- r sort tosort {BY wobj_*->weight} store sort-res
+ r sort tosort BY wobj_*->weight store sort-res
assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res
}
test "SORT DESC" {
- assert_equal [lsort -decreasing -integer $result] [r sort tosort {DESC}]
+ assert_equal [lsort -decreasing -integer $result] [r sort tosort DESC]
}
test "SORT ALPHA against integer encoded strings" {
test "SORT speed, $num element list BY key, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
- set sorted [r sort tosort {BY weight_* LIMIT 0 10}]
+ set sorted [r sort tosort BY weight_* LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
test "SORT speed, $num element list BY hash field, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
- set sorted [r sort tosort {BY wobj_*->weight LIMIT 0 10}]
+ set sorted [r sort tosort BY wobj_*->weight LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
test "SORT speed, $num element list directly, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
- set sorted [r sort tosort {LIMIT 0 10}]
+ set sorted [r sort tosort LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
test "SORT speed, $num element list BY <const>, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
- set sorted [r sort tosort {BY nokey LIMIT 0 10}]
+ set sorted [r sort tosort BY nokey LIMIT 0 10]
}
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
set _ $rv
} {{{} {}} {{} {}} {{} {}}}
+ test {HMGET against wrong type} {
+ r set wrongtype somevalue
+ assert_error "*wrong*" {r hmget wrongtype field1 field2}
+ }
+
test {HMGET - small hash} {
set keys {}
set vals {}
list $v1 $v2 [r zscore zset foo] [r zscore zset bar]
} {{bar foo} {foo bar} -2 6}
- test {ZRANGEBYSCORE and ZCOUNT basics} {
- r del zset
- r zadd zset 1 a
- r zadd zset 2 b
- r zadd zset 3 c
- r zadd zset 4 d
- r zadd zset 5 e
- list [r zrangebyscore zset 2 4] [r zrangebyscore zset (2 (4] \
- [r zcount zset 2 4] [r zcount zset (2 (4]
- } {{b c d} c 3 1}
-
- test {ZRANGEBYSCORE withscores} {
- r del zset
- r zadd zset 1 a
- r zadd zset 2 b
- r zadd zset 3 c
- r zadd zset 4 d
- r zadd zset 5 e
- r zrangebyscore zset 2 4 withscores
- } {b 2 c 3 d 4}
+ proc create_default_zset {} {
+ create_zset zset {-inf a 1 b 2 c 3 d 4 e 5 f +inf g}
+ }
+
+ test "ZRANGEBYSCORE/ZREVRANGEBYSCORE/ZCOUNT basics" {
+ create_default_zset
+
+ # inclusive range
+ assert_equal {a b c} [r zrangebyscore zset -inf 2]
+ assert_equal {b c d} [r zrangebyscore zset 0 3]
+ assert_equal {d e f} [r zrangebyscore zset 3 6]
+ assert_equal {e f g} [r zrangebyscore zset 4 +inf]
+ assert_equal {c b a} [r zrevrangebyscore zset 2 -inf]
+ assert_equal {d c b} [r zrevrangebyscore zset 3 0]
+ assert_equal {f e d} [r zrevrangebyscore zset 6 3]
+ assert_equal {g f e} [r zrevrangebyscore zset +inf 4]
+ assert_equal 3 [r zcount zset 0 3]
+
+ # exclusive range
+ assert_equal {b} [r zrangebyscore zset (-inf (2]
+ assert_equal {b c} [r zrangebyscore zset (0 (3]
+ assert_equal {e f} [r zrangebyscore zset (3 (6]
+ assert_equal {f} [r zrangebyscore zset (4 (+inf]
+ assert_equal {b} [r zrevrangebyscore zset (2 (-inf]
+ assert_equal {c b} [r zrevrangebyscore zset (3 (0]
+ assert_equal {f e} [r zrevrangebyscore zset (6 (3]
+ assert_equal {f} [r zrevrangebyscore zset (+inf (4]
+ assert_equal 2 [r zcount zset (0 (3]
+ }
+
+ test "ZRANGEBYSCORE with WITHSCORES" {
+ create_default_zset
+ assert_equal {b 1 c 2 d 3} [r zrangebyscore zset 0 3 withscores]
+ assert_equal {d 3 c 2 b 1} [r zrevrangebyscore zset 3 0 withscores]
+ }
+
+ test "ZRANGEBYSCORE with LIMIT" {
+ create_default_zset
+ assert_equal {b c} [r zrangebyscore zset 0 10 LIMIT 0 2]
+ assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 3]
+ assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 10]
+ assert_equal {} [r zrangebyscore zset 0 10 LIMIT 20 10]
+ assert_equal {f e} [r zrevrangebyscore zset 10 0 LIMIT 0 2]
+ assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 3]
+ assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 10]
+ assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT 20 10]
+ }
+
+ test "ZRANGEBYSCORE with LIMIT and WITHSCORES" {
+ create_default_zset
+ assert_equal {e 4 f 5} [r zrangebyscore zset 2 5 LIMIT 2 3 WITHSCORES]
+ assert_equal {d 3 c 2} [r zrevrangebyscore zset 5 2 LIMIT 2 3 WITHSCORES]
+ }
+
+ test "ZRANGEBYSCORE with non-value min or max" {
+ assert_error "*not a double*" {r zrangebyscore fooz str 1}
+ assert_error "*not a double*" {r zrangebyscore fooz 1 str}
+ assert_error "*not a double*" {r zrangebyscore fooz 1 NaN}
+ }
tags {"slow"} {
test {ZRANGEBYSCORE fuzzy test, 100 ranges in 1000 elements sorted set} {
} {}
}
- test {ZRANGEBYSCORE with LIMIT} {
- r del zset
- r zadd zset 1 a
- r zadd zset 2 b
- r zadd zset 3 c
- r zadd zset 4 d
- r zadd zset 5 e
- list \
- [r zrangebyscore zset 0 10 LIMIT 0 2] \
- [r zrangebyscore zset 0 10 LIMIT 2 3] \
- [r zrangebyscore zset 0 10 LIMIT 2 10] \
- [r zrangebyscore zset 0 10 LIMIT 20 10]
- } {{a b} {c d e} {c d e} {}}
-
- test {ZRANGEBYSCORE with LIMIT and withscores} {
- r del zset
- r zadd zset 10 a
- r zadd zset 20 b
- r zadd zset 30 c
- r zadd zset 40 d
- r zadd zset 50 e
- r zrangebyscore zset 20 50 LIMIT 2 3 withscores
- } {d 40 e 50}
-
- test {ZREMRANGEBYSCORE basics} {
- r del zset
- r zadd zset 1 a
- r zadd zset 2 b
- r zadd zset 3 c
- r zadd zset 4 d
- r zadd zset 5 e
- list [r zremrangebyscore zset 2 4] [r zrange zset 0 -1]
- } {3 {a e}}
-
- test {ZREMRANGEBYSCORE from -inf to +inf} {
- r del zset
- r zadd zset 1 a
- r zadd zset 2 b
- r zadd zset 3 c
- r zadd zset 4 d
- r zadd zset 5 e
- list [r zremrangebyscore zset -inf +inf] [r zrange zset 0 -1]
- } {5 {}}
+ test "ZREMRANGEBYSCORE basics" {
+ proc remrangebyscore {min max} {
+ create_zset zset {1 a 2 b 3 c 4 d 5 e}
+ r zremrangebyscore zset $min $max
+ }
+
+ # inner range
+ assert_equal 3 [remrangebyscore 2 4]
+ assert_equal {a e} [r zrange zset 0 -1]
+
+ # start underflow
+ assert_equal 1 [remrangebyscore -10 1]
+ assert_equal {b c d e} [r zrange zset 0 -1]
+
+ # end overflow
+ assert_equal 1 [remrangebyscore 5 10]
+ assert_equal {a b c d} [r zrange zset 0 -1]
+
+ # switch min and max
+ assert_equal 0 [remrangebyscore 4 2]
+ assert_equal {a b c d e} [r zrange zset 0 -1]
+
+ # -inf to mid
+ assert_equal 3 [remrangebyscore -inf 3]
+ assert_equal {d e} [r zrange zset 0 -1]
+
+ # mid to +inf
+ assert_equal 3 [remrangebyscore 3 +inf]
+ assert_equal {a b} [r zrange zset 0 -1]
+
+ # -inf to +inf
+ assert_equal 5 [remrangebyscore -inf +inf]
+ assert_equal {} [r zrange zset 0 -1]
+
+ # exclusive min
+ assert_equal 4 [remrangebyscore (1 5]
+ assert_equal {a} [r zrange zset 0 -1]
+ assert_equal 3 [remrangebyscore (2 5]
+ assert_equal {a b} [r zrange zset 0 -1]
+
+ # exclusive max
+ assert_equal 4 [remrangebyscore 1 (5]
+ assert_equal {e} [r zrange zset 0 -1]
+ assert_equal 3 [remrangebyscore 1 (4]
+ assert_equal {d e} [r zrange zset 0 -1]
+
+ # exclusive min and max
+ assert_equal 3 [remrangebyscore (1 (5]
+ assert_equal {a e} [r zrange zset 0 -1]
+ }
+
+ test "ZREMRANGEBYSCORE with non-value min or max" {
+ assert_error "*not a double*" {r zremrangebyscore fooz str 1}
+ assert_error "*not a double*" {r zremrangebyscore fooz 1 str}
+ assert_error "*not a double*" {r zremrangebyscore fooz 1 NaN}
+ }
test "ZREMRANGEBYRANK basics" {
proc remrangebyrank {min max} {