]> git.saurik.com Git - redis.git/blobdiff - src/redis.c
new tests for EXPIRE family functions, new millisecond precision, and new millisecond...
[redis.git] / src / redis.c
index ab92cc5606f2bbccae9235db8718836100476964..7d2094390c8a37363faf845200836c32d20cca5a 100644 (file)
@@ -81,12 +81,17 @@ struct redisCommand *commandTable;
  * m: may increase memory usage once called. Don't allow if out of memory.
  * a: admin command, like SAVE or SHUTDOWN.
  * p: Pub/Sub related command.
- * f: force replication of this command, regarless of server.dirty. */
+ * f: force replication of this command, regarless of server.dirty.
+ * s: command not allowed in scripts.
+ * r: random command. Command is not deterministic, that is, the same command
+ *    with the same arguments, with the same key space, may have different
+ *    results. For instance SPOP and RANDOMKEY are two random commands. */
 struct redisCommand redisCommandTable[] = {
     {"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
     {"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
     {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
     {"setex",setexCommand,4,"wm",0,noPreloadGetKeys,2,2,1,0,0},
+    {"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,2,2,1,0,0},
     {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
     {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
     {"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0},
@@ -121,8 +126,8 @@ struct redisCommand redisCommandTable[] = {
     {"smove",smoveCommand,4,"w",0,NULL,1,2,1,0,0},
     {"sismember",sismemberCommand,3,"r",0,NULL,1,1,1,0,0},
     {"scard",scardCommand,2,"r",0,NULL,1,1,1,0,0},
-    {"spop",spopCommand,2,"w",0,NULL,1,1,1,0,0},
-    {"srandmember",srandmemberCommand,2,"r",0,NULL,1,1,1,0,0},
+    {"spop",spopCommand,2,"wRs",0,NULL,1,1,1,0,0},
+    {"srandmember",srandmemberCommand,2,"rR",0,NULL,1,1,1,0,0},
     {"sinter",sinterCommand,-2,"r",0,NULL,1,-1,1,0,0},
     {"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,2,-1,1,0,0},
     {"sunion",sunionCommand,-2,"r",0,NULL,1,-1,1,0,0},
@@ -163,13 +168,15 @@ struct redisCommand redisCommandTable[] = {
     {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0},
     {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
     {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
-    {"randomkey",randomkeyCommand,1,"r",0,NULL,0,0,0,0,0},
+    {"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
     {"select",selectCommand,2,"r",0,NULL,0,0,0,0,0},
     {"move",moveCommand,3,"w",0,NULL,1,1,1,0,0},
     {"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
     {"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
     {"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
     {"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
+    {"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
+    {"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
     {"keys",keysCommand,2,"r",0,NULL,0,0,0,0,0},
     {"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0},
     {"auth",authCommand,2,"r",0,NULL,0,0,0,0,0},
@@ -181,36 +188,39 @@ struct redisCommand redisCommandTable[] = {
     {"shutdown",shutdownCommand,1,"ar",0,NULL,0,0,0,0,0},
     {"lastsave",lastsaveCommand,1,"r",0,NULL,0,0,0,0,0},
     {"type",typeCommand,2,"r",0,NULL,1,1,1,0,0},
-    {"multi",multiCommand,1,"r",0,NULL,0,0,0,0,0},
-    {"exec",execCommand,1,"wm",0,NULL,0,0,0,0,0},
-    {"discard",discardCommand,1,"r",0,NULL,0,0,0,0,0},
-    {"sync",syncCommand,1,"ar",0,NULL,0,0,0,0,0},
+    {"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0},
+    {"exec",execCommand,1,"wms",0,NULL,0,0,0,0,0},
+    {"discard",discardCommand,1,"rs",0,NULL,0,0,0,0,0},
+    {"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
     {"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0},
     {"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0},
     {"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0},
     {"info",infoCommand,-1,"r",0,NULL,0,0,0,0,0},
-    {"monitor",monitorCommand,1,"ar",0,NULL,0,0,0,0,0},
+    {"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
     {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
+    {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0},
     {"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0},
-    {"slaveof",slaveofCommand,3,"aw",0,NULL,0,0,0,0,0},
+    {"slaveof",slaveofCommand,3,"aws",0,NULL,0,0,0,0,0},
     {"debug",debugCommand,-2,"aw",0,NULL,0,0,0,0,0},
     {"config",configCommand,-2,"ar",0,NULL,0,0,0,0,0},
-    {"subscribe",subscribeCommand,-2,"rp",0,NULL,0,0,0,0,0},
-    {"unsubscribe",unsubscribeCommand,-1,"rp",0,NULL,0,0,0,0,0},
-    {"psubscribe",psubscribeCommand,-2,"rp",0,NULL,0,0,0,0,0},
-    {"punsubscribe",punsubscribeCommand,-1,"rp",0,NULL,0,0,0,0,0},
+    {"subscribe",subscribeCommand,-2,"rps",0,NULL,0,0,0,0,0},
+    {"unsubscribe",unsubscribeCommand,-1,"rps",0,NULL,0,0,0,0,0},
+    {"psubscribe",psubscribeCommand,-2,"rps",0,NULL,0,0,0,0,0},
+    {"punsubscribe",punsubscribeCommand,-1,"rps",0,NULL,0,0,0,0,0},
     {"publish",publishCommand,3,"rpf",0,NULL,0,0,0,0,0},
-    {"watch",watchCommand,-2,"r",0,noPreloadGetKeys,1,-1,1,0,0},
-    {"unwatch",unwatchCommand,1,"r",0,NULL,0,0,0,0,0},
+    {"watch",watchCommand,-2,"rs",0,noPreloadGetKeys,1,-1,1,0,0},
+    {"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0},
     {"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
-    {"restore",restoreCommand,4,"awm",0,NULL,0,0,0,0,0},
+    {"restore",restoreCommand,4,"awm",0,NULL,1,1,1,0,0},
     {"migrate",migrateCommand,6,"aw",0,NULL,0,0,0,0,0},
+    {"asking",askingCommand,1,"r",0,NULL,0,0,0,0,0},
     {"dump",dumpCommand,2,"ar",0,NULL,0,0,0,0,0},
     {"object",objectCommand,-2,"r",0,NULL,0,0,0,0,0},
     {"client",clientCommand,-2,"ar",0,NULL,0,0,0,0,0},
-    {"eval",evalCommand,-3,"wm",0,zunionInterGetKeys,0,0,0,0,0},
-    {"evalsha",evalShaCommand,-3,"wm",0,zunionInterGetKeys,0,0,0,0,0},
-    {"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0}
+    {"eval",evalCommand,-3,"wms",0,zunionInterGetKeys,0,0,0,0,0},
+    {"evalsha",evalShaCommand,-3,"wms",0,zunionInterGetKeys,0,0,0,0,0},
+    {"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
+    {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0}
 };
 
 /*============================ Utility functions ============================ */
@@ -282,6 +292,11 @@ long long ustime(void) {
     return ust;
 }
 
+/* Return the UNIX time in milliseconds */
+long long mstime(void) {
+    return ustime()/1000;
+}
+
 /*====================== Hash table type implementation  ==================== */
 
 /* This is an hash table type that uses the SDS dynamic strings libary as
@@ -557,9 +572,9 @@ void activeExpireCycle(void) {
                 time_t t;
 
                 if ((de = dictGetRandomKey(db->expires)) == NULL) break;
-                t = (time_t) dictGetEntryVal(de);
+                t = (time_t) dictGetVal(de);
                 if (now > t) {
-                    sds key = dictGetEntryKey(de);
+                    sds key = dictGetKey(de);
                     robj *keyobj = createStringObject(key,sdslen(key));
 
                     propagateExpire(db,keyobj);
@@ -787,6 +802,8 @@ void createSharedObjects(void) {
         "-NOSCRIPT No matching script. Please use EVAL.\r\n"));
     shared.loadingerr = createObject(REDIS_STRING,sdsnew(
         "-LOADING Redis is loading the dataset in memory\r\n"));
+    shared.slowscripterr = createObject(REDIS_STRING,sdsnew(
+        "-BUSY Redis is busy running a script. Please wait or stop the server with SHUTDOWN.\r\n"));
     shared.space = createObject(REDIS_STRING,sdsnew(" "));
     shared.colon = createObject(REDIS_STRING,sdsnew(":"));
     shared.plus = createObject(REDIS_STRING,sdsnew("+"));
@@ -818,6 +835,7 @@ void initServerConfig() {
     server.port = REDIS_SERVERPORT;
     server.bindaddr = NULL;
     server.unixsocket = NULL;
+    server.unixsocketperm = 0;
     server.ipfd = -1;
     server.sofd = -1;
     server.dbnum = REDIS_DEFAULT_DBNUM;
@@ -847,7 +865,7 @@ void initServerConfig() {
     server.requirepass = NULL;
     server.rdbcompression = 1;
     server.activerehashing = 1;
-    server.maxclients = 0;
+    server.maxclients = REDIS_MAX_CLIENTS;
     server.bpop_blocked_clients = 0;
     server.maxmemory = 0;
     server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
@@ -860,9 +878,13 @@ void initServerConfig() {
     server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
     server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
     server.shutdown_asap = 0;
+    server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD;
+    server.repl_timeout = REDIS_REPL_TIMEOUT;
     server.cluster_enabled = 0;
     server.cluster.configfile = zstrdup("nodes.conf");
     server.lua_time_limit = REDIS_LUA_TIME_LIMIT;
+    server.lua_client = NULL;
+    server.lua_timedout = 0;
 
     updateLRUClock();
     resetServerSaveParams();
@@ -931,7 +953,7 @@ void initServer() {
     }
     if (server.unixsocket != NULL) {
         unlink(server.unixsocket); /* don't care if this fails */
-        server.sofd = anetUnixServer(server.neterr,server.unixsocket);
+        server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);
         if (server.sofd == ANET_ERR) {
             redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
             exit(1);
@@ -989,6 +1011,39 @@ void initServer() {
     slowlogInit();
     bioInit();
     srand(time(NULL)^getpid());
+
+    /* Try to raise the max number of open files accordingly to the
+     * configured max number of clients. Also account for 32 additional
+     * file descriptors as we need a few more for persistence, listening
+     * sockets, log files and so forth. */
+    {
+        rlim_t maxfiles = server.maxclients+32;
+        struct rlimit limit;
+
+        if (maxfiles < 1024) maxfiles = 1024;
+        if (getrlimit(RLIMIT_NOFILE,&limit) == -1) {
+            redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s), assuming 1024 and setting the max clients configuration accordingly.",
+                strerror(errno));
+            server.maxclients = 1024-32;
+        } else {
+            rlim_t oldlimit = limit.rlim_cur;
+
+            /* Set the max number of files if the current limit is not enough
+             * for our needs. */
+            if (oldlimit < maxfiles) {
+                limit.rlim_cur = maxfiles;
+                limit.rlim_max = maxfiles;
+                if (setrlimit(RLIMIT_NOFILE,&limit) == -1) {
+                    server.maxclients = oldlimit-32;
+                    redisLog(REDIS_WARNING,"Unable to set the max number of files limit to %d (%s), setting the max clients configuration to %d.",
+                        (int) maxfiles, strerror(errno), (int) server.maxclients);
+                } else {
+                    redisLog(REDIS_NOTICE,"Max number of open files set to %d",
+                        (int) maxfiles);
+                }
+            }
+        }
+    }
 }
 
 /* Populates the Redis Command Table starting from the hard coded list
@@ -1010,6 +1065,8 @@ void populateCommandTable(void) {
             case 'a': c->flags |= REDIS_CMD_ADMIN; break;
             case 'p': c->flags |= REDIS_CMD_PUBSUB; break;
             case 'f': c->flags |= REDIS_CMD_FORCE_REPLICATION; break;
+            case 's': c->flags |= REDIS_CMD_NOSCRIPT; break;
+            case 'R': c->flags |= REDIS_CMD_RANDOM; break;
             default: redisPanic("Unsupported command flag"); break;
             }
             f++;
@@ -1173,6 +1230,12 @@ int processCommand(redisClient *c) {
         return REDIS_OK;
     }
 
+    /* Lua script too slow? */
+    if (server.lua_timedout && c->cmd->proc != shutdownCommand) {
+        addReply(c, shared.slowscripterr);
+        return REDIS_OK;
+    }
+
     /* Exec the command */
     if (c->flags & REDIS_MULTI &&
         c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
@@ -1230,6 +1293,10 @@ int prepareForShutdown() {
     /* Close the listening sockets. Apparently this allows faster restarts. */
     if (server.ipfd != -1) close(server.ipfd);
     if (server.sofd != -1) close(server.sofd);
+    if (server.unixsocket) {
+        redisLog(REDIS_NOTICE,"Removing the unix socket file.");
+        unlink(server.unixsocket); /* don't care if this fails */
+    }
 
     redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
     return REDIS_OK;
@@ -1238,7 +1305,9 @@ int prepareForShutdown() {
 /*================================== Commands =============================== */
 
 void authCommand(redisClient *c) {
-    if (!server.requirepass || !strcmp(c->argv[1]->ptr, server.requirepass)) {
+    if (!server.requirepass) {
+        addReplyError(c,"Client sent AUTH, but no password is set");
+    } else if (!strcmp(c->argv[1]->ptr, server.requirepass)) {
       c->authenticated = 1;
       addReply(c,shared.ok);
     } else {
@@ -1504,10 +1573,10 @@ sds genRedisInfoString(char *section) {
         "used_cpu_user:%.2f\r\n"
         "used_cpu_sys_children:%.2f\r\n"
         "used_cpu_user_children:%.2f\r\n",
-        (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
         (float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
-        (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
-        (float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000);
+        (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
+        (float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
+        (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000);
     }
 
     /* cmdtime */
@@ -1619,7 +1688,7 @@ void freeMemoryIfNeeded(void) {
                 server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
             {
                 de = dictGetRandomKey(dict);
-                bestkey = dictGetEntryKey(de);
+                bestkey = dictGetKey(de);
             }
 
             /* volatile-lru and allkeys-lru policy */
@@ -1632,12 +1701,12 @@ void freeMemoryIfNeeded(void) {
                     robj *o;
 
                     de = dictGetRandomKey(dict);
-                    thiskey = dictGetEntryKey(de);
+                    thiskey = dictGetKey(de);
                     /* When policy is volatile-lru we need an additonal lookup
                      * to locate the real key, as dict is set to db->expires. */
                     if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
                         de = dictFind(db->dict, thiskey);
-                    o = dictGetEntryVal(de);
+                    o = dictGetVal(de);
                     thisval = estimateObjectIdleTime(o);
 
                     /* Higher idle time is better candidate for deletion */
@@ -1655,8 +1724,8 @@ void freeMemoryIfNeeded(void) {
                     long thisval;
 
                     de = dictGetRandomKey(dict);
-                    thiskey = dictGetEntryKey(de);
-                    thisval = (long) dictGetEntryVal(de);
+                    thiskey = dictGetKey(de);
+                    thisval = (long) dictGetVal(de);
 
                     /* Expire sooner (minor expire unix timestamp) is better
                      * candidate for deletion */
@@ -1789,8 +1858,13 @@ int main(int argc, char **argv) {
         if (loadAppendOnlyFile(server.appendfilename) == REDIS_OK)
             redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
     } else {
-        if (rdbLoad(server.dbfilename) == REDIS_OK)
-            redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",(float)(ustime()-start)/1000000);
+        if (rdbLoad(server.dbfilename) == REDIS_OK) {
+            redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",
+                (float)(ustime()-start)/1000000);
+        } else if (errno != ENOENT) {
+            redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting.");
+            exit(1);
+        }
     }
     if (server.ipfd > 0)
         redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);