struct redisServer server; /* server global state */
struct redisCommand *commandTable;
-/* Our command table. Command flags are expressed using strings where every
- * character represents a flag. Later the populateCommandTable() function will
- * take care of populating the real 'flags' field using this characters.
+/* Our command table.
+ *
+ * Every entry is composed of the following fields:
+ *
+ * name: a string representing the command name.
+ * function: pointer to the C function implementing the command.
+ * arity: number of arguments, it is possible to use -N to say >= N
+ * sflags: command flags as string. See below for a table of flags.
+ * flags: flags as bitmask. Computed by Redis using the 'sflags' field.
+ * get_keys_proc: an optional function to get key arguments from a command.
+ * This is only used when the following three fields are not
+ * enough to specify what arguments are keys.
+ * first_key_index: first argument that is a key
+ * last_key_index: last argument that is a key
+ * key_step: step to get all the keys from first to last argument. For instance
+ * in MSET the step is two since arguments are key,val,key,val,...
+ * microseconds: microseconds of total execution time for this command.
+ * calls: total number of calls of this command.
+ *
+ * The flags, microseconds and calls fields are computed by Redis and should
+ * always be set to zero.
+ *
+ * Command flags are expressed using strings where every character represents
+ * a flag. Later the populateCommandTable() function will take care of
+ * populating the real 'flags' field using this characters.
*
* This is the meaning of the flags:
*
* p: Pub/Sub related command.
* 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
+ * 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[] = {
{"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},
{"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0},
{"rpop",rpopCommand,2,"w",0,NULL,1,1,1,0,0},
{"lpop",lpopCommand,2,"w",0,NULL,1,1,1,0,0},
- {"brpop",brpopCommand,-3,"w",0,NULL,1,1,1,0,0},
- {"brpoplpush",brpoplpushCommand,4,"wm",0,NULL,1,2,1,0,0},
- {"blpop",blpopCommand,-3,"w",0,NULL,1,-2,1,0,0},
+ {"brpop",brpopCommand,-3,"ws",0,NULL,1,1,1,0,0},
+ {"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0},
+ {"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
{"llen",llenCommand,2,"r",0,NULL,1,1,1,0,0},
{"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0},
{"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0},
{"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
{"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},
{"hincrby",hincrbyCommand,4,"wm",0,NULL,1,1,1,0,0},
+ {"hincrbyfloat",hincrbyfloatCommand,4,"wm",0,NULL,1,1,1,0,0},
{"hdel",hdelCommand,-3,"w",0,NULL,1,1,1,0,0},
{"hlen",hlenCommand,2,"r",0,NULL,1,1,1,0,0},
{"hkeys",hkeysCommand,2,"r",0,NULL,1,1,1,0,0},
{"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0},
{"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
{"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
+ {"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0},
{"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},
{"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},
+ {"auth",authCommand,2,"rs",0,NULL,0,0,0,0,0},
{"ping",pingCommand,1,"r",0,NULL,0,0,0,0,0},
{"echo",echoCommand,2,"r",0,NULL,0,0,0,0,0},
- {"save",saveCommand,1,"ar",0,NULL,0,0,0,0,0},
+ {"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
{"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
{"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0},
- {"shutdown",shutdownCommand,1,"ar",0,NULL,0,0,0,0,0},
+ {"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,"rs",0,NULL,0,0,0,0,0},
{"info",infoCommand,-1,"r",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,"aws",0,NULL,0,0,0,0,0},
- {"debug",debugCommand,-2,"aw",0,NULL,0,0,0,0,0},
+ {"debug",debugCommand,-2,"aws",0,NULL,0,0,0,0,0},
{"config",configCommand,-2,"ar",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},
{"cluster",clusterCommand,-2,"ar",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,"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}
+ {"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
+ {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0}
};
/*============================ Utility functions ============================ */
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
* of the keys were expired. */
do {
long num = dictSize(db->expires);
- time_t now = time(NULL);
+ long long now = mstime();
expired = 0;
if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
num = REDIS_EXPIRELOOKUPS_PER_CRON;
while (num--) {
dictEntry *de;
- time_t t;
+ long long t;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
- t = (time_t) dictGetEntryVal(de);
+ t = dictGetSignedIntegerVal(de);
if (now > t) {
- sds key = dictGetEntryKey(de);
+ sds key = dictGetKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
propagateExpire(db,keyobj);
/* We received a SIGTERM, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */
if (server.shutdown_asap) {
- if (prepareForShutdown() == REDIS_OK) exit(0);
+ if (prepareForShutdown(0) == REDIS_OK) exit(0);
redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
}
"-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. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));
shared.space = createObject(REDIS_STRING,sdsnew(" "));
shared.colon = createObject(REDIS_STRING,sdsnew(":"));
shared.plus = createObject(REDIS_STRING,sdsnew("+"));
server.dbnum = REDIS_DEFAULT_DBNUM;
server.verbosity = REDIS_VERBOSE;
server.maxidletime = REDIS_MAXIDLETIME;
+ server.client_max_querybuf_len = REDIS_MAX_QUERYBUF_LEN;
server.saveparams = NULL;
server.loading = 0;
server.logfile = NULL; /* NULL = log on standard output */
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;
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_caller = NULL;
server.lua_time_limit = REDIS_LUA_TIME_LIMIT;
+ server.lua_client = NULL;
+ server.lua_timedout = 0;
updateLRUClock();
resetServerSaveParams();
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
return REDIS_OK;
}
+ /* Lua script too slow? Only allow SHUTDOWN NOSAVE and SCRIPT KILL. */
+ if (server.lua_timedout &&
+ !(c->cmd->proc != shutdownCommand &&
+ c->argc == 2 &&
+ tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
+ !(c->cmd->proc == scriptCommand &&
+ c->argc == 2 &&
+ tolower(((char*)c->argv[1]->ptr)[0]) == 'k'))
+ {
+ addReply(c, shared.slowscripterr);
+ return REDIS_OK;
+ }
+
/* Exec the command */
if (c->flags & REDIS_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
/*================================== Shutdown =============================== */
-int prepareForShutdown() {
+int prepareForShutdown(int flags) {
+ int save = flags & REDIS_SHUTDOWN_SAVE;
+ int nosave = flags & REDIS_SHUTDOWN_NOSAVE;
+
redisLog(REDIS_WARNING,"User requested shutdown...");
/* Kill the saving child if there is a background saving in progress.
We want to avoid race conditions, for instance our saving child may
redisLog(REDIS_NOTICE,"Calling fsync() on the AOF file.");
aof_fsync(server.appendfd);
}
- if (server.saveparamslen > 0) {
+ if ((server.saveparamslen > 0 && !nosave) || save) {
redisLog(REDIS_NOTICE,"Saving the final RDB snapshot before exiting.");
/* Snapshotting. Perform a SYNC SAVE and exit */
if (rdbSave(server.dbfilename) != REDIS_OK) {
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
{
de = dictGetRandomKey(dict);
- bestkey = dictGetEntryKey(de);
+ bestkey = dictGetKey(de);
}
/* volatile-lru and allkeys-lru policy */
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 */
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 */
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);