/*================================= Globals ================================= */
-/* Alternate stack for SIGSEGV/etc handlers */
-char altstack[SIGSTKSZ];
-
/* Global vars */
struct redisServer server; /* server global state */
struct redisCommand *commandTable;
{"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}
+ {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
+ {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
+ {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}
};
/*============================ Utility functions ============================ */
int j;
for (j = 0; j < server.dbnum; j++) {
+ /* Keys dictionary */
if (dictIsRehashing(server.db[j].dict)) {
dictRehashMilliseconds(server.db[j].dict,1);
break; /* already used our millisecond for this loop... */
}
+ /* Expires */
+ if (dictIsRehashing(server.db[j].expires)) {
+ dictRehashMilliseconds(server.db[j].expires,1);
+ break; /* already used our millisecond for this loop... */
+ }
}
}
* it will get more aggressive to avoid that too much memory is used by
* keys that can be removed from the keyspace. */
void activeExpireCycle(void) {
- int j;
+ int j, iteration = 0;
+ long long start = ustime(), timelimit;
+
+ /* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time
+ * per iteration. Since this function gets called with a frequency of
+ * REDIS_HZ times per second, the following is the max amount of
+ * microseconds we can spend in this function. */
+ timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100;
+ if (timelimit <= 0) timelimit = 1;
for (j = 0; j < server.dbnum; j++) {
int expired;
/* Continue to expire if at the end of the cycle more than 25%
* of the keys were expired. */
do {
- long num = dictSize(db->expires);
+ unsigned long num = dictSize(db->expires);
+ unsigned long slots = dictSlots(db->expires);
long long now = mstime();
+ /* When there are less than 1% filled slots getting random
+ * keys is expensive, so stop here waiting for better times...
+ * The dictionary will be resized asap. */
+ if (num && slots > DICT_HT_INITIAL_SIZE &&
+ (num*100/slots < 1)) break;
+
+ /* The main collection cycle. Sample random keys among keys
+ * with an expire set, checking for expired ones. */
expired = 0;
if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
num = REDIS_EXPIRELOOKUPS_PER_CRON;
server.stat_expiredkeys++;
}
}
+ /* We can't block forever here even if there are many keys to
+ * expire. So after a given amount of milliseconds return to the
+ * caller waiting for the other active expire cycle. */
+ iteration++;
+ if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */
+ (ustime()-start) > timelimit) return;
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
}
}
}
void clientsCron(void) {
- /* Make sure to process at least 1/100 of clients per call.
- * Since this function is called 10 times per second we are sure that
+ /* Make sure to process at least 1/(REDIS_HZ*10) of clients per call.
+ * Since this function is called REDIS_HZ times per second we are sure that
* in the worst case we process all the clients in 10 seconds.
* In normal conditions (a reasonable number of clients) we process
* all the clients in a shorter time. */
int numclients = listLength(server.clients);
- int iterations = numclients/100;
+ int iterations = numclients/(REDIS_HZ*10);
if (iterations < 50)
iterations = (numclients < 50) ? numclients : 50;
}
}
+/* This is our timer interrupt, called REDIS_HZ times per second.
+ * Here is where we do a number of things that need to be done asynchronously.
+ * For instance:
+ *
+ * - Active expired keys collection (it is also performed in a lazy way on
+ * lookup).
+ * - Software watchdong.
+ * - Update some statistic.
+ * - Incremental rehashing of the DBs hash tables.
+ * - Triggering BGSAVE / AOF rewrite, and handling of terminated children.
+ * - Clients timeout of differnet kinds.
+ * - Replication reconnection.
+ * - Many more...
+ *
+ * Everything directly called here will be called REDIS_HZ times per second,
+ * so in order to throttle execution of things we want to do less frequently
+ * a macro is used: run_with_period(milliseconds) { .... }
+ */
+
+/* Using the following macro you can run code inside serverCron() with the
+ * specified period, specified in milliseconds.
+ * The actual resolution depends on REDIS_HZ. */
+#define run_with_period(_ms_) if (!(loops % ((_ms_)/(1000/REDIS_HZ))))
+
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j, loops = server.cronloops;
REDIS_NOTUSED(eventLoop);
* To access a global var is faster than calling time(NULL) */
server.unixtime = time(NULL);
- trackOperationsPerSecond();
+ run_with_period(100) trackOperationsPerSecond();
/* We have just 22 bits per object for LRU information.
* So we use an (eventually wrapping) LRU clock with 10 seconds resolution.
}
/* Show some info about non-empty databases */
- for (j = 0; j < server.dbnum; j++) {
- long long size, used, vkeys;
-
- size = dictSlots(server.db[j].dict);
- used = dictSize(server.db[j].dict);
- vkeys = dictSize(server.db[j].expires);
- if (!(loops % 50) && (used || vkeys)) {
- redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
- /* dictPrintStats(server.dict); */
+ run_with_period(5000) {
+ for (j = 0; j < server.dbnum; j++) {
+ long long size, used, vkeys;
+
+ size = dictSlots(server.db[j].dict);
+ used = dictSize(server.db[j].dict);
+ vkeys = dictSize(server.db[j].expires);
+ if (used || vkeys) {
+ redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
+ /* dictPrintStats(server.dict); */
+ }
}
}
* a lot of memory movements in the parent will cause a lot of pages
* copied. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
- if (!(loops % 10)) tryResizeHashTables();
+ tryResizeHashTables();
if (server.activerehashing) incrementallyRehash();
}
/* Show information about connected clients */
- if (!(loops % 50)) {
+ run_with_period(5000) {
redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use",
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
/* Replication cron function -- used to reconnect to master and
* to detect transfer failures. */
- if (!(loops % 10)) replicationCron();
+ run_with_period(1000) replicationCron();
server.cronloops++;
- return 100;
+ return 1000/REDIS_HZ;
}
/* This function gets called every time Redis is entering the
"-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. 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(
server.aof_rewrite_base_size = 0;
server.aof_rewrite_scheduled = 0;
server.aof_last_fsync = time(NULL);
+ server.aof_rewrite_time_last = -1;
+ server.aof_rewrite_time_start = -1;
server.aof_delayed_fsync = 0;
server.aof_fd = -1;
server.aof_selected_db = -1; /* Make sure the first time will not match */
server.cronloops = 0;
server.rdb_child_pid = -1;
server.aof_child_pid = -1;
- server.aof_rewrite_buf = sdsempty();
+ aofRewriteBufferReset();
server.aof_buf = sdsempty();
server.lastsave = time(NULL);
+ server.rdb_save_time_last = -1;
+ server.rdb_save_time_start = -1;
server.dirty = 0;
server.stat_numcommands = 0;
server.stat_numconnections = 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 &&
info = sdscatprintf(info,
"# Persistence\r\n"
"loading:%d\r\n"
+ "rdb_changes_since_last_save:%lld\r\n"
+ "rdb_bgsave_in_progress:%d\r\n"
+ "rdb_last_save_time:%ld\r\n"
+ "rdb_last_bgsave_status:%s\r\n"
+ "rdb_last_bgsave_time_sec:%ld\r\n"
+ "rdb_current_bgsave_time_sec:%ld\r\n"
"aof_enabled:%d\r\n"
- "changes_since_last_save:%lld\r\n"
- "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_scheduled:%d\r\n",
+ "aof_rewrite_in_progress:%d\r\n"
+ "aof_rewrite_scheduled:%d\r\n"
+ "aof_last_rewrite_time_sec:%ld\r\n"
+ "aof_current_rewrite_time_sec:%ld\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.rdb_save_time_last,
+ (server.rdb_child_pid == -1) ?
+ -1 : time(NULL)-server.rdb_save_time_start,
+ server.aof_state != REDIS_AOF_OFF,
server.aof_child_pid != -1,
- server.aof_rewrite_scheduled);
+ server.aof_rewrite_scheduled,
+ server.aof_rewrite_time_last,
+ (server.aof_child_pid == -1) ?
+ -1 : time(NULL)-server.aof_rewrite_time_start);
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_rewrite_buffer_length:%zu\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),
+ aofRewriteBufferSize(),
bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC),
server.aof_delayed_fsync);
}
}
if (server.aof_state != REDIS_AOF_OFF) {
mem_used -= sdslen(server.aof_buf);
- mem_used -= sdslen(server.aof_rewrite_buf);
+ mem_used -= aofRewriteBufferSize();
}
/* Check if we are over the memory limit. */
void setupSignalHandlers(void) {
struct sigaction act;
- stack_t stack;
-
- stack.ss_sp = altstack;
- stack.ss_flags = 0;
- stack.ss_size = SIGSTKSZ;
-
- sigaltstack(&stack, NULL);
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
* Otherwise, sa_handler is used. */
sigaction(SIGTERM, &act, NULL);
#ifdef HAVE_BACKTRACE
- /* Use alternate stack so we don't clobber stack in case of segv, or when we run out of stack ..
- * also resethand & nodefer so we can get interrupted (and killed) if we cause SEGV during SEGV handler */
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);