#include <float.h>
#include <math.h>
#include <sys/resource.h>
+#include <sys/utsname.h>
/* Our shared "common" objects */
{"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},
- {"exec",execCommand,1,"wms",0,NULL,0,0,0,0,0},
+ {"exec",execCommand,1,"s",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},
{"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,"aws",0,NULL,0,0,0,0,0},
+ {"slaveof",slaveofCommand,3,"as",0,NULL,0,0,0,0,0},
+ {"debug",debugCommand,-2,"as",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},
{"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},
+ {"publish",publishCommand,3,"pf",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},
{"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0},
{"object",objectCommand,-2,"r",0,NULL,2,2,2,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},
+ {"eval",evalCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0},
+ {"evalsha",evalShaCommand,-3,"s",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},
{"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0}
void redisLogRaw(int level, const char *msg) {
const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING };
const char *c = ".-*#";
- time_t now = time(NULL);
FILE *fp;
char buf[64];
int rawmode = (level & REDIS_LOG_RAW);
if (rawmode) {
fprintf(fp,"%s",msg);
} else {
- strftime(buf,sizeof(buf),"%d %b %H:%M:%S",localtime(&now));
+ int off;
+ struct timeval tv;
+
+ gettimeofday(&tv,NULL);
+ off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",localtime(&tv.tv_sec));
+ snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg);
}
fflush(fp);
redisLogRaw(level,msg);
}
+/* Log a fixed message without printf-alike capabilities, in a way that is
+ * safe to call from a signal handler.
+ *
+ * We actually use this only for signals that are not fatal from the point
+ * of view of Redis. Signals that are going to kill the server anyway and
+ * where we need printf-alike features are served by redisLog(). */
+void redisLogFromHandler(int level, const char *msg) {
+ int fd;
+ char buf[64];
+
+ if ((level&0xff) < server.verbosity ||
+ (server.logfile == NULL && server.daemonize)) return;
+ fd = server.logfile ?
+ open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) :
+ STDOUT_FILENO;
+ if (fd == -1) return;
+ ll2string(buf,sizeof(buf),getpid());
+ if (write(fd,"[",1) == -1) goto err;
+ if (write(fd,buf,strlen(buf)) == -1) goto err;
+ if (write(fd," | signal handler] (",20) == -1) goto err;
+ ll2string(buf,sizeof(buf),time(NULL));
+ if (write(fd,buf,strlen(buf)) == -1) goto err;
+ if (write(fd,") ",2) == -1) goto err;
+ if (write(fd,msg,strlen(msg)) == -1) goto err;
+ if (write(fd,"\n",1) == -1) goto err;
+err:
+ if (server.logfile) close(fd);
+}
+
/* Redis generally does not try to recover from out of memory conditions
* when allocating objects or strings, it is not clear if it will be possible
* to report this condition to the client since the networking layer itself
return ustime()/1000;
}
+/* After an RDB dump or AOF rewrite we exit from children using _exit() instead of
+ * exit(), because the latter may interact with the same file objects used by
+ * the parent process. However if we are testing the coverage normal exit() is
+ * used in order to obtain the right coverage information. */
+void exitFromChild(int retcode) {
+#ifdef COVERAGE_TEST
+ exit(retcode);
+#else
+ _exit(retcode);
+#endif
+}
+
/*====================== Hash table type implementation ==================== */
/* This is an hash table type that uses the SDS dynamic strings libary as
}
void updateLRUClock(void) {
- server.lruclock = (time(NULL)/REDIS_LRU_CLOCK_RESOLUTION) &
+ server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) &
REDIS_LRU_CLOCK_MAX;
}
return sum / REDIS_OPS_SEC_SAMPLES;
}
-void clientsCronHandleTimeout(redisClient *c) {
+/* Check for timeouts. Returns non-zero if the client was terminated */
+int clientsCronHandleTimeout(redisClient *c) {
time_t now = server.unixtime;
if (server.maxidletime &&
{
redisLog(REDIS_VERBOSE,"Closing idle client");
freeClient(c);
+ return 1;
} else if (c->flags & REDIS_BLOCKED) {
if (c->bpop.timeout != 0 && c->bpop.timeout < now) {
addReply(c,shared.nullmultibulk);
unblockClientWaitingData(c);
}
}
+ return 0;
}
/* The client query buffer is an sds.c string that can end with a lot of
- * free space not used, this function reclaims space if needed. */
-void clientsCronResizeQueryBuffer(redisClient *c) {
+ * free space not used, this function reclaims space if needed.
+ *
+ * The funciton always returns 0 as it never terminates the client. */
+int clientsCronResizeQueryBuffer(redisClient *c) {
size_t querybuf_size = sdsAllocSize(c->querybuf);
time_t idletime = server.unixtime - c->lastinteraction;
/* Reset the peak again to capture the peak memory usage in the next
* cycle. */
c->querybuf_peak = 0;
+ return 0;
}
void clientsCron(void) {
listRotate(server.clients);
head = listFirst(server.clients);
c = listNodeValue(head);
- clientsCronHandleTimeout(c);
- clientsCronResizeQueryBuffer(c);
+ /* The following functions do different service checks on the client.
+ * The protocol is that they return non-zero if the client was
+ * terminated. */
+ if (clientsCronHandleTimeout(c)) continue;
+ if (clientsCronResizeQueryBuffer(c)) continue;
}
}
REDIS_NOTUSED(id);
REDIS_NOTUSED(clientData);
+ /* Software watchdog: deliver the SIGALRM that will reach the signal
+ * handler if we don't return here fast enough. */
+ if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
+
/* We take a cached value of the unix time in the global state because
* with virtual memory and aging there is to store the current time
* in objects at every object access, and accuracy is not needed.
updateDictResizePolicy();
}
} else {
- time_t now = time(NULL);
-
/* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now */
for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = server.saveparams+j;
if (server.dirty >= sp->changes &&
- now-server.lastsave > sp->seconds) {
+ server.unixtime-server.lastsave > sp->seconds) {
redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, sp->seconds);
rdbSaveBackground(server.rdb_filename);
"-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.masterdownerr = createObject(REDIS_STRING,sdsnew(
+ "-MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n"));
shared.bgsaveerr = createObject(REDIS_STRING,sdsnew(
- "-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Write commands are disabled. Please check Redis logs for details about the error.\r\n"));
+ "-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.\r\n"));
+ shared.roslaveerr = createObject(REDIS_STRING,sdsnew(
+ "-READONLY You can't write against a read only slave.\r\n"));
+ shared.oomerr = createObject(REDIS_STRING,sdsnew(
+ "-OOM command not allowed when used memory > 'maxmemory'.\r\n"));
shared.space = createObject(REDIS_STRING,sdsnew(" "));
shared.colon = createObject(REDIS_STRING,sdsnew(":"));
shared.plus = createObject(REDIS_STRING,sdsnew("+"));
- shared.select0 = createStringObject("select 0\r\n",10);
- shared.select1 = createStringObject("select 1\r\n",10);
- shared.select2 = createStringObject("select 2\r\n",10);
- shared.select3 = createStringObject("select 3\r\n",10);
- shared.select4 = createStringObject("select 4\r\n",10);
- shared.select5 = createStringObject("select 5\r\n",10);
- shared.select6 = createStringObject("select 6\r\n",10);
- shared.select7 = createStringObject("select 7\r\n",10);
- shared.select8 = createStringObject("select 8\r\n",10);
- shared.select9 = createStringObject("select 9\r\n",10);
+
+ for (j = 0; j < REDIS_SHARED_SELECT_CMDS; j++) {
+ shared.select[j] = createObject(REDIS_STRING,
+ sdscatprintf(sdsempty(),"select %d\r\n", j));
+ }
shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13);
shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14);
shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15);
server.aof_rewrite_base_size = 0;
server.aof_rewrite_scheduled = 0;
server.aof_last_fsync = time(NULL);
+ server.aof_delayed_fsync = 0;
server.aof_fd = -1;
server.aof_selected_db = -1; /* Make sure the first time will not match */
server.aof_flush_postponed_start = 0;
server.aof_filename = zstrdup("appendonly.aof");
server.requirepass = NULL;
server.rdb_compression = 1;
+ server.rdb_checksum = 1;
server.activerehashing = 1;
server.maxclients = REDIS_MAX_CLIENTS;
server.bpop_blocked_clients = 0;
server.repl_state = REDIS_REPL_NONE;
server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = 1;
- server.repl_down_since = -1;
+ server.repl_slave_ro = 1;
+ server.repl_down_since = time(NULL);
/* Client output buffer limits */
server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].hard_limit_bytes = 0;
server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN;
server.slowlog_max_len = REDIS_SLOWLOG_MAX_LEN;
- /* Assert */
+ /* Debugging */
server.assert_failed = "<no assertion failed>";
server.assert_file = "<no file>";
server.assert_line = 0;
server.bug_report_start = 0;
+ server.watchdog_period = 0;
}
/* This function will try to raise the max number of open files accordingly to
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));
/* 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;
+ rlim_t f;
+
+ f = maxfiles;
+ while(f > oldlimit) {
+ limit.rlim_cur = f;
+ limit.rlim_max = f;
+ if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break;
+ f -= 128;
+ }
+ if (f < oldlimit) f = oldlimit;
+ if (f != maxfiles) {
+ server.maxclients = f-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 {
if (server.maxmemory) {
int retval = freeMemoryIfNeeded();
if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {
- addReplyError(c,
- "command not allowed when used memory > 'maxmemory'");
+ addReply(c, shared.oomerr);
return REDIS_OK;
}
}
return REDIS_OK;
}
+ /* Don't accept wirte commands if this is a read only slave. But
+ * accept write commands if this is our master. */
+ if (server.masterhost && server.repl_slave_ro &&
+ !(c->flags & REDIS_MASTER) &&
+ c->cmd->flags & REDIS_CMD_WRITE)
+ {
+ addReply(c, shared.roslaveerr);
+ return REDIS_OK;
+ }
+
/* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
if ((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0)
&&
server.repl_serve_stale_data == 0 &&
c->cmd->proc != infoCommand && c->cmd->proc != slaveofCommand)
{
- addReplyError(c,
- "link with MASTER is down and slave-serve-stale-data is set to no");
+ addReply(c, shared.masterdownerr);
return REDIS_OK;
}
/* Lua script too slow? Only allow SHUTDOWN NOSAVE and SCRIPT KILL. */
if (server.lua_timedout &&
- !(c->cmd->proc != shutdownCommand &&
+ !(c->cmd->proc == shutdownCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
!(c->cmd->proc == scriptCommand &&
* on memory corruption problems. */
sds genRedisInfoString(char *section) {
sds info = sdsempty();
- time_t uptime = time(NULL)-server.stat_starttime;
+ time_t uptime = server.unixtime-server.stat_starttime;
int j, numcommands;
struct rusage self_ru, c_ru;
unsigned long lol, bib;
/* Server */
if (allsections || defsections || !strcasecmp(section,"server")) {
+ struct utsname name;
+
if (sections++) info = sdscat(info,"\r\n");
+ uname(&name);
info = sdscatprintf(info,
"# Server\r\n"
"redis_version:%s\r\n"
"redis_git_sha1:%s\r\n"
"redis_git_dirty:%d\r\n"
+ "os:%s %s %s\r\n"
"arch_bits:%d\r\n"
"multiplexing_api:%s\r\n"
"gcc_version:%d.%d.%d\r\n"
REDIS_VERSION,
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
+ name.sysname, name.release, name.machine,
server.arch_bits,
aeGetApiName(),
#ifdef __GNUC__
"bgsave_in_progress:%d\r\n"
"last_save_time:%ld\r\n"
"last_bgsave_status:%s\r\n"
- "bgrewriteaof_in_progress:%d\r\n",
+ "bgrewriteaof_in_progress:%d\r\n"
+ "bgrewriteaof_scheduled:%d\r\n",
server.loading,
server.aof_state != REDIS_AOF_OFF,
server.dirty,
server.rdb_child_pid != -1,
server.lastsave,
server.lastbgsave_status == REDIS_OK ? "ok" : "err",
- server.aof_child_pid != -1);
+ server.aof_child_pid != -1,
+ server.aof_rewrite_scheduled);
if (server.aof_state != REDIS_AOF_OFF) {
info = sdscatprintf(info,
"aof_base_size:%lld\r\n"
"aof_pending_rewrite:%d\r\n"
"aof_buffer_length:%zu\r\n"
- "aof_pending_bio_fsync:%llu\r\n",
+ "aof_pending_bio_fsync:%llu\r\n"
+ "aof_delayed_fsync:%lu\r\n",
(long long) server.aof_current_size,
(long long) server.aof_rewrite_base_size,
server.aof_rewrite_scheduled,
sdslen(server.aof_buf),
- bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC));
+ bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC),
+ server.aof_delayed_fsync);
}
if (server.loading) {
perc = ((double)server.loading_loaded_bytes /
server.loading_total_bytes) * 100;
- elapsed = time(NULL)-server.loading_start_time;
+ elapsed = server.unixtime-server.loading_start_time;
if (elapsed == 0) {
eta = 1; /* A fake 1 second figure if we don't have
enough info */
(server.repl_state == REDIS_REPL_CONNECTED) ?
"up" : "down",
server.master ?
- ((int)(time(NULL)-server.master->lastinteraction)) : -1,
+ ((int)(server.unixtime-server.master->lastinteraction)) : -1,
server.repl_state == REDIS_REPL_TRANSFER
);
"master_sync_left_bytes:%ld\r\n"
"master_sync_last_io_seconds_ago:%d\r\n"
,(long)server.repl_transfer_left,
- (int)(time(NULL)-server.repl_transfer_lastio)
+ (int)(server.unixtime-server.repl_transfer_lastio)
);
}
if (server.repl_state != REDIS_REPL_CONNECTED) {
info = sdscatprintf(info,
"master_link_down_since_seconds:%ld\r\n",
- (long)time(NULL)-server.repl_down_since);
+ (long)server.unixtime-server.repl_down_since);
}
}
info = sdscatprintf(info,
}
}
- /* Clusetr */
+ /* Cluster */
if (allsections || defsections || !strcasecmp(section,"cluster")) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info,
}
void version() {
- printf("Redis server version %s (%s:%d)\n", REDIS_VERSION,
- redisGitSHA1(), atoi(redisGitDirty()) > 0);
+ printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d\n",
+ REDIS_VERSION,
+ redisGitSHA1(),
+ atoi(redisGitDirty()) > 0,
+ ZMALLOC_LIB,
+ sizeof(long) == 4 ? 32 : 64);
exit(0);
}
fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf] [options]\n");
fprintf(stderr," ./redis-server - (read config from stdin)\n");
fprintf(stderr," ./redis-server -v or --version\n");
- fprintf(stderr," ./redis-server -h or --help\n\n");
+ fprintf(stderr," ./redis-server -h or --help\n");
+ fprintf(stderr," ./redis-server --test-memory <megabytes>\n\n");
fprintf(stderr,"Examples:\n");
fprintf(stderr," ./redis-server (run the server with default conf)\n");
fprintf(stderr," ./redis-server /etc/redis/6379.conf\n");
static void sigtermHandler(int sig) {
REDIS_NOTUSED(sig);
- redisLog(REDIS_WARNING,"Received SIGTERM, scheduling shutdown...");
+ redisLogFromHandler(REDIS_WARNING,"Received SIGTERM, scheduling shutdown...");
server.shutdown_asap = 1;
}
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
* Otherwise, sa_handler is used. */
sigemptyset(&act.sa_mask);
- act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND;
+ act.sa_flags = 0;
act.sa_handler = sigtermHandler;
sigaction(SIGTERM, &act, NULL);
#ifdef HAVE_BACKTRACE
sigemptyset(&act.sa_mask);
- act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO;
+ act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
act.sa_sigaction = sigsegvHandler;
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGBUS, &act, NULL);
return;
}
+void memtest(size_t megabytes, int passes);
+
int main(int argc, char **argv) {
long long start;
struct timeval tv;
strcmp(argv[1], "--version") == 0) version();
if (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) usage();
+ if (strcmp(argv[1], "--test-memory") == 0) {
+ if (argc == 3) {
+ memtest(atoi(argv[2]),50);
+ exit(0);
+ } else {
+ fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
+ fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
+ exit(1);
+ }
+ }
+
/* First argument is the config file name? */
if (argv[j][0] != '-' || argv[j][1] != '-')
configfile = argv[j++];