X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/a0f643ea4c574d7f393a325f8f6e7c6c2e1c476f..be42428336aeb2059c6312cfeaac8d2bd23b53f1:/redis.c diff --git a/redis.c b/redis.c index 2d8b5d3e..2761541c 100644 --- a/redis.c +++ b/redis.c @@ -27,7 +27,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define REDIS_VERSION "0.100" +#define REDIS_VERSION "0.101" #include "fmacros.h" @@ -80,8 +80,13 @@ #define REDIS_HT_MINSLOTS 16384 /* Never resize the HT under this */ /* Command flags */ -#define REDIS_CMD_BULK 1 -#define REDIS_CMD_INLINE 2 +#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 /* Object types */ #define REDIS_STRING 0 @@ -245,6 +250,8 @@ struct redisServer { int masterport; redisClient *master; /* client that is master for this slave */ int replstate; + unsigned int maxclients; + unsigned int maxmemory; /* Sort parameters - qsort_r() is only available under BSD so we * have to take this state global, in order to pass it to sortCompare() */ int sort_desc; @@ -306,6 +313,7 @@ static int deleteKey(redisDb *db, robj *key); static time_t getExpire(redisDb *db, robj *key); static int setExpire(redisDb *db, robj *key, time_t when); static void updateSalvesWaitingBgsave(int bgsaveerr); +static void freeMemoryIfNeeded(void); static void authCommand(redisClient *c); static void pingCommand(redisClient *c); @@ -370,38 +378,38 @@ static void slaveofCommand(redisClient *c); static struct redisServer server; /* server global state */ static struct redisCommand cmdTable[] = { {"get",getCommand,2,REDIS_CMD_INLINE}, - {"set",setCommand,3,REDIS_CMD_BULK}, - {"setnx",setnxCommand,3,REDIS_CMD_BULK}, + {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, + {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"del",delCommand,-2,REDIS_CMD_INLINE}, {"exists",existsCommand,2,REDIS_CMD_INLINE}, - {"incr",incrCommand,2,REDIS_CMD_INLINE}, - {"decr",decrCommand,2,REDIS_CMD_INLINE}, + {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"mget",mgetCommand,-2,REDIS_CMD_INLINE}, - {"rpush",rpushCommand,3,REDIS_CMD_BULK}, - {"lpush",lpushCommand,3,REDIS_CMD_BULK}, + {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, + {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"rpop",rpopCommand,2,REDIS_CMD_INLINE}, {"lpop",lpopCommand,2,REDIS_CMD_INLINE}, {"llen",llenCommand,2,REDIS_CMD_INLINE}, {"lindex",lindexCommand,3,REDIS_CMD_INLINE}, - {"lset",lsetCommand,4,REDIS_CMD_BULK}, + {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"lrange",lrangeCommand,4,REDIS_CMD_INLINE}, {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE}, {"lrem",lremCommand,4,REDIS_CMD_BULK}, - {"sadd",saddCommand,3,REDIS_CMD_BULK}, + {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"srem",sremCommand,3,REDIS_CMD_BULK}, {"smove",smoveCommand,4,REDIS_CMD_BULK}, {"sismember",sismemberCommand,3,REDIS_CMD_BULK}, {"scard",scardCommand,2,REDIS_CMD_INLINE}, - {"sinter",sinterCommand,-2,REDIS_CMD_INLINE}, - {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE}, - {"sunion",sunionCommand,-2,REDIS_CMD_INLINE}, - {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE}, - {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE}, - {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE}, + {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"smembers",sinterCommand,2,REDIS_CMD_INLINE}, - {"incrby",incrbyCommand,3,REDIS_CMD_INLINE}, - {"decrby",decrbyCommand,3,REDIS_CMD_INLINE}, - {"getset",getSetCommand,3,REDIS_CMD_BULK}, + {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, + {"getset",getSetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE}, {"select",selectCommand,2,REDIS_CMD_INLINE}, {"move",moveCommand,3,REDIS_CMD_INLINE}, @@ -421,7 +429,7 @@ static struct redisCommand cmdTable[] = { {"sync",syncCommand,1,REDIS_CMD_INLINE}, {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE}, {"flushall",flushallCommand,1,REDIS_CMD_INLINE}, - {"sort",sortCommand,-2,REDIS_CMD_INLINE}, + {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"info",infoCommand,1,REDIS_CMD_INLINE}, {"monitor",monitorCommand,1,REDIS_CMD_INLINE}, {"ttl",ttlCommand,2,REDIS_CMD_INLINE}, @@ -862,6 +870,8 @@ static void initServerConfig() { server.dbfilename = "dump.rdb"; server.requirepass = NULL; server.shareobjects = 0; + server.maxclients = 0; + server.maxmemory = 0; ResetServerSaveParams(); appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */ @@ -1020,6 +1030,10 @@ static void loadServerConfig(char *filename) { if (server.dbnum < 1) { err = "Invalid number of databases"; goto loaderr; } + } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) { + server.maxclients = atoi(argv[1]); + } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) { + server.maxmemory = atoi(argv[1]); } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) { server.masterhost = sdsnew(argv[1]); server.masterport = atoi(argv[2]); @@ -1203,6 +1217,9 @@ static int processCommand(redisClient *c) { struct redisCommand *cmd; long long dirty; + /* Free some memory if needed (maxmemory setting) */ + if (server.maxmemory) freeMemoryIfNeeded(); + /* The QUIT command is handled as a special case. Normal command * procs are unable to close the client connection safely */ if (!strcasecmp(c->argv[0]->ptr,"quit")) { @@ -1219,6 +1236,10 @@ static int processCommand(redisClient *c) { addReplySds(c,sdsnew("-ERR wrong number of arguments\r\n")); resetClient(c); return 1; + } else if (server.maxmemory && cmd->flags & REDIS_CMD_DENYOOM && zmalloc_used_memory() > server.maxmemory) { + addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n")); + resetClient(c); + return 1; } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { int bulklen = atoi(c->argv[c->argc-1]->ptr); @@ -1417,7 +1438,7 @@ again: * on the query buffer try to process the next command. */ if (processCommand(c) && sdslen(c->querybuf)) goto again; return; - } else if (sdslen(c->querybuf) >= 1024) { + } else if (sdslen(c->querybuf) >= 1024*32) { redisLog(REDIS_DEBUG, "Client protocol error"); freeClient(c); return; @@ -1500,6 +1521,7 @@ static void addReplySds(redisClient *c, sds s) { static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { int cport, cfd; char cip[128]; + redisClient *c; REDIS_NOTUSED(el); REDIS_NOTUSED(mask); REDIS_NOTUSED(privdata); @@ -1510,11 +1532,23 @@ static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { return; } redisLog(REDIS_DEBUG,"Accepted %s:%d", cip, cport); - if (createClient(cfd) == NULL) { + if ((c = createClient(cfd)) == NULL) { redisLog(REDIS_WARNING,"Error allocating resoures for the client"); close(cfd); /* May be already closed, just ingore errors */ return; } + /* If maxclient directive is set and this is one client more... close the + * connection. Note that we create the client instead to check before + * for this condition, since now the socket is already set in nonblocking + * mode and we can send an error for free using the Kernel I/O */ + if (server.maxclients && listLength(server.clients) > server.maxclients) { + char *err = "-ERR max number of clients reached\r\n"; + + /* That's a best effort error message, don't check write errors */ + (void) write(c->fd,err,strlen(err)); + freeClient(c); + return; + } server.stat_numconnections++; } @@ -3957,6 +3991,58 @@ static void slaveofCommand(redisClient *c) { addReply(c,shared.ok); } +/* ============================ Maxmemory directive ======================== */ + +/* This function gets called when 'maxmemory' is set on the config file to limit + * the max memory used by the server, and we are out of memory. + * This function will try to, in order: + * + * - Free objects from the free list + * - Try to remove keys with an EXPIRE set + * + * It is not possible to free enough memory to reach used-memory < maxmemory + * the server will start refusing commands that will enlarge even more the + * memory usage. + */ +static void freeMemoryIfNeeded(void) { + while (server.maxmemory && zmalloc_used_memory() > server.maxmemory) { + if (listLength(server.objfreelist)) { + robj *o; + + listNode *head = listFirst(server.objfreelist); + o = listNodeValue(head); + listDelNode(server.objfreelist,head); + zfree(o); + } else { + int j, k, freed = 0; + + for (j = 0; j < server.dbnum; j++) { + int minttl = -1; + robj *minkey = NULL; + struct dictEntry *de; + + if (dictSize(server.db[j].expires)) { + freed = 1; + /* From a sample of three keys drop the one nearest to + * the natural expire */ + for (k = 0; k < 3; k++) { + time_t t; + + de = dictGetRandomKey(server.db[j].expires); + t = (time_t) dictGetEntryVal(de); + if (minttl == -1 || t < minttl) { + minkey = dictGetEntryKey(de); + minttl = t; + } + } + deleteKey(server.db+j,minkey); + } + } + if (!freed) return; /* nothing to free... */ + } + } +} + /* =================================== Main! ================================ */ #ifdef __linux__