Commit bf759cc9 authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'antirez/unstable' into jemalloc_purge_bg

parents 2e19b941 ee1cef18
...@@ -264,9 +264,9 @@ int startAppendOnly(void) { ...@@ -264,9 +264,9 @@ int startAppendOnly(void) {
strerror(errno)); strerror(errno));
return C_ERR; return C_ERR;
} }
if (server.rdb_child_pid != -1) { if (hasActiveChildProcess() && server.aof_child_pid == -1) {
server.aof_rewrite_scheduled = 1; server.aof_rewrite_scheduled = 1;
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible."); serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
} else { } else {
/* If there is a pending AOF rewrite, we need to switch it off and /* If there is a pending AOF rewrite, we need to switch it off and
* start a new one: the old one cannot be reused because it is not * start a new one: the old one cannot be reused because it is not
...@@ -303,9 +303,7 @@ ssize_t aofWrite(int fd, const char *buf, size_t len) { ...@@ -303,9 +303,7 @@ ssize_t aofWrite(int fd, const char *buf, size_t len) {
nwritten = write(fd, buf, len); nwritten = write(fd, buf, len);
if (nwritten < 0) { if (nwritten < 0) {
if (errno == EINTR) { if (errno == EINTR) continue;
continue;
}
return totwritten ? totwritten : -1; return totwritten ? totwritten : -1;
} }
...@@ -397,7 +395,7 @@ void flushAppendOnlyFile(int force) { ...@@ -397,7 +395,7 @@ void flushAppendOnlyFile(int force) {
* useful for graphing / monitoring purposes. */ * useful for graphing / monitoring purposes. */
if (sync_in_progress) { if (sync_in_progress) {
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency); latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) { } else if (hasActiveChildProcess()) {
latencyAddSampleIfNeeded("aof-write-active-child",latency); latencyAddSampleIfNeeded("aof-write-active-child",latency);
} else { } else {
latencyAddSampleIfNeeded("aof-write-alone",latency); latencyAddSampleIfNeeded("aof-write-alone",latency);
...@@ -493,9 +491,8 @@ void flushAppendOnlyFile(int force) { ...@@ -493,9 +491,8 @@ void flushAppendOnlyFile(int force) {
try_fsync: try_fsync:
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
* children doing I/O in the background. */ * children doing I/O in the background. */
if (server.aof_no_fsync_on_rewrite && if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
(server.aof_child_pid != -1 || server.rdb_child_pid != -1)) return;
return;
/* Perform the fsync if needed. */ /* Perform the fsync if needed. */
if (server.aof_fsync == AOF_FSYNC_ALWAYS) { if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
...@@ -729,7 +726,7 @@ int loadAppendOnlyFile(char *filename) { ...@@ -729,7 +726,7 @@ int loadAppendOnlyFile(char *filename) {
server.aof_state = AOF_OFF; server.aof_state = AOF_OFF;
fakeClient = createFakeClient(); fakeClient = createFakeClient();
startLoading(fp); startLoadingFile(fp, filename);
/* Check if this AOF file has an RDB preamble. In that case we need to /* Check if this AOF file has an RDB preamble. In that case we need to
* load the RDB file and later continue loading the AOF tail. */ * load the RDB file and later continue loading the AOF tail. */
...@@ -864,6 +861,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */ ...@@ -864,6 +861,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */ readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
if (!feof(fp)) { if (!feof(fp)) {
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */ if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
fclose(fp);
serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno)); serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
exit(1); exit(1);
} }
...@@ -894,11 +892,13 @@ uxeof: /* Unexpected AOF end of file. */ ...@@ -894,11 +892,13 @@ uxeof: /* Unexpected AOF end of file. */
} }
} }
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */ if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
fclose(fp);
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server."); serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
exit(1); exit(1);
fmterr: /* Format error. */ fmterr: /* Format error. */
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */ if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
fclose(fp);
serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>"); serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
exit(1); exit(1);
} }
...@@ -1562,39 +1562,24 @@ void aofClosePipes(void) { ...@@ -1562,39 +1562,24 @@ void aofClosePipes(void) {
*/ */
int rewriteAppendOnlyFileBackground(void) { int rewriteAppendOnlyFileBackground(void) {
pid_t childpid; pid_t childpid;
long long start;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR; if (hasActiveChildProcess()) return C_ERR;
if (aofCreatePipes() != C_OK) return C_ERR; if (aofCreatePipes() != C_OK) return C_ERR;
openChildInfoPipe(); openChildInfoPipe();
start = ustime(); if ((childpid = redisFork()) == 0) {
if ((childpid = fork()) == 0) {
char tmpfile[256]; char tmpfile[256];
/* Child */ /* Child */
closeListeningSockets(0);
redisSetProcTitle("redis-aof-rewrite"); redisSetProcTitle("redis-aof-rewrite");
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) { if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1); sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
if (private_dirty) {
serverLog(LL_NOTICE,
"AOF rewrite: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
sendChildInfo(CHILD_INFO_TYPE_AOF);
exitFromChild(0); exitFromChild(0);
} else { } else {
exitFromChild(1); exitFromChild(1);
} }
} else { } else {
/* Parent */ /* Parent */
server.stat_fork_time = ustime()-start;
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) { if (childpid == -1) {
closeChildInfoPipe(); closeChildInfoPipe();
serverLog(LL_WARNING, serverLog(LL_WARNING,
...@@ -1608,7 +1593,6 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -1608,7 +1593,6 @@ int rewriteAppendOnlyFileBackground(void) {
server.aof_rewrite_scheduled = 0; server.aof_rewrite_scheduled = 0;
server.aof_rewrite_time_start = time(NULL); server.aof_rewrite_time_start = time(NULL);
server.aof_child_pid = childpid; server.aof_child_pid = childpid;
updateDictResizePolicy();
/* We set appendseldb to -1 in order to force the next call to the /* We set appendseldb to -1 in order to force the next call to the
* feedAppendOnlyFile() to issue a SELECT command, so the differences * feedAppendOnlyFile() to issue a SELECT command, so the differences
* accumulated by the parent into server.aof_rewrite_buf will start * accumulated by the parent into server.aof_rewrite_buf will start
...@@ -1623,13 +1607,14 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -1623,13 +1607,14 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(client *c) { void bgrewriteaofCommand(client *c) {
if (server.aof_child_pid != -1) { if (server.aof_child_pid != -1) {
addReplyError(c,"Background append only file rewriting already in progress"); addReplyError(c,"Background append only file rewriting already in progress");
} else if (server.rdb_child_pid != -1) { } else if (hasActiveChildProcess()) {
server.aof_rewrite_scheduled = 1; server.aof_rewrite_scheduled = 1;
addReplyStatus(c,"Background append only file rewriting scheduled"); addReplyStatus(c,"Background append only file rewriting scheduled");
} else if (rewriteAppendOnlyFileBackground() == C_OK) { } else if (rewriteAppendOnlyFileBackground() == C_OK) {
addReplyStatus(c,"Background append only file rewriting started"); addReplyStatus(c,"Background append only file rewriting started");
} else { } else {
addReply(c,shared.err); addReplyError(c,"Can't execute an AOF background rewriting. "
"Please check the server logs for more information.");
} }
} }
......
...@@ -229,6 +229,207 @@ void disconnectAllBlockedClients(void) { ...@@ -229,6 +229,207 @@ void disconnectAllBlockedClients(void) {
} }
} }
/* Helper function for handleClientsBlockedOnKeys(). This function is called
* when there may be clients blocked on a list key, and there may be new
* data to fetch (the key is ready). */
void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
if (de) {
list *clients = dictGetVal(de);
int numclients = listLength(clients);
while(numclients--) {
listNode *clientnode = listFirst(clients);
client *receiver = clientnode->value;
if (receiver->btype != BLOCKED_LIST) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
continue;
}
robj *dstkey = receiver->bpop.target;
int where = (receiver->lastcmd &&
receiver->lastcmd->proc == blpopCommand) ?
LIST_HEAD : LIST_TAIL;
robj *value = listTypePop(o,where);
if (value) {
/* Protect receiver->bpop.target, that will be
* freed by the next unblockClient()
* call. */
if (dstkey) incrRefCount(dstkey);
unblockClient(receiver);
if (serveClientBlockedOnList(receiver,
rl->key,dstkey,rl->db,value,
where) == C_ERR)
{
/* If we failed serving the client we need
* to also undo the POP operation. */
listTypePush(o,value,where);
}
if (dstkey) decrRefCount(dstkey);
decrRefCount(value);
} else {
break;
}
}
}
if (listTypeLength(o) == 0) {
dbDelete(rl->db,rl->key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
}
/* We don't call signalModifiedKey() as it was already called
* when an element was pushed on the list. */
}
/* Helper function for handleClientsBlockedOnKeys(). This function is called
* when there may be clients blocked on a sorted set key, and there may be new
* data to fetch (the key is ready). */
void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
if (de) {
list *clients = dictGetVal(de);
int numclients = listLength(clients);
unsigned long zcard = zsetLength(o);
while(numclients-- && zcard) {
listNode *clientnode = listFirst(clients);
client *receiver = clientnode->value;
if (receiver->btype != BLOCKED_ZSET) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
continue;
}
int where = (receiver->lastcmd &&
receiver->lastcmd->proc == bzpopminCommand)
? ZSET_MIN : ZSET_MAX;
unblockClient(receiver);
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
zcard--;
/* Replicate the command. */
robj *argv[2];
struct redisCommand *cmd = where == ZSET_MIN ?
server.zpopminCommand :
server.zpopmaxCommand;
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
argv[1] = rl->key;
incrRefCount(rl->key);
propagate(cmd,receiver->db->id,
argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
}
}
/* Helper function for handleClientsBlockedOnKeys(). This function is called
* when there may be clients blocked on a stream key, and there may be new
* data to fetch (the key is ready). */
void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
stream *s = o->ptr;
/* We need to provide the new data arrived on the stream
* to all the clients that are waiting for an offset smaller
* than the current top item. */
if (de) {
list *clients = dictGetVal(de);
listNode *ln;
listIter li;
listRewind(clients,&li);
while((ln = listNext(&li))) {
client *receiver = listNodeValue(ln);
if (receiver->btype != BLOCKED_STREAM) continue;
streamID *gt = dictFetchValue(receiver->bpop.keys,
rl->key);
/* If we blocked in the context of a consumer
* group, we need to resolve the group and update the
* last ID the client is blocked for: this is needed
* because serving other clients in the same consumer
* group will alter the "last ID" of the consumer
* group, and clients blocked in a consumer group are
* always blocked for the ">" ID: we need to deliver
* only new messages and avoid unblocking the client
* otherwise. */
streamCG *group = NULL;
if (receiver->bpop.xread_group) {
group = streamLookupCG(s,
receiver->bpop.xread_group->ptr);
/* If the group was not found, send an error
* to the consumer. */
if (!group) {
addReplyError(receiver,
"-NOGROUP the consumer group this client "
"was blocked on no longer exists");
unblockClient(receiver);
continue;
} else {
*gt = group->last_id;
}
}
if (streamCompareID(&s->last_id, gt) > 0) {
streamID start = *gt;
start.seq++; /* Can't overflow, it's an uint64_t */
/* Lookup the consumer for the group, if any. */
streamConsumer *consumer = NULL;
int noack = 0;
if (group) {
consumer = streamLookupConsumer(group,
receiver->bpop.xread_consumer->ptr,
1);
noack = receiver->bpop.xread_group_noack;
}
/* Emit the two elements sub-array consisting of
* the name of the stream and the data we
* extracted from it. Wrapped in a single-item
* array, since we have just one key. */
if (receiver->resp == 2) {
addReplyArrayLen(receiver,1);
addReplyArrayLen(receiver,2);
} else {
addReplyMapLen(receiver,1);
}
addReplyBulk(receiver,rl->key);
streamPropInfo pi = {
rl->key,
receiver->bpop.xread_group
};
streamReplyWithRange(receiver,s,&start,NULL,
receiver->bpop.xread_count,
0, group, consumer, noack, &pi);
/* Note that after we unblock the client, 'gt'
* and other receiver->bpop stuff are no longer
* valid, so we must do the setup above before
* this call. */
unblockClient(receiver);
}
}
}
}
/* This function should be called by Redis every time a single command, /* This function should be called by Redis every time a single command,
* a MULTI/EXEC block, or a Lua script, terminated its execution after * a MULTI/EXEC block, or a Lua script, terminated its execution after
* being called by a client. It handles serving clients blocked in * being called by a client. It handles serving clients blocked in
...@@ -271,202 +472,14 @@ void handleClientsBlockedOnKeys(void) { ...@@ -271,202 +472,14 @@ void handleClientsBlockedOnKeys(void) {
/* Serve clients blocked on list key. */ /* Serve clients blocked on list key. */
robj *o = lookupKeyWrite(rl->db,rl->key); robj *o = lookupKeyWrite(rl->db,rl->key);
if (o != NULL && o->type == OBJ_LIST) {
dictEntry *de;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
de = dictFind(rl->db->blocking_keys,rl->key);
if (de) {
list *clients = dictGetVal(de);
int numclients = listLength(clients);
while(numclients--) {
listNode *clientnode = listFirst(clients);
client *receiver = clientnode->value;
if (receiver->btype != BLOCKED_LIST) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
continue;
}
robj *dstkey = receiver->bpop.target;
int where = (receiver->lastcmd &&
receiver->lastcmd->proc == blpopCommand) ?
LIST_HEAD : LIST_TAIL;
robj *value = listTypePop(o,where);
if (value) {
/* Protect receiver->bpop.target, that will be
* freed by the next unblockClient()
* call. */
if (dstkey) incrRefCount(dstkey);
unblockClient(receiver);
if (serveClientBlockedOnList(receiver,
rl->key,dstkey,rl->db,value,
where) == C_ERR)
{
/* If we failed serving the client we need
* to also undo the POP operation. */
listTypePush(o,value,where);
}
if (dstkey) decrRefCount(dstkey);
decrRefCount(value);
} else {
break;
}
}
}
if (listTypeLength(o) == 0) {
dbDelete(rl->db,rl->key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
}
/* We don't call signalModifiedKey() as it was already called
* when an element was pushed on the list. */
}
/* Serve clients blocked on sorted set key. */ if (o != NULL) {
else if (o != NULL && o->type == OBJ_ZSET) { if (o->type == OBJ_LIST)
dictEntry *de; serveClientsBlockedOnListKey(o,rl);
else if (o->type == OBJ_ZSET)
/* We serve clients in the same order they blocked for serveClientsBlockedOnSortedSetKey(o,rl);
* this key, from the first blocked to the last. */ else if (o->type == OBJ_STREAM)
de = dictFind(rl->db->blocking_keys,rl->key); serveClientsBlockedOnStreamKey(o,rl);
if (de) {
list *clients = dictGetVal(de);
int numclients = listLength(clients);
unsigned long zcard = zsetLength(o);
while(numclients-- && zcard) {
listNode *clientnode = listFirst(clients);
client *receiver = clientnode->value;
if (receiver->btype != BLOCKED_ZSET) {
/* Put at the tail, so that at the next call
* we'll not run into it again. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
continue;
}
int where = (receiver->lastcmd &&
receiver->lastcmd->proc == bzpopminCommand)
? ZSET_MIN : ZSET_MAX;
unblockClient(receiver);
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
zcard--;
/* Replicate the command. */
robj *argv[2];
struct redisCommand *cmd = where == ZSET_MIN ?
server.zpopminCommand :
server.zpopmaxCommand;
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
argv[1] = rl->key;
incrRefCount(rl->key);
propagate(cmd,receiver->db->id,
argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
}
}
/* Serve clients blocked on stream key. */
else if (o != NULL && o->type == OBJ_STREAM) {
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
stream *s = o->ptr;
/* We need to provide the new data arrived on the stream
* to all the clients that are waiting for an offset smaller
* than the current top item. */
if (de) {
list *clients = dictGetVal(de);
listNode *ln;
listIter li;
listRewind(clients,&li);
while((ln = listNext(&li))) {
client *receiver = listNodeValue(ln);
if (receiver->btype != BLOCKED_STREAM) continue;
streamID *gt = dictFetchValue(receiver->bpop.keys,
rl->key);
/* If we blocked in the context of a consumer
* group, we need to resolve the group and update the
* last ID the client is blocked for: this is needed
* because serving other clients in the same consumer
* group will alter the "last ID" of the consumer
* group, and clients blocked in a consumer group are
* always blocked for the ">" ID: we need to deliver
* only new messages and avoid unblocking the client
* otherwise. */
streamCG *group = NULL;
if (receiver->bpop.xread_group) {
group = streamLookupCG(s,
receiver->bpop.xread_group->ptr);
/* If the group was not found, send an error
* to the consumer. */
if (!group) {
addReplyError(receiver,
"-NOGROUP the consumer group this client "
"was blocked on no longer exists");
unblockClient(receiver);
continue;
} else {
*gt = group->last_id;
}
}
if (streamCompareID(&s->last_id, gt) > 0) {
streamID start = *gt;
start.seq++; /* Can't overflow, it's an uint64_t */
/* Lookup the consumer for the group, if any. */
streamConsumer *consumer = NULL;
int noack = 0;
if (group) {
consumer = streamLookupConsumer(group,
receiver->bpop.xread_consumer->ptr,
1);
noack = receiver->bpop.xread_group_noack;
}
/* Emit the two elements sub-array consisting of
* the name of the stream and the data we
* extracted from it. Wrapped in a single-item
* array, since we have just one key. */
if (receiver->resp == 2) {
addReplyArrayLen(receiver,1);
addReplyArrayLen(receiver,2);
} else {
addReplyMapLen(receiver,1);
}
addReplyBulk(receiver,rl->key);
streamPropInfo pi = {
rl->key,
receiver->bpop.xread_group
};
streamReplyWithRange(receiver,s,&start,NULL,
receiver->bpop.xread_count,
0, group, consumer, noack, &pi);
/* Note that after we unblock the client, 'gt'
* and other receiver->bpop stuff are no longer
* valid, so we must do the setup above before
* this call. */
unblockClient(receiver);
}
}
}
} }
/* Free this item. */ /* Free this item. */
...@@ -592,7 +605,7 @@ void unblockClientWaitingData(client *c) { ...@@ -592,7 +605,7 @@ void unblockClientWaitingData(client *c) {
* the same key again and again in the list in case of multiple pushes * the same key again and again in the list in case of multiple pushes
* made by a script or in the context of MULTI/EXEC. * made by a script or in the context of MULTI/EXEC.
* *
* The list will be finally processed by handleClientsBlockedOnLists() */ * The list will be finally processed by handleClientsBlockedOnKeys() */
void signalKeyAsReady(redisDb *db, robj *key) { void signalKeyAsReady(redisDb *db, robj *key) {
readyList *rl; readyList *rl;
......
...@@ -80,6 +80,8 @@ void receiveChildInfo(void) { ...@@ -80,6 +80,8 @@ void receiveChildInfo(void) {
server.stat_rdb_cow_bytes = server.child_info_data.cow_size; server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) { } else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
server.stat_aof_cow_bytes = server.child_info_data.cow_size; server.stat_aof_cow_bytes = server.child_info_data.cow_size;
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
server.stat_module_cow_bytes = server.child_info_data.cow_size;
} }
} }
} }
...@@ -138,6 +138,7 @@ int clusterLoadConfig(char *filename) { ...@@ -138,6 +138,7 @@ int clusterLoadConfig(char *filename) {
/* Handle the special "vars" line. Don't pretend it is the last /* Handle the special "vars" line. Don't pretend it is the last
* line even if it actually is when generated by Redis. */ * line even if it actually is when generated by Redis. */
if (strcasecmp(argv[0],"vars") == 0) { if (strcasecmp(argv[0],"vars") == 0) {
if (!(argc % 2)) goto fmterr;
for (j = 1; j < argc; j += 2) { for (j = 1; j < argc; j += 2) {
if (strcasecmp(argv[j],"currentEpoch") == 0) { if (strcasecmp(argv[j],"currentEpoch") == 0) {
server.cluster->currentEpoch = server.cluster->currentEpoch =
...@@ -4251,12 +4252,9 @@ NULL ...@@ -4251,12 +4252,9 @@ NULL
} }
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
/* CLUSTER NODES */ /* CLUSTER NODES */
robj *o; sds nodes = clusterGenNodesDescription(0);
sds ci = clusterGenNodesDescription(0); addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
sdsfree(nodes);
o = createObject(OBJ_STRING,ci);
addReplyBulk(c,o);
decrRefCount(o);
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
/* CLUSTER MYID */ /* CLUSTER MYID */
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN); addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
...@@ -4498,10 +4496,8 @@ NULL ...@@ -4498,10 +4496,8 @@ NULL
"cluster_stats_messages_received:%lld\r\n", tot_msg_received); "cluster_stats_messages_received:%lld\r\n", tot_msg_received);
/* Produce the reply protocol. */ /* Produce the reply protocol. */
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n", addReplyVerbatim(c,info,sdslen(info),"txt");
(unsigned long)sdslen(info))); sdsfree(info);
addReplySds(c,info);
addReply(c,shared.crlf);
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
int retval = clusterSaveConfig(1); int retval = clusterSaveConfig(1);
...@@ -4832,7 +4828,7 @@ int verifyDumpPayload(unsigned char *p, size_t len) { ...@@ -4832,7 +4828,7 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
* DUMP is actually not used by Redis Cluster but it is the obvious * DUMP is actually not used by Redis Cluster but it is the obvious
* complement of RESTORE and can be useful for different applications. */ * complement of RESTORE and can be useful for different applications. */
void dumpCommand(client *c) { void dumpCommand(client *c) {
robj *o, *dumpobj; robj *o;
rio payload; rio payload;
/* Check if the key is here. */ /* Check if the key is here. */
...@@ -4845,9 +4841,7 @@ void dumpCommand(client *c) { ...@@ -4845,9 +4841,7 @@ void dumpCommand(client *c) {
createDumpPayload(&payload,o,c->argv[1]); createDumpPayload(&payload,o,c->argv[1]);
/* Transfer to the client */ /* Transfer to the client */
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr); addReplyBulkSds(c,payload.io.buffer.ptr);
addReplyBulk(c,dumpobj);
decrRefCount(dumpobj);
return; return;
} }
......
...@@ -91,6 +91,13 @@ configEnum aof_fsync_enum[] = { ...@@ -91,6 +91,13 @@ configEnum aof_fsync_enum[] = {
{NULL, 0} {NULL, 0}
}; };
configEnum repl_diskless_load_enum[] = {
{"disabled", REPL_DISKLESS_LOAD_DISABLED},
{"on-empty-db", REPL_DISKLESS_LOAD_WHEN_DB_EMPTY},
{"swapdb", REPL_DISKLESS_LOAD_SWAPDB},
{NULL, 0}
};
/* Output buffer limits presets. */ /* Output buffer limits presets. */
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{0, 0, 0}, /* normal */ {0, 0, 0}, /* normal */
...@@ -98,6 +105,49 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { ...@@ -98,6 +105,49 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{1024*1024*32, 1024*1024*8, 60} /* pubsub */ {1024*1024*32, 1024*1024*8, 60} /* pubsub */
}; };
/* Configuration values that require no special handling to set, get, load or
* rewrite. */
typedef struct configYesNo {
const char *name; /* The user visible name of this config */
const char *alias; /* An alias that can also be used for this config */
int *config; /* The pointer to the server config this value is stored in */
const int modifiable; /* Can this value be updated by CONFIG SET? */
const int default_value; /* The default value of the config on rewrite */
} configYesNo;
configYesNo configs_yesno[] = {
/* Non-Modifiable */
{"rdbchecksum",NULL,&server.rdb_checksum,0,CONFIG_DEFAULT_RDB_CHECKSUM},
{"daemonize",NULL,&server.daemonize,0,0},
{"io-threads-do-reads",NULL,&server.io_threads_do_reads, 0, CONFIG_DEFAULT_IO_THREADS_DO_READS},
{"always-show-logo",NULL,&server.always_show_logo,0,CONFIG_DEFAULT_ALWAYS_SHOW_LOGO},
/* Modifiable */
{"protected-mode",NULL,&server.protected_mode,1,CONFIG_DEFAULT_PROTECTED_MODE},
{"rdbcompression",NULL,&server.rdb_compression,1,CONFIG_DEFAULT_RDB_COMPRESSION},
{"activerehashing",NULL,&server.activerehashing,1,CONFIG_DEFAULT_ACTIVE_REHASHING},
{"stop-writes-on-bgsave-error",NULL,&server.stop_writes_on_bgsave_err,1,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR},
{"dynamic-hz",NULL,&server.dynamic_hz,1,CONFIG_DEFAULT_DYNAMIC_HZ},
{"lazyfree-lazy-eviction",NULL,&server.lazyfree_lazy_eviction,1,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION},
{"lazyfree-lazy-expire",NULL,&server.lazyfree_lazy_expire,1,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE},
{"lazyfree-lazy-server-del",NULL,&server.lazyfree_lazy_server_del,1,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL},
{"repl-disable-tcp-nodelay",NULL,&server.repl_disable_tcp_nodelay,1,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY},
{"repl-diskless-sync",NULL,&server.repl_diskless_sync,1,CONFIG_DEFAULT_REPL_DISKLESS_SYNC},
{"gopher-enabled",NULL,&server.gopher_enabled,1,CONFIG_DEFAULT_GOPHER_ENABLED},
{"aof-rewrite-incremental-fsync",NULL,&server.aof_rewrite_incremental_fsync,1,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC},
{"no-appendfsync-on-rewrite",NULL,&server.aof_no_fsync_on_rewrite,1,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE},
{"cluster-require-full-coverage",NULL,&server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE},
{"rdb-save-incremental-fsync",NULL,&server.rdb_save_incremental_fsync,1,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC},
{"aof-load-truncated",NULL,&server.aof_load_truncated,1,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED},
{"aof-use-rdb-preamble",NULL,&server.aof_use_rdb_preamble,1,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE},
{"cluster-replica-no-failover","cluster-slave-no-failover",&server.cluster_slave_no_failover,1,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER},
{"replica-lazy-flush","slave-lazy-flush",&server.repl_slave_lazy_flush,1,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH},
{"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
{"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
{"jemalloc-bg-thread",NULL,&server.jemalloc_bg_thread,1,1},
{NULL, NULL, 0, 0}
};
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Enum access functions * Enum access functions
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
...@@ -201,6 +251,26 @@ void loadServerConfigFromString(char *config) { ...@@ -201,6 +251,26 @@ void loadServerConfigFromString(char *config) {
} }
sdstolower(argv[0]); sdstolower(argv[0]);
/* Iterate the configs that are standard */
int match = 0;
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
if ((!strcasecmp(argv[0],config->name) ||
(config->alias && !strcasecmp(argv[0],config->alias))) &&
(argc == 2))
{
if ((*(config->config) = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
match = 1;
break;
}
}
if (match) {
sdsfreesplitres(argv,argc);
continue;
}
/* Execute config directives */ /* Execute config directives */
if (!strcasecmp(argv[0],"timeout") && argc == 2) { if (!strcasecmp(argv[0],"timeout") && argc == 2) {
server.maxidletime = atoi(argv[1]); server.maxidletime = atoi(argv[1]);
...@@ -212,14 +282,6 @@ void loadServerConfigFromString(char *config) { ...@@ -212,14 +282,6 @@ void loadServerConfigFromString(char *config) {
if (server.tcpkeepalive < 0) { if (server.tcpkeepalive < 0) {
err = "Invalid tcp-keepalive value"; goto loaderr; err = "Invalid tcp-keepalive value"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"gopher-enabled") && argc == 2) {
if ((server.gopher_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"port") && argc == 2) { } else if (!strcasecmp(argv[0],"port") && argc == 2) {
server.port = atoi(argv[1]); server.port = atoi(argv[1]);
if (server.port < 0 || server.port > 65535) { if (server.port < 0 || server.port > 65535) {
...@@ -290,10 +352,6 @@ void loadServerConfigFromString(char *config) { ...@@ -290,10 +352,6 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"aclfile") && argc == 2) { } else if (!strcasecmp(argv[0],"aclfile") && argc == 2) {
zfree(server.acl_filename); zfree(server.acl_filename);
server.acl_filename = zstrdup(argv[1]); server.acl_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
if ((server.always_show_logo = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) { } else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) { if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
...@@ -318,10 +376,6 @@ void loadServerConfigFromString(char *config) { ...@@ -318,10 +376,6 @@ void loadServerConfigFromString(char *config) {
if (server.io_threads_num < 1 || server.io_threads_num > 512) { if (server.io_threads_num < 1 || server.io_threads_num > 512) {
err = "Invalid number of I/O threads"; goto loaderr; err = "Invalid number of I/O threads"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"io-threads-do-reads") && argc == 2) {
if ((server.io_threads_do_reads = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"include") && argc == 2) { } else if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1],NULL); loadServerConfig(argv[1],NULL);
} else if (!strcasecmp(argv[0],"maxclients") && argc == 2) { } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
...@@ -381,13 +435,11 @@ void loadServerConfigFromString(char *config) { ...@@ -381,13 +435,11 @@ void loadServerConfigFromString(char *config) {
err = "repl-timeout must be 1 or greater"; err = "repl-timeout must be 1 or greater";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"repl-disable-tcp-nodelay") && argc==2) { } else if (!strcasecmp(argv[0],"repl-diskless-load") && argc==2) {
if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) { server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
err = "argument must be 'yes' or 'no'"; goto loaderr; if (server.repl_diskless_load == INT_MIN) {
} err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
} else if (!strcasecmp(argv[0],"repl-diskless-sync") && argc==2) { goto loaderr;
if ((server.repl_diskless_sync = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) { } else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
server.repl_diskless_sync_delay = atoi(argv[1]); server.repl_diskless_sync_delay = atoi(argv[1]);
...@@ -414,57 +466,6 @@ void loadServerConfigFromString(char *config) { ...@@ -414,57 +466,6 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) { } else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
zfree(server.masterauth); zfree(server.masterauth);
server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL; server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL;
} else if ((!strcasecmp(argv[0],"slave-serve-stale-data") ||
!strcasecmp(argv[0],"replica-serve-stale-data"))
&& argc == 2)
{
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if ((!strcasecmp(argv[0],"slave-read-only") ||
!strcasecmp(argv[0],"replica-read-only"))
&& argc == 2)
{
if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if ((!strcasecmp(argv[0],"slave-ignore-maxmemory") ||
!strcasecmp(argv[0],"replica-ignore-maxmemory"))
&& argc == 2)
{
if ((server.repl_slave_ignore_maxmemory = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"lazyfree-lazy-eviction") && argc == 2) {
if ((server.lazyfree_lazy_eviction = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"lazyfree-lazy-expire") && argc == 2) {
if ((server.lazyfree_lazy_expire = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"lazyfree-lazy-server-del") && argc == 2){
if ((server.lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if ((!strcasecmp(argv[0],"slave-lazy-flush") ||
!strcasecmp(argv[0],"replica-lazy-flush")) && argc == 2)
{
if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"activedefrag") && argc == 2) { } else if (!strcasecmp(argv[0],"activedefrag") && argc == 2) {
if ((server.active_defrag_enabled = yesnotoi(argv[1])) == -1) { if ((server.active_defrag_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
...@@ -474,29 +475,15 @@ void loadServerConfigFromString(char *config) { ...@@ -474,29 +475,15 @@ void loadServerConfigFromString(char *config) {
err = "active defrag can't be enabled without proper jemalloc support"; goto loaderr; err = "active defrag can't be enabled without proper jemalloc support"; goto loaderr;
#endif #endif
} }
} else if (!strcasecmp(argv[0],"jemalloc-bg-thread") && argc == 2) {
if ((server.jemalloc_bg_thread = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"dynamic-hz") && argc == 2) {
if ((server.dynamic_hz = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"hz") && argc == 2) { } else if (!strcasecmp(argv[0],"hz") && argc == 2) {
server.config_hz = atoi(argv[1]); server.config_hz = atoi(argv[1]);
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ; if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ; if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) { } else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
int yes; if ((server.aof_enabled = yesnotoi(argv[1])) == -1) {
if ((yes = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
server.aof_state = yes ? AOF_ON : AOF_OFF; server.aof_state = server.aof_enabled ? AOF_ON : AOF_OFF;
} else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) { } else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
if (!pathIsBaseName(argv[1])) { if (!pathIsBaseName(argv[1])) {
err = "appendfilename can't be a path, just a filename"; err = "appendfilename can't be a path, just a filename";
...@@ -504,11 +491,6 @@ void loadServerConfigFromString(char *config) { ...@@ -504,11 +491,6 @@ void loadServerConfigFromString(char *config) {
} }
zfree(server.aof_filename); zfree(server.aof_filename);
server.aof_filename = zstrdup(argv[1]); server.aof_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
&& argc == 2) {
if ((server.aof_no_fsync_on_rewrite= yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) { } else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
server.aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]); server.aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]);
if (server.aof_fsync == INT_MIN) { if (server.aof_fsync == INT_MIN) {
...@@ -527,27 +509,11 @@ void loadServerConfigFromString(char *config) { ...@@ -527,27 +509,11 @@ void loadServerConfigFromString(char *config) {
argc == 2) argc == 2)
{ {
server.aof_rewrite_min_size = memtoll(argv[1],NULL); server.aof_rewrite_min_size = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") && } else if (!strcasecmp(argv[0],"rdb-key-save-delay") && argc==2) {
argc == 2) server.rdb_key_save_delay = atoi(argv[1]);
{ if (server.rdb_key_save_delay < 0) {
if ((server.aof_rewrite_incremental_fsync = err = "rdb-key-save-delay can't be negative";
yesnotoi(argv[1])) == -1) { goto loaderr;
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"rdb-save-incremental-fsync") &&
argc == 2)
{
if ((server.rdb_save_incremental_fsync =
yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"aof-use-rdb-preamble") && argc == 2) {
if ((server.aof_use_rdb_preamble = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) { } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) { if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
...@@ -682,13 +648,6 @@ void loadServerConfigFromString(char *config) { ...@@ -682,13 +648,6 @@ void loadServerConfigFromString(char *config) {
{ {
err = "Invalid port"; goto loaderr; err = "Invalid port"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"cluster-require-full-coverage") &&
argc == 2)
{
if ((server.cluster_require_full_coverage = yesnotoi(argv[1])) == -1)
{
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) { } else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
server.cluster_node_timeout = strtoll(argv[1],NULL,10); server.cluster_node_timeout = strtoll(argv[1],NULL,10);
if (server.cluster_node_timeout <= 0) { if (server.cluster_node_timeout <= 0) {
...@@ -711,19 +670,14 @@ void loadServerConfigFromString(char *config) { ...@@ -711,19 +670,14 @@ void loadServerConfigFromString(char *config) {
err = "cluster replica validity factor must be zero or positive"; err = "cluster replica validity factor must be zero or positive";
goto loaderr; goto loaderr;
} }
} else if ((!strcasecmp(argv[0],"cluster-slave-no-failover") ||
!strcasecmp(argv[0],"cluster-replica-no-failover")) &&
argc == 2)
{
server.cluster_slave_no_failover = yesnotoi(argv[1]);
if (server.cluster_slave_no_failover == -1) {
err = "argument must be 'yes' or 'no'";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) { } else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
server.lua_time_limit = strtoll(argv[1],NULL,10); server.lua_time_limit = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) { } else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
server.lua_always_replicate_commands = yesnotoi(argv[1]); server.lua_always_replicate_commands = yesnotoi(argv[1]);
if (server.lua_always_replicate_commands == -1) {
err = "argument must be 'yes' or 'no'";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") && } else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
argc == 2) argc == 2)
{ {
...@@ -738,6 +692,17 @@ void loadServerConfigFromString(char *config) { ...@@ -738,6 +692,17 @@ void loadServerConfigFromString(char *config) {
} }
} else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) { } else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
server.slowlog_max_len = strtoll(argv[1],NULL,10); server.slowlog_max_len = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"tracking-table-max-fill") &&
argc == 2)
{
server.tracking_table_max_fill = strtoll(argv[1],NULL,10);
if (server.tracking_table_max_fill > 100 ||
server.tracking_table_max_fill < 0)
{
err = "The tracking table fill percentage must be an "
"integer between 0 and 100";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"client-output-buffer-limit") && } else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
argc == 5) argc == 5)
{ {
...@@ -760,11 +725,6 @@ void loadServerConfigFromString(char *config) { ...@@ -760,11 +725,6 @@ void loadServerConfigFromString(char *config) {
server.client_obuf_limits[class].hard_limit_bytes = hard; server.client_obuf_limits[class].hard_limit_bytes = hard;
server.client_obuf_limits[class].soft_limit_bytes = soft; server.client_obuf_limits[class].soft_limit_bytes = soft;
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds; server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
} else if (!strcasecmp(argv[0],"stop-writes-on-bgsave-error") &&
argc == 2) {
if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if ((!strcasecmp(argv[0],"slave-priority") || } else if ((!strcasecmp(argv[0],"slave-priority") ||
!strcasecmp(argv[0],"replica-priority")) && argc == 2) !strcasecmp(argv[0],"replica-priority")) && argc == 2)
{ {
...@@ -945,6 +905,19 @@ void configSetCommand(client *c) { ...@@ -945,6 +905,19 @@ void configSetCommand(client *c) {
serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3])); serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3]; o = c->argv[3];
/* Iterate the configs that are standard */
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
if(config->modifiable && (!strcasecmp(c->argv[2]->ptr,config->name) ||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
{
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
*(config->config) = yn;
addReply(c,shared.ok);
return;
}
}
if (0) { /* this starts the config_set macros else-if chain. */ if (0) { /* this starts the config_set macros else-if chain. */
/* Special fields that can't be handled with general macros. */ /* Special fields that can't be handled with general macros. */
...@@ -1002,6 +975,7 @@ void configSetCommand(client *c) { ...@@ -1002,6 +975,7 @@ void configSetCommand(client *c) {
int enable = yesnotoi(o->ptr); int enable = yesnotoi(o->ptr);
if (enable == -1) goto badfmt; if (enable == -1) goto badfmt;
server.aof_enabled = enable;
if (enable == 0 && server.aof_state != AOF_OFF) { if (enable == 0 && server.aof_state != AOF_OFF) {
stopAppendOnly(); stopAppendOnly();
} else if (enable && server.aof_state == AOF_OFF) { } else if (enable && server.aof_state == AOF_OFF) {
...@@ -1109,40 +1083,6 @@ void configSetCommand(client *c) { ...@@ -1109,40 +1083,6 @@ void configSetCommand(client *c) {
/* Boolean fields. /* Boolean fields.
* config_set_bool_field(name,var). */ * config_set_bool_field(name,var). */
} config_set_bool_field(
"rdbcompression", server.rdb_compression) {
} config_set_bool_field(
"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay) {
} config_set_bool_field(
"repl-diskless-sync",server.repl_diskless_sync) {
} config_set_bool_field(
"cluster-require-full-coverage",server.cluster_require_full_coverage) {
} config_set_bool_field(
"cluster-slave-no-failover",server.cluster_slave_no_failover) {
} config_set_bool_field(
"cluster-replica-no-failover",server.cluster_slave_no_failover) {
} config_set_bool_field(
"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
} config_set_bool_field(
"rdb-save-incremental-fsync",server.rdb_save_incremental_fsync) {
} config_set_bool_field(
"aof-load-truncated",server.aof_load_truncated) {
} config_set_bool_field(
"aof-use-rdb-preamble",server.aof_use_rdb_preamble) {
} config_set_bool_field(
"slave-serve-stale-data",server.repl_serve_stale_data) {
} config_set_bool_field(
"replica-serve-stale-data",server.repl_serve_stale_data) {
} config_set_bool_field(
"slave-read-only",server.repl_slave_ro) {
} config_set_bool_field(
"replica-read-only",server.repl_slave_ro) {
} config_set_bool_field(
"slave-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
} config_set_bool_field(
"replica-ignore-maxmemory",server.repl_slave_ignore_maxmemory) {
} config_set_bool_field(
"activerehashing",server.activerehashing) {
} config_set_bool_field( } config_set_bool_field(
"activedefrag",server.active_defrag_enabled) { "activedefrag",server.active_defrag_enabled) {
#ifndef HAVE_DEFRAG #ifndef HAVE_DEFRAG
...@@ -1156,30 +1096,6 @@ void configSetCommand(client *c) { ...@@ -1156,30 +1096,6 @@ void configSetCommand(client *c) {
return; return;
} }
#endif #endif
} config_set_bool_field(
"jemalloc-bg-thread",server.jemalloc_bg_thread) {
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
} config_set_bool_field(
"protected-mode",server.protected_mode) {
} config_set_bool_field(
"gopher-enabled",server.gopher_enabled) {
} config_set_bool_field(
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
} config_set_bool_field(
"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction) {
} config_set_bool_field(
"lazyfree-lazy-expire",server.lazyfree_lazy_expire) {
} config_set_bool_field(
"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del) {
} config_set_bool_field(
"slave-lazy-flush",server.repl_slave_lazy_flush) {
} config_set_bool_field(
"replica-lazy-flush",server.repl_slave_lazy_flush) {
} config_set_bool_field(
"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
} config_set_bool_field(
"dynamic-hz",server.dynamic_hz) {
/* Numerical fields. /* Numerical fields.
* config_set_numerical_field(name,var,min,max) */ * config_set_numerical_field(name,var,min,max) */
} config_set_numerical_field( } config_set_numerical_field(
...@@ -1234,6 +1150,8 @@ void configSetCommand(client *c) { ...@@ -1234,6 +1150,8 @@ void configSetCommand(client *c) {
"slowlog-max-len",ll,0,LONG_MAX) { "slowlog-max-len",ll,0,LONG_MAX) {
/* Cast to unsigned. */ /* Cast to unsigned. */
server.slowlog_max_len = (unsigned long)ll; server.slowlog_max_len = (unsigned long)ll;
} config_set_numerical_field(
"tracking-table-max-fill",server.tracking_table_max_fill,0,100) {
} config_set_numerical_field( } config_set_numerical_field(
"latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){ "latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
} config_set_numerical_field( } config_set_numerical_field(
...@@ -1250,6 +1168,8 @@ void configSetCommand(client *c) { ...@@ -1250,6 +1168,8 @@ void configSetCommand(client *c) {
"slave-priority",server.slave_priority,0,INT_MAX) { "slave-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"replica-priority",server.slave_priority,0,INT_MAX) { "replica-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field(
"rdb-key-save-delay",server.rdb_key_save_delay,0,LLONG_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"slave-announce-port",server.slave_announce_port,0,65535) { "slave-announce-port",server.slave_announce_port,0,65535) {
} config_set_numerical_field( } config_set_numerical_field(
...@@ -1317,6 +1237,8 @@ void configSetCommand(client *c) { ...@@ -1317,6 +1237,8 @@ void configSetCommand(client *c) {
"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum) { "maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum) {
} config_set_enum_field( } config_set_enum_field(
"appendfsync",server.aof_fsync,aof_fsync_enum) { "appendfsync",server.aof_fsync,aof_fsync_enum) {
} config_set_enum_field(
"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) {
/* Everyhing else is an error... */ /* Everyhing else is an error... */
} config_set_else { } config_set_else {
...@@ -1435,8 +1357,8 @@ void configGetCommand(client *c) { ...@@ -1435,8 +1357,8 @@ void configGetCommand(client *c) {
server.slowlog_log_slower_than); server.slowlog_log_slower_than);
config_get_numerical_field("latency-monitor-threshold", config_get_numerical_field("latency-monitor-threshold",
server.latency_monitor_threshold); server.latency_monitor_threshold);
config_get_numerical_field("slowlog-max-len", config_get_numerical_field("slowlog-max-len", server.slowlog_max_len);
server.slowlog_max_len); config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
config_get_numerical_field("port",server.port); config_get_numerical_field("port",server.port);
config_get_numerical_field("cluster-announce-port",server.cluster_announce_port); config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port); config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
...@@ -1464,64 +1386,19 @@ void configGetCommand(client *c) { ...@@ -1464,64 +1386,19 @@ void configGetCommand(client *c) {
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor); config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor); config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay); config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
config_get_numerical_field("rdb-key-save-delay",server.rdb_key_save_delay);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive); config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
/* Bool (yes/no) values */ /* Bool (yes/no) values */
config_get_bool_field("cluster-require-full-coverage", /* Iterate the configs that are standard */
server.cluster_require_full_coverage); for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
config_get_bool_field("cluster-slave-no-failover", config_get_bool_field(config->name, *(config->config));
server.cluster_slave_no_failover); if (config->alias) {
config_get_bool_field("cluster-replica-no-failover", config_get_bool_field(config->alias, *(config->config));
server.cluster_slave_no_failover); }
config_get_bool_field("no-appendfsync-on-rewrite", }
server.aof_no_fsync_on_rewrite);
config_get_bool_field("slave-serve-stale-data",
server.repl_serve_stale_data);
config_get_bool_field("replica-serve-stale-data",
server.repl_serve_stale_data);
config_get_bool_field("slave-read-only",
server.repl_slave_ro);
config_get_bool_field("replica-read-only",
server.repl_slave_ro);
config_get_bool_field("slave-ignore-maxmemory",
server.repl_slave_ignore_maxmemory);
config_get_bool_field("replica-ignore-maxmemory",
server.repl_slave_ignore_maxmemory);
config_get_bool_field("stop-writes-on-bgsave-error",
server.stop_writes_on_bgsave_err);
config_get_bool_field("daemonize", server.daemonize);
config_get_bool_field("rdbcompression", server.rdb_compression);
config_get_bool_field("rdbchecksum", server.rdb_checksum);
config_get_bool_field("activerehashing", server.activerehashing);
config_get_bool_field("activedefrag", server.active_defrag_enabled); config_get_bool_field("activedefrag", server.active_defrag_enabled);
config_get_bool_field("jemalloc-bg-thread", server.jemalloc_bg_thread);
config_get_bool_field("protected-mode", server.protected_mode);
config_get_bool_field("gopher-enabled", server.gopher_enabled);
config_get_bool_field("io-threads-do-reads", server.io_threads_do_reads);
config_get_bool_field("repl-disable-tcp-nodelay",
server.repl_disable_tcp_nodelay);
config_get_bool_field("repl-diskless-sync",
server.repl_diskless_sync);
config_get_bool_field("aof-rewrite-incremental-fsync",
server.aof_rewrite_incremental_fsync);
config_get_bool_field("rdb-save-incremental-fsync",
server.rdb_save_incremental_fsync);
config_get_bool_field("aof-load-truncated",
server.aof_load_truncated);
config_get_bool_field("aof-use-rdb-preamble",
server.aof_use_rdb_preamble);
config_get_bool_field("lazyfree-lazy-eviction",
server.lazyfree_lazy_eviction);
config_get_bool_field("lazyfree-lazy-expire",
server.lazyfree_lazy_expire);
config_get_bool_field("lazyfree-lazy-server-del",
server.lazyfree_lazy_server_del);
config_get_bool_field("slave-lazy-flush",
server.repl_slave_lazy_flush);
config_get_bool_field("replica-lazy-flush",
server.repl_slave_lazy_flush);
config_get_bool_field("dynamic-hz",
server.dynamic_hz);
/* Enum values */ /* Enum values */
config_get_enum_field("maxmemory-policy", config_get_enum_field("maxmemory-policy",
...@@ -1534,12 +1411,14 @@ void configGetCommand(client *c) { ...@@ -1534,12 +1411,14 @@ void configGetCommand(client *c) {
server.aof_fsync,aof_fsync_enum); server.aof_fsync,aof_fsync_enum);
config_get_enum_field("syslog-facility", config_get_enum_field("syslog-facility",
server.syslog_facility,syslog_facility_enum); server.syslog_facility,syslog_facility_enum);
config_get_enum_field("repl-diskless-load",
server.repl_diskless_load,repl_diskless_load_enum);
/* Everything we can't handle with macros follows. */ /* Everything we can't handle with macros follows. */
if (stringmatch(pattern,"appendonly",1)) { if (stringmatch(pattern,"appendonly",1)) {
addReplyBulkCString(c,"appendonly"); addReplyBulkCString(c,"appendonly");
addReplyBulkCString(c,server.aof_state == AOF_OFF ? "no" : "yes"); addReplyBulkCString(c,server.aof_enabled ? "yes" : "no");
matches++; matches++;
} }
if (stringmatch(pattern,"dir",1)) { if (stringmatch(pattern,"dir",1)) {
...@@ -1610,12 +1489,10 @@ void configGetCommand(client *c) { ...@@ -1610,12 +1489,10 @@ void configGetCommand(client *c) {
matches++; matches++;
} }
if (stringmatch(pattern,"notify-keyspace-events",1)) { if (stringmatch(pattern,"notify-keyspace-events",1)) {
robj *flagsobj = createObject(OBJ_STRING, sds flags = keyspaceEventsFlagsToString(server.notify_keyspace_events);
keyspaceEventsFlagsToString(server.notify_keyspace_events));
addReplyBulkCString(c,"notify-keyspace-events"); addReplyBulkCString(c,"notify-keyspace-events");
addReplyBulk(c,flagsobj); addReplyBulkSds(c,flags);
decrRefCount(flagsobj);
matches++; matches++;
} }
if (stringmatch(pattern,"bind",1)) { if (stringmatch(pattern,"bind",1)) {
...@@ -1866,7 +1743,7 @@ void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, lo ...@@ -1866,7 +1743,7 @@ void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, lo
} }
/* Rewrite a yes/no option. */ /* Rewrite a yes/no option. */
void rewriteConfigYesNoOption(struct rewriteConfigState *state, char *option, int value, int defvalue) { void rewriteConfigYesNoOption(struct rewriteConfigState *state, const char *option, int value, int defvalue) {
int force = value != defvalue; int force = value != defvalue;
sds line = sdscatprintf(sdsempty(),"%s %s",option, sds line = sdscatprintf(sdsempty(),"%s %s",option,
value ? "yes" : "no"); value ? "yes" : "no");
...@@ -2236,7 +2113,11 @@ int rewriteConfig(char *path) { ...@@ -2236,7 +2113,11 @@ int rewriteConfig(char *path) {
/* Step 2: rewrite every single option, replacing or appending it inside /* Step 2: rewrite every single option, replacing or appending it inside
* the rewrite state. */ * the rewrite state. */
rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0); /* Iterate the configs that are standard */
for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
rewriteConfigYesNoOption(state,config->name,*(config->config),config->default_value);
}
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE); rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT); rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT); rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
...@@ -2258,9 +2139,6 @@ int rewriteConfig(char *path) { ...@@ -2258,9 +2139,6 @@ int rewriteConfig(char *path) {
rewriteConfigUserOption(state); rewriteConfigUserOption(state);
rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM); rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM);
rewriteConfigNumericalOption(state,"io-threads",server.dbnum,CONFIG_DEFAULT_IO_THREADS_NUM); rewriteConfigNumericalOption(state,"io-threads",server.dbnum,CONFIG_DEFAULT_IO_THREADS_NUM);
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION);
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM);
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME); rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME);
rewriteConfigDirOption(state); rewriteConfigDirOption(state);
rewriteConfigSlaveofOption(state,"replicaof"); rewriteConfigSlaveofOption(state,"replicaof");
...@@ -2268,15 +2146,11 @@ int rewriteConfig(char *path) { ...@@ -2268,15 +2146,11 @@ int rewriteConfig(char *path) {
rewriteConfigStringOption(state,"masteruser",server.masteruser,NULL); rewriteConfigStringOption(state,"masteruser",server.masteruser,NULL);
rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL); rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL); rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL);
rewriteConfigYesNoOption(state,"replica-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
rewriteConfigYesNoOption(state,"replica-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
rewriteConfigYesNoOption(state,"replica-ignore-maxmemory",server.repl_slave_ignore_maxmemory,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY);
rewriteConfigNumericalOption(state,"repl-ping-replica-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD); rewriteConfigNumericalOption(state,"repl-ping-replica-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT); rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE); rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT); rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY); rewriteConfigEnumOption(state,"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum,CONFIG_DEFAULT_REPL_DISKLESS_LOAD);
rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC);
rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY); rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
rewriteConfigNumericalOption(state,"replica-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY); rewriteConfigNumericalOption(state,"replica-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
rewriteConfigNumericalOption(state,"min-replicas-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE); rewriteConfigNumericalOption(state,"min-replicas-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
...@@ -2296,23 +2170,21 @@ int rewriteConfig(char *path) { ...@@ -2296,23 +2170,21 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN); rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN);
rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX); rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX);
rewriteConfigNumericalOption(state,"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS); rewriteConfigNumericalOption(state,"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS);
rewriteConfigYesNoOption(state,"appendonly",server.aof_state != AOF_OFF,0); rewriteConfigYesNoOption(state,"appendonly",server.aof_enabled,0);
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME); rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC); rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,AOF_REWRITE_PERC); rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,AOF_REWRITE_PERC);
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,AOF_REWRITE_MIN_SIZE); rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,AOF_REWRITE_MIN_SIZE);
rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,LUA_SCRIPT_TIME_LIMIT); rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,LUA_SCRIPT_TIME_LIMIT);
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0); rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE); rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
rewriteConfigYesNoOption(state,"cluster-replica-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT); rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER); rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
rewriteConfigNumericalOption(state,"cluster-replica-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY); rewriteConfigNumericalOption(state,"cluster-replica-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN); rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN);
rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD); rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD);
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN); rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN);
rewriteConfigNumericalOption(state,"tracking-table-max-fill",server.tracking_table_max_fill,CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL);
rewriteConfigNotifykeyspaceeventsOption(state); rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,OBJ_HASH_MAX_ZIPLIST_ENTRIES); rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,OBJ_HASH_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,OBJ_HASH_MAX_ZIPLIST_VALUE); rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,OBJ_HASH_MAX_ZIPLIST_VALUE);
...@@ -2324,24 +2196,11 @@ int rewriteConfig(char *path) { ...@@ -2324,24 +2196,11 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,OBJ_ZSET_MAX_ZIPLIST_ENTRIES); rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,OBJ_ZSET_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE); rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES); rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG); rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
rewriteConfigYesNoOption(state,"jemalloc-bg-thread",server.jemalloc_bg_thread,1);
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
rewriteConfigYesNoOption(state,"gopher-enabled",server.gopher_enabled,CONFIG_DEFAULT_GOPHER_ENABLED);
rewriteConfigYesNoOption(state,"io-threads-do-reads",server.io_threads_do_reads,CONFIG_DEFAULT_IO_THREADS_DO_READS);
rewriteConfigClientoutputbufferlimitOption(state); rewriteConfigClientoutputbufferlimitOption(state);
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ); rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
rewriteConfigYesNoOption(state,"rdb-save-incremental-fsync",server.rdb_save_incremental_fsync,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC);
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED);
rewriteConfigYesNoOption(state,"aof-use-rdb-preamble",server.aof_use_rdb_preamble,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE); rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION); rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY);
rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
rewriteConfigYesNoOption(state,"replica-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
rewriteConfigYesNoOption(state,"dynamic-hz",server.dynamic_hz,CONFIG_DEFAULT_DYNAMIC_HZ);
/* Rewrite Sentinel config if in Sentinel mode. */ /* Rewrite Sentinel config if in Sentinel mode. */
if (server.sentinel_mode) rewriteConfigSentinelOption(state); if (server.sentinel_mode) rewriteConfigSentinelOption(state);
......
...@@ -60,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { ...@@ -60,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* Update the access time for the ageing algorithm. /* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger * Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */ * a copy on write madness. */
if (server.rdb_child_pid == -1 && if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val); updateLFU(val);
} else { } else {
...@@ -344,7 +341,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) { ...@@ -344,7 +341,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
* On success the fuction returns the number of keys removed from the * On success the fuction returns the number of keys removed from the
* database(s). Otherwise -1 is returned in the specific case the * database(s). Otherwise -1 is returned in the specific case the
* DB number is out of range, and errno is set to EINVAL. */ * DB number is out of range, and errno is set to EINVAL. */
long long emptyDb(int dbnum, int flags, void(callback)(void*)) { long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
int async = (flags & EMPTYDB_ASYNC); int async = (flags & EMPTYDB_ASYNC);
long long removed = 0; long long removed = 0;
...@@ -353,6 +350,11 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) { ...@@ -353,6 +350,11 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
return -1; return -1;
} }
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
* Note that we need to call the function while the keys are still
* there. */
signalFlushedDb(dbnum);
int startdb, enddb; int startdb, enddb;
if (dbnum == -1) { if (dbnum == -1) {
startdb = 0; startdb = 0;
...@@ -362,12 +364,12 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) { ...@@ -362,12 +364,12 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
} }
for (int j = startdb; j <= enddb; j++) { for (int j = startdb; j <= enddb; j++) {
removed += dictSize(server.db[j].dict); removed += dictSize(dbarray[j].dict);
if (async) { if (async) {
emptyDbAsync(&server.db[j]); emptyDbAsync(&dbarray[j]);
} else { } else {
dictEmpty(server.db[j].dict,callback); dictEmpty(dbarray[j].dict,callback);
dictEmpty(server.db[j].expires,callback); dictEmpty(dbarray[j].expires,callback);
} }
} }
if (server.cluster_enabled) { if (server.cluster_enabled) {
...@@ -381,6 +383,10 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) { ...@@ -381,6 +383,10 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
return removed; return removed;
} }
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
return emptyDbGeneric(server.db, dbnum, flags, callback);
}
int selectDb(client *c, int id) { int selectDb(client *c, int id) {
if (id < 0 || id >= server.dbnum) if (id < 0 || id >= server.dbnum)
return C_ERR; return C_ERR;
...@@ -388,6 +394,15 @@ int selectDb(client *c, int id) { ...@@ -388,6 +394,15 @@ int selectDb(client *c, int id) {
return C_OK; return C_OK;
} }
long long dbTotalServerKeyCount() {
long long total = 0;
int j;
for (j = 0; j < server.dbnum; j++) {
total += dictSize(server.db[j].dict);
}
return total;
}
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Hooks for key space changes. * Hooks for key space changes.
* *
...@@ -399,10 +414,12 @@ int selectDb(client *c, int id) { ...@@ -399,10 +414,12 @@ int selectDb(client *c, int id) {
void signalModifiedKey(redisDb *db, robj *key) { void signalModifiedKey(redisDb *db, robj *key) {
touchWatchedKey(db,key); touchWatchedKey(db,key);
trackingInvalidateKey(key);
} }
void signalFlushedDb(int dbid) { void signalFlushedDb(int dbid) {
touchWatchedKeysOnFlush(dbid); touchWatchedKeysOnFlush(dbid);
trackingInvalidateKeysOnFlush(dbid);
} }
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
...@@ -438,7 +455,6 @@ void flushdbCommand(client *c) { ...@@ -438,7 +455,6 @@ void flushdbCommand(client *c) {
int flags; int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return; if (getFlushCommandFlags(c,&flags) == C_ERR) return;
signalFlushedDb(c->db->id);
server.dirty += emptyDb(c->db->id,flags,NULL); server.dirty += emptyDb(c->db->id,flags,NULL);
addReply(c,shared.ok); addReply(c,shared.ok);
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
...@@ -457,7 +473,6 @@ void flushallCommand(client *c) { ...@@ -457,7 +473,6 @@ void flushallCommand(client *c) {
int flags; int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return; if (getFlushCommandFlags(c,&flags) == C_ERR) return;
signalFlushedDb(-1);
server.dirty += emptyDb(-1,flags,NULL); server.dirty += emptyDb(-1,flags,NULL);
addReply(c,shared.ok); addReply(c,shared.ok);
if (server.rdb_child_pid != -1) killRDBChild(); if (server.rdb_child_pid != -1) killRDBChild();
...@@ -627,7 +642,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) { ...@@ -627,7 +642,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
} }
/* This command implements SCAN, HSCAN and SSCAN commands. /* This command implements SCAN, HSCAN and SSCAN commands.
* If object 'o' is passed, then it must be a Hash or Set object, otherwise * If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise
* if 'o' is NULL the command will operate on the dictionary associated with * if 'o' is NULL the command will operate on the dictionary associated with
* the current database. * the current database.
* *
...@@ -643,6 +658,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { ...@@ -643,6 +658,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
listNode *node, *nextnode; listNode *node, *nextnode;
long count = 10; long count = 10;
sds pat = NULL; sds pat = NULL;
sds typename = NULL;
int patlen = 0, use_pattern = 0; int patlen = 0, use_pattern = 0;
dict *ht; dict *ht;
...@@ -679,6 +695,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { ...@@ -679,6 +695,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
use_pattern = !(pat[0] == '*' && patlen == 1); use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2; i += 2;
} else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {
/* SCAN for a particular type only applies to the db dict */
typename = c->argv[i+1]->ptr;
i+= 2;
} else { } else {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
goto cleanup; goto cleanup;
...@@ -773,6 +793,13 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { ...@@ -773,6 +793,13 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
} }
} }
/* Filter an element if it isn't the type we want. */
if (!filter && o == NULL && typename){
robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
char* type = getObjectTypeName(typecheck);
if (strcasecmp((char*) typename, type)) filter = 1;
}
/* Filter element if it is an expired key. */ /* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1; if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
...@@ -829,11 +856,8 @@ void lastsaveCommand(client *c) { ...@@ -829,11 +856,8 @@ void lastsaveCommand(client *c) {
addReplyLongLong(c,server.lastsave); addReplyLongLong(c,server.lastsave);
} }
void typeCommand(client *c) { char* getObjectTypeName(robj *o) {
robj *o; char* type;
char *type;
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
if (o == NULL) { if (o == NULL) {
type = "none"; type = "none";
} else { } else {
...@@ -851,7 +875,13 @@ void typeCommand(client *c) { ...@@ -851,7 +875,13 @@ void typeCommand(client *c) {
default: type = "unknown"; break; default: type = "unknown"; break;
} }
} }
addReplyStatus(c,type); return type;
}
void typeCommand(client *c) {
robj *o;
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
addReplyStatus(c, getObjectTypeName(o));
} }
void shutdownCommand(client *c) { void shutdownCommand(client *c) {
...@@ -1013,7 +1043,7 @@ void scanDatabaseForReadyLists(redisDb *db) { ...@@ -1013,7 +1043,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
* *
* Returns C_ERR if at least one of the DB ids are out of range, otherwise * Returns C_ERR if at least one of the DB ids are out of range, otherwise
* C_OK is returned. */ * C_OK is returned. */
int dbSwapDatabases(int id1, int id2) { int dbSwapDatabases(long id1, long id2) {
if (id1 < 0 || id1 >= server.dbnum || if (id1 < 0 || id1 >= server.dbnum ||
id2 < 0 || id2 >= server.dbnum) return C_ERR; id2 < 0 || id2 >= server.dbnum) return C_ERR;
if (id1 == id2) return C_OK; if (id1 == id2) return C_OK;
......
...@@ -692,7 +692,8 @@ NULL ...@@ -692,7 +692,8 @@ NULL
dictGetStats(buf,sizeof(buf),server.db[dbid].expires); dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
stats = sdscat(stats,buf); stats = sdscat(stats,buf);
addReplyBulkSds(c,stats); addReplyVerbatim(c,stats,sdslen(stats),"txt");
sdsfree(stats);
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
robj *o; robj *o;
dict *ht = NULL; dict *ht = NULL;
...@@ -719,7 +720,7 @@ NULL ...@@ -719,7 +720,7 @@ NULL
} else { } else {
char buf[4096]; char buf[4096];
dictGetStats(buf,sizeof(buf),ht); dictGetStats(buf,sizeof(buf),ht);
addReplyBulkCString(c,buf); addReplyVerbatim(c,buf,strlen(buf),"txt");
} }
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id"); serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
...@@ -764,7 +765,7 @@ void _serverAssertPrintClientInfo(const client *c) { ...@@ -764,7 +765,7 @@ void _serverAssertPrintClientInfo(const client *c) {
bugReportStart(); bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ==="); serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
serverLog(LL_WARNING,"client->flags = %d", c->flags); serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long)c->flags);
serverLog(LL_WARNING,"client->fd = %d", c->fd); serverLog(LL_WARNING,"client->fd = %d", c->fd);
serverLog(LL_WARNING,"client->argc = %d", c->argc); serverLog(LL_WARNING,"client->argc = %d", c->argc);
for (j=0; j < c->argc; j++) { for (j=0; j < c->argc; j++) {
...@@ -1172,6 +1173,33 @@ void logRegisters(ucontext_t *uc) { ...@@ -1172,6 +1173,33 @@ void logRegisters(ucontext_t *uc) {
(unsigned long) uc->uc_mcontext.mc_cs (unsigned long) uc->uc_mcontext.mc_cs
); );
logStackContent((void**)uc->uc_mcontext.mc_rsp); logStackContent((void**)uc->uc_mcontext.mc_rsp);
#elif defined(__aarch64__) /* Linux AArch64 */
serverLog(LL_WARNING,
"\n"
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
"X30:%016lx\n"
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
(unsigned long) uc->uc_mcontext.regs[18],
(unsigned long) uc->uc_mcontext.regs[19],
(unsigned long) uc->uc_mcontext.regs[20],
(unsigned long) uc->uc_mcontext.regs[21],
(unsigned long) uc->uc_mcontext.regs[22],
(unsigned long) uc->uc_mcontext.regs[23],
(unsigned long) uc->uc_mcontext.regs[24],
(unsigned long) uc->uc_mcontext.regs[25],
(unsigned long) uc->uc_mcontext.regs[26],
(unsigned long) uc->uc_mcontext.regs[27],
(unsigned long) uc->uc_mcontext.regs[28],
(unsigned long) uc->uc_mcontext.regs[29],
(unsigned long) uc->uc_mcontext.regs[30],
(unsigned long) uc->uc_mcontext.pc,
(unsigned long) uc->uc_mcontext.sp,
(unsigned long) uc->uc_mcontext.pstate,
(unsigned long) uc->uc_mcontext.fault_address
);
logStackContent((void**)uc->uc_mcontext.sp);
#else #else
serverLog(LL_WARNING, serverLog(LL_WARNING,
" Dumping of registers not supported for this OS/arch"); " Dumping of registers not supported for this OS/arch");
...@@ -1399,6 +1427,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) { ...@@ -1399,6 +1427,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
/* Log dump of processor registers */ /* Log dump of processor registers */
logRegisters(uc); logRegisters(uc);
/* Log Modules INFO */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
serverLogRaw(LL_WARNING|LL_RAW, infostring);
sdsfree(infostring);
#if defined(HAVE_PROC_MAPS) #if defined(HAVE_PROC_MAPS)
/* Test memory */ /* Test memory */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n"); serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
......
...@@ -1039,7 +1039,7 @@ void activeDefragCycle(void) { ...@@ -1039,7 +1039,7 @@ void activeDefragCycle(void) {
mstime_t latency; mstime_t latency;
int quit = 0; int quit = 0;
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1) if (hasActiveChildProcess())
return; /* Defragging memory while there's a fork will just do damage. */ return; /* Defragging memory while there's a fork will just do damage. */
/* Once a second, check if we the fragmentation justfies starting a scan /* Once a second, check if we the fragmentation justfies starting a scan
......
...@@ -64,6 +64,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) { ...@@ -64,6 +64,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
dbSyncDelete(db,keyobj); dbSyncDelete(db,keyobj);
notifyKeyspaceEvent(NOTIFY_EXPIRED, notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyobj,db->id); "expired",keyobj,db->id);
trackingInvalidateKey(keyobj);
decrRefCount(keyobj); decrRefCount(keyobj);
server.stat_expiredkeys++; server.stat_expiredkeys++;
return 1; return 1;
......
...@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = NULL; robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL || if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL ||
checkType(c, zobj, OBJ_ZSET)) { checkType(c, zobj, OBJ_ZSET)) {
return; return;
} }
...@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
/* If no matching results, the user gets an empty reply. */ /* If no matching results, the user gets an empty reply. */
if (ga->used == 0 && storekey == NULL) { if (ga->used == 0 && storekey == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
geoArrayFree(ga); geoArrayFree(ga);
return; return;
} }
......
...@@ -700,7 +700,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) { ...@@ -700,7 +700,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
p += oplen; p += oplen;
first += span; first += span;
} }
if (span == 0) return -1; /* Invalid format. */ if (span == 0 || p >= end) return -1; /* Invalid format. */
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1; next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
if (next >= end) next = NULL; if (next >= end) next = NULL;
...@@ -1242,7 +1242,7 @@ void pfcountCommand(client *c) { ...@@ -1242,7 +1242,7 @@ void pfcountCommand(client *c) {
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/ if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
if (isHLLObjectOrReply(c,o) != C_OK) return; if (isHLLObjectOrReply(c,o) != C_OK) return;
/* Merge with this HLL with our 'max' HHL by setting max[i] /* Merge with this HLL with our 'max' HLL by setting max[i]
* to MAX(max[i],hll[i]). */ * to MAX(max[i],hll[i]). */
if (hllMerge(registers,o) == C_ERR) { if (hllMerge(registers,o) == C_ERR) {
addReplySds(c,sdsnew(invalid_hll_err)); addReplySds(c,sdsnew(invalid_hll_err));
...@@ -1329,7 +1329,7 @@ void pfmergeCommand(client *c) { ...@@ -1329,7 +1329,7 @@ void pfmergeCommand(client *c) {
hdr = o->ptr; hdr = o->ptr;
if (hdr->encoding == HLL_DENSE) use_dense = 1; if (hdr->encoding == HLL_DENSE) use_dense = 1;
/* Merge with this HLL with our 'max' HHL by setting max[i] /* Merge with this HLL with our 'max' HLL by setting max[i]
* to MAX(max[i],hll[i]). */ * to MAX(max[i],hll[i]). */
if (hllMerge(max,o) == C_ERR) { if (hllMerge(max,o) == C_ERR) {
addReplySds(c,sdsnew(invalid_hll_err)); addReplySds(c,sdsnew(invalid_hll_err));
......
...@@ -599,7 +599,7 @@ NULL ...@@ -599,7 +599,7 @@ NULL
event = dictGetKey(de); event = dictGetKey(de);
graph = latencyCommandGenSparkeline(event,ts); graph = latencyCommandGenSparkeline(event,ts);
addReplyBulkCString(c,graph); addReplyVerbatim(c,graph,sdslen(graph),"txt");
sdsfree(graph); sdsfree(graph);
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
/* LATENCY LATEST */ /* LATENCY LATEST */
...@@ -608,7 +608,7 @@ NULL ...@@ -608,7 +608,7 @@ NULL
/* LATENCY DOCTOR */ /* LATENCY DOCTOR */
sds report = createLatencyReport(); sds report = createLatencyReport();
addReplyBulkCBuffer(c,report,sdslen(report)); addReplyVerbatim(c,report,sdslen(report),"txt");
sdsfree(report); sdsfree(report);
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) { } else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
/* LATENCY RESET */ /* LATENCY RESET */
......
...@@ -43,7 +43,8 @@ void lolwutUnstableCommand(client *c) { ...@@ -43,7 +43,8 @@ void lolwutUnstableCommand(client *c) {
sds rendered = sdsnew("Redis ver. "); sds rendered = sdsnew("Redis ver. ");
rendered = sdscat(rendered,REDIS_VERSION); rendered = sdscat(rendered,REDIS_VERSION);
rendered = sdscatlen(rendered,"\n",1); rendered = sdscatlen(rendered,"\n",1);
addReplyBulkSds(c,rendered); addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
sdsfree(rendered);
} }
void lolwutCommand(client *c) { void lolwutCommand(client *c) {
......
...@@ -277,6 +277,7 @@ void lolwut5Command(client *c) { ...@@ -277,6 +277,7 @@ void lolwut5Command(client *c) {
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. "); "\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
rendered = sdscat(rendered,REDIS_VERSION); rendered = sdscat(rendered,REDIS_VERSION);
rendered = sdscatlen(rendered,"\n",1); rendered = sdscatlen(rendered,"\n",1);
addReplyBulkSds(c,rendered); addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
sdsfree(rendered);
lwFreeCanvas(canvas); lwFreeCanvas(canvas);
} }
...@@ -29,7 +29,9 @@ ...@@ -29,7 +29,9 @@
#include "server.h" #include "server.h"
#include "cluster.h" #include "cluster.h"
#include "rdb.h"
#include <dlfcn.h> #include <dlfcn.h>
#include <sys/wait.h>
#define REDISMODULE_CORE 1 #define REDISMODULE_CORE 1
#include "redismodule.h" #include "redismodule.h"
...@@ -40,6 +42,17 @@ ...@@ -40,6 +42,17 @@
* pointers that have an API the module can call with them) * pointers that have an API the module can call with them)
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
typedef struct RedisModuleInfoCtx {
struct RedisModule *module;
sds requested_section;
sds info; /* info string we collected so far */
int sections; /* number of sections we collected so far */
int in_section; /* indication if we're in an active section or not */
int in_dict_field; /* indication that we're curreintly appending to a dict */
} RedisModuleInfoCtx;
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
/* This structure represents a module inside the system. */ /* This structure represents a module inside the system. */
struct RedisModule { struct RedisModule {
void *handle; /* Module dlopen() handle. */ void *handle; /* Module dlopen() handle. */
...@@ -51,6 +64,8 @@ struct RedisModule { ...@@ -51,6 +64,8 @@ struct RedisModule {
list *using; /* List of modules we use some APIs of. */ list *using; /* List of modules we use some APIs of. */
list *filters; /* List of filters the module has registered. */ list *filters; /* List of filters the module has registered. */
int in_call; /* RM_Call() nesting level */ int in_call; /* RM_Call() nesting level */
int options; /* Module options and capabilities. */
RedisModuleInfoFunc info_cb; /* callback for module to add INFO fields. */
}; };
typedef struct RedisModule RedisModule; typedef struct RedisModule RedisModule;
...@@ -132,10 +147,14 @@ struct RedisModuleCtx { ...@@ -132,10 +147,14 @@ struct RedisModuleCtx {
int keys_count; int keys_count;
struct RedisModulePoolAllocBlock *pa_head; struct RedisModulePoolAllocBlock *pa_head;
redisOpArray saved_oparray; /* When propagating commands in a callback
we reallocate the "also propagate" op
array. Here we save the old one to
restore it later. */
}; };
typedef struct RedisModuleCtx RedisModuleCtx; typedef struct RedisModuleCtx RedisModuleCtx;
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL} #define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL, {0}}
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0) #define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1) #define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2) #define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
...@@ -143,6 +162,7 @@ typedef struct RedisModuleCtx RedisModuleCtx; ...@@ -143,6 +162,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4) #define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
#define REDISMODULE_CTX_THREAD_SAFE (1<<5) #define REDISMODULE_CTX_THREAD_SAFE (1<<5)
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6) #define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
/* This represents a Redis key opened with RM_OpenKey(). */ /* This represents a Redis key opened with RM_OpenKey(). */
struct RedisModuleKey { struct RedisModuleKey {
...@@ -291,6 +311,18 @@ typedef struct RedisModuleCommandFilter { ...@@ -291,6 +311,18 @@ typedef struct RedisModuleCommandFilter {
/* Registered filters */ /* Registered filters */
static list *moduleCommandFilters; static list *moduleCommandFilters;
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
static struct RedisModuleForkInfo {
RedisModuleForkDoneHandler done_handler;
void* done_handler_user_data;
} moduleForkInfo = {0};
/* Flags for moduleCreateArgvFromUserFormat(). */
#define REDISMODULE_ARGV_REPLICATE (1<<0)
#define REDISMODULE_ARGV_NO_AOF (1<<1)
#define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Prototypes * Prototypes
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
...@@ -496,8 +528,47 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) { ...@@ -496,8 +528,47 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Helper function for when a command callback is called, in order to handle
* details needed to correctly replicate commands. */
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
client *c = ctx->client;
/* We don't need to do anything here if the context was never used
* in order to propagate commands. */
if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
if (c->flags & CLIENT_LUA) return;
/* Handle the replication of the final EXEC, since whatever a command
* emits is always wrapped around MULTI/EXEC. */
robj *propargv[1];
propargv[0] = createStringObject("EXEC",4);
alsoPropagate(server.execCommand,c->db->id,propargv,1,
PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(propargv[0]);
/* If this is not a module command context (but is instead a simple
* callback context), we have to handle directly the "also propagate"
* array and emit it. In a module command call this will be handled
* directly by call(). */
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
server.also_propagate.numops)
{
for (int j = 0; j < server.also_propagate.numops; j++) {
redisOp *rop = &server.also_propagate.ops[j];
int target = rop->target;
if (target)
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
}
redisOpArrayFree(&server.also_propagate);
}
/* Restore the previous oparray in case of nexted use of the API. */
server.also_propagate = ctx->saved_oparray;
}
/* Free the context after the user function was called. */ /* Free the context after the user function was called. */
void moduleFreeContext(RedisModuleCtx *ctx) { void moduleFreeContext(RedisModuleCtx *ctx) {
moduleHandlePropagationAfterCommandCallback(ctx);
autoMemoryCollect(ctx); autoMemoryCollect(ctx);
poolAllocRelease(ctx); poolAllocRelease(ctx);
if (ctx->postponed_arrays) { if (ctx->postponed_arrays) {
...@@ -513,34 +584,16 @@ void moduleFreeContext(RedisModuleCtx *ctx) { ...@@ -513,34 +584,16 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client); if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
} }
/* Helper function for when a command callback is called, in order to handle
* details needed to correctly replicate commands. */
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
client *c = ctx->client;
if (c->flags & CLIENT_LUA) return;
/* Handle the replication of the final EXEC, since whatever a command
* emits is always wrapped around MULTI/EXEC. */
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
robj *propargv[1];
propargv[0] = createStringObject("EXEC",4);
alsoPropagate(server.execCommand,c->db->id,propargv,1,
PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(propargv[0]);
}
}
/* This Redis command binds the normal Redis command invocation with commands /* This Redis command binds the normal Redis command invocation with commands
* exported by modules. */ * exported by modules. */
void RedisModuleCommandDispatcher(client *c) { void RedisModuleCommandDispatcher(client *c) {
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc; RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
RedisModuleCtx ctx = REDISMODULE_CTX_INIT; RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
ctx.module = cp->module; ctx.module = cp->module;
ctx.client = c; ctx.client = c;
cp->func(&ctx,(void**)c->argv,c->argc); cp->func(&ctx,(void**)c->argv,c->argc);
moduleHandlePropagationAfterCommandCallback(&ctx);
moduleFreeContext(&ctx); moduleFreeContext(&ctx);
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to /* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
...@@ -771,6 +824,19 @@ long long RM_Milliseconds(void) { ...@@ -771,6 +824,19 @@ long long RM_Milliseconds(void) {
return mstime(); return mstime();
} }
/* Set flags defining capabilities or behavior bit flags.
*
* REDISMODULE_OPTIONS_HANDLE_IO_ERRORS:
* Generally, modules don't need to bother with this, as the process will just
* terminate if a read error happens, however, setting this flag would allow
* repl-diskless-load to work if enabled.
* The module should use RedisModule_IsIOError after reads, before using the
* data that was read, and in case of error, propagate it upwards, and also be
* able to release the partially populated value and all it's allocations. */
void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) {
ctx->module->options = options;
}
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Automatic memory management for modules * Automatic memory management for modules
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
...@@ -1125,10 +1191,9 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) { ...@@ -1125,10 +1191,9 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) { int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
client *c = moduleGetReplyClient(ctx); client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK; if (c == NULL) return REDISMODULE_OK;
sds strmsg = sdsnewlen(prefix,1); addReplyProto(c,prefix,strlen(prefix));
strmsg = sdscat(strmsg,msg); addReplyProto(c,msg,strlen(msg));
strmsg = sdscatlen(strmsg,"\r\n",2); addReplyProto(c,"\r\n",2);
addReplySds(c,strmsg);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -1242,6 +1307,17 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) { ...@@ -1242,6 +1307,17 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* Reply with a bulk string, taking in input a C buffer pointer that is
* assumed to be null-terminated.
*
* The function always returns REDISMODULE_OK. */
int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK;
addReplyBulkCString(c,(char*)buf);
return REDISMODULE_OK;
}
/* Reply with a bulk string, taking in input a RedisModuleString object. /* Reply with a bulk string, taking in input a RedisModuleString object.
* *
* The function always returns REDISMODULE_OK. */ * The function always returns REDISMODULE_OK. */
...@@ -1304,9 +1380,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) { ...@@ -1304,9 +1380,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
/* If we already emitted MULTI return ASAP. */ /* If we already emitted MULTI return ASAP. */
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return; if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
/* If this is a thread safe context, we do not want to wrap commands /* If this is a thread safe context, we do not want to wrap commands
* executed into MUTLI/EXEC, they are executed as single commands * executed into MULTI/EXEC, they are executed as single commands
* from an external client in essence. */ * from an external client in essence. */
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return; if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
/* If this is a callback context, and not a module command execution
* context, we have to setup the op array for the "also propagate" API
* so that RM_Replicate() will work. */
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
ctx->saved_oparray = server.also_propagate;
redisOpArrayInit(&server.also_propagate);
}
execCommandPropagateMulti(ctx->client); execCommandPropagateMulti(ctx->client);
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED; ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
} }
...@@ -1328,6 +1411,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) { ...@@ -1328,6 +1411,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
* *
* Please refer to RedisModule_Call() for more information. * Please refer to RedisModule_Call() for more information.
* *
* Using the special "A" and "R" modifiers, the caller can exclude either
* the AOF or the replicas from the propagation of the specified command.
* Otherwise, by default, the command will be propagated in both channels.
*
* ## Note about calling this function from a thread safe context:
*
* Normally when you call this function from the callback implementing a
* module command, or any other callback provided by the Redis Module API,
* Redis will accumulate all the calls to this function in the context of
* the callback, and will propagate all the commands wrapped in a MULTI/EXEC
* transaction. However when calling this function from a threaded safe context
* that can live an undefined amount of time, and can be locked/unlocked in
* at will, the behavior is different: MULTI/EXEC wrapper is not emitted
* and the command specified is inserted in the AOF and replication stream
* immediately.
*
* ## Return value
*
* The command returns REDISMODULE_ERR if the format specifiers are invalid * The command returns REDISMODULE_ERR if the format specifiers are invalid
* or the command name does not belong to a known command. */ * or the command name does not belong to a known command. */
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
...@@ -1345,10 +1446,23 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) ...@@ -1345,10 +1446,23 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
va_end(ap); va_end(ap);
if (argv == NULL) return REDISMODULE_ERR; if (argv == NULL) return REDISMODULE_ERR;
/* Replicate! */ /* Select the propagation target. Usually is AOF + replicas, however
moduleReplicateMultiIfNeeded(ctx); * the caller can exclude one or the other using the "A" or "R"
alsoPropagate(cmd,ctx->client->db->id,argv,argc, * modifiers. */
PROPAGATE_AOF|PROPAGATE_REPL); int target = 0;
if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF;
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL;
/* Replicate! When we are in a threaded context, we want to just insert
* the replicated command ASAP, since it is not clear when the context
* will stop being used, so accumulating stuff does not make much sense,
* nor we could easily use the alsoPropagate() API from threads. */
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
propagate(cmd,ctx->client->db->id,argv,argc,target);
} else {
moduleReplicateMultiIfNeeded(ctx);
alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
}
/* Release the argv. */ /* Release the argv. */
for (j = 0; j < argc; j++) decrRefCount(argv[j]); for (j = 0; j < argc; j++) decrRefCount(argv[j]);
...@@ -1455,6 +1569,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) { ...@@ -1455,6 +1569,9 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
if (server.cluster_enabled) if (server.cluster_enabled)
flags |= REDISMODULE_CTX_FLAGS_CLUSTER; flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
if (server.loading)
flags |= REDISMODULE_CTX_FLAGS_LOADING;
/* Maxmemory and eviction policy */ /* Maxmemory and eviction policy */
if (server.maxmemory > 0) { if (server.maxmemory > 0) {
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY; flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
...@@ -2374,7 +2491,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { ...@@ -2374,7 +2491,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
* *
* REDISMODULE_HASH_EXISTS: instead of setting the value of the field * REDISMODULE_HASH_EXISTS: instead of setting the value of the field
* expecting a RedisModuleString pointer to pointer, the function just * expecting a RedisModuleString pointer to pointer, the function just
* reports if the field esists or not and expects an integer pointer * reports if the field exists or not and expects an integer pointer
* as the second element of each pair. * as the second element of each pair.
* *
* Example of REDISMODULE_HASH_CFIELD: * Example of REDISMODULE_HASH_CFIELD:
...@@ -2663,12 +2780,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) { ...@@ -2663,12 +2780,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
* to special modifiers in "fmt". For now only one exists: * to special modifiers in "fmt". For now only one exists:
* *
* "!" -> REDISMODULE_ARGV_REPLICATE * "!" -> REDISMODULE_ARGV_REPLICATE
* "A" -> REDISMODULE_ARGV_NO_AOF
* "R" -> REDISMODULE_ARGV_NO_REPLICAS
* *
* On error (format specifier error) NULL is returned and nothing is * On error (format specifier error) NULL is returned and nothing is
* allocated. On success the argument vector is returned. */ * allocated. On success the argument vector is returned. */
#define REDISMODULE_ARGV_REPLICATE (1<<0)
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) { robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
int argc = 0, argv_size, j; int argc = 0, argv_size, j;
robj **argv = NULL; robj **argv = NULL;
...@@ -2717,6 +2833,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int ...@@ -2717,6 +2833,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
} }
} else if (*p == '!') { } else if (*p == '!') {
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE; if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
} else if (*p == 'A') {
if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF;
} else if (*p == 'R') {
if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS;
} else { } else {
goto fmterr; goto fmterr;
} }
...@@ -2737,7 +2857,10 @@ fmterr: ...@@ -2737,7 +2857,10 @@ fmterr:
* NULL is returned and errno is set to the following values: * NULL is returned and errno is set to the following values:
* *
* EINVAL: command non existing, wrong arity, wrong format specifier. * EINVAL: command non existing, wrong arity, wrong format specifier.
* EPERM: operation in Cluster instance with key in non local slot. */ * EPERM: operation in Cluster instance with key in non local slot.
*
* This API is documented here: https://redis.io/topics/modules-intro
*/
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
struct redisCommand *cmd; struct redisCommand *cmd;
client *c = NULL; client *c = NULL;
...@@ -2808,8 +2931,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch ...@@ -2808,8 +2931,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Run the command */ /* Run the command */
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS; int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
if (replicate) { if (replicate) {
call_flags |= CMD_CALL_PROPAGATE_AOF; if (!(flags & REDISMODULE_ARGV_NO_AOF))
call_flags |= CMD_CALL_PROPAGATE_REPL; call_flags |= CMD_CALL_PROPAGATE_AOF;
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
call_flags |= CMD_CALL_PROPAGATE_REPL;
} }
call(c,call_flags); call(c,call_flags);
...@@ -3064,6 +3189,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver, ...@@ -3064,6 +3189,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
moduleTypeMemUsageFunc mem_usage; moduleTypeMemUsageFunc mem_usage;
moduleTypeDigestFunc digest; moduleTypeDigestFunc digest;
moduleTypeFreeFunc free; moduleTypeFreeFunc free;
struct {
moduleTypeAuxLoadFunc aux_load;
moduleTypeAuxSaveFunc aux_save;
int aux_save_triggers;
} v2;
} *tms = (struct typemethods*) typemethods_ptr; } *tms = (struct typemethods*) typemethods_ptr;
moduleType *mt = zcalloc(sizeof(*mt)); moduleType *mt = zcalloc(sizeof(*mt));
...@@ -3075,6 +3205,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver, ...@@ -3075,6 +3205,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
mt->mem_usage = tms->mem_usage; mt->mem_usage = tms->mem_usage;
mt->digest = tms->digest; mt->digest = tms->digest;
mt->free = tms->free; mt->free = tms->free;
if (tms->version >= 2) {
mt->aux_load = tms->v2.aux_load;
mt->aux_save = tms->v2.aux_save;
mt->aux_save_triggers = tms->v2.aux_save_triggers;
}
memcpy(mt->name,name,sizeof(mt->name)); memcpy(mt->name,name,sizeof(mt->name));
listAddNodeTail(ctx->module->types,mt); listAddNodeTail(ctx->module->types,mt);
return mt; return mt;
...@@ -3125,9 +3260,14 @@ void *RM_ModuleTypeGetValue(RedisModuleKey *key) { ...@@ -3125,9 +3260,14 @@ void *RM_ModuleTypeGetValue(RedisModuleKey *key) {
* RDB loading and saving functions * RDB loading and saving functions
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* Called when there is a load error in the context of a module. This cannot /* Called when there is a load error in the context of a module. On some
* be recovered like for the built-in types. */ * modules this cannot be recovered, but if the module declared capability
* to handle errors, we'll raise a flag rather than exiting. */
void moduleRDBLoadError(RedisModuleIO *io) { void moduleRDBLoadError(RedisModuleIO *io) {
if (io->type->module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS) {
io->error = 1;
return;
}
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Error loading data from RDB (short read or EOF). " "Error loading data from RDB (short read or EOF). "
"Read performed by module '%s' about type '%s' " "Read performed by module '%s' about type '%s' "
...@@ -3138,6 +3278,33 @@ void moduleRDBLoadError(RedisModuleIO *io) { ...@@ -3138,6 +3278,33 @@ void moduleRDBLoadError(RedisModuleIO *io) {
exit(1); exit(1);
} }
/* Returns 0 if there's at least one registered data type that did not declare
* REDISMODULE_OPTIONS_HANDLE_IO_ERRORS, in which case diskless loading should
* be avoided since it could cause data loss. */
int moduleAllDatatypesHandleErrors() {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
if (listLength(module->types) &&
!(module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS))
{
dictReleaseIterator(di);
return 0;
}
}
dictReleaseIterator(di);
return 1;
}
/* Returns true if any previous IO API failed.
* for Load* APIs the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with
* RediModule_SetModuleOptions first. */
int RM_IsIOError(RedisModuleIO *io) {
return io->error;
}
/* Save an unsigned 64 bit value into the RDB file. This function should only /* Save an unsigned 64 bit value into the RDB file. This function should only
* be called in the context of the rdb_save method of modules implementing new * be called in the context of the rdb_save method of modules implementing new
* data types. */ * data types. */
...@@ -3161,6 +3328,7 @@ saveerr: ...@@ -3161,6 +3328,7 @@ saveerr:
* be called in the context of the rdb_load method of modules implementing * be called in the context of the rdb_load method of modules implementing
* new data types. */ * new data types. */
uint64_t RM_LoadUnsigned(RedisModuleIO *io) { uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
if (io->error) return 0;
if (io->ver == 2) { if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL); uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr; if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr;
...@@ -3172,7 +3340,7 @@ uint64_t RM_LoadUnsigned(RedisModuleIO *io) { ...@@ -3172,7 +3340,7 @@ uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
loaderr: loaderr:
moduleRDBLoadError(io); moduleRDBLoadError(io);
return 0; /* Never reached. */ return 0;
} }
/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */ /* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */
...@@ -3231,6 +3399,7 @@ saveerr: ...@@ -3231,6 +3399,7 @@ saveerr:
/* Implements RM_LoadString() and RM_LoadStringBuffer() */ /* Implements RM_LoadString() and RM_LoadStringBuffer() */
void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) { void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
if (io->error) return NULL;
if (io->ver == 2) { if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL); uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr; if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr;
...@@ -3242,7 +3411,7 @@ void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) { ...@@ -3242,7 +3411,7 @@ void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
loaderr: loaderr:
moduleRDBLoadError(io); moduleRDBLoadError(io);
return NULL; /* Never reached. */ return NULL;
} }
/* In the context of the rdb_load method of a module data type, loads a string /* In the context of the rdb_load method of a module data type, loads a string
...@@ -3263,7 +3432,7 @@ RedisModuleString *RM_LoadString(RedisModuleIO *io) { ...@@ -3263,7 +3432,7 @@ RedisModuleString *RM_LoadString(RedisModuleIO *io) {
* RedisModule_Realloc() or RedisModule_Free(). * RedisModule_Realloc() or RedisModule_Free().
* *
* The size of the string is stored at '*lenptr' if not NULL. * The size of the string is stored at '*lenptr' if not NULL.
* The returned string is not automatically NULL termianted, it is loaded * The returned string is not automatically NULL terminated, it is loaded
* exactly as it was stored inisde the RDB file. */ * exactly as it was stored inisde the RDB file. */
char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) { char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) {
return moduleLoadString(io,1,lenptr); return moduleLoadString(io,1,lenptr);
...@@ -3291,6 +3460,7 @@ saveerr: ...@@ -3291,6 +3460,7 @@ saveerr:
/* In the context of the rdb_save method of a module data type, loads back the /* In the context of the rdb_save method of a module data type, loads back the
* double value saved by RedisModule_SaveDouble(). */ * double value saved by RedisModule_SaveDouble(). */
double RM_LoadDouble(RedisModuleIO *io) { double RM_LoadDouble(RedisModuleIO *io) {
if (io->error) return 0;
if (io->ver == 2) { if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL); uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr; if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr;
...@@ -3302,7 +3472,7 @@ double RM_LoadDouble(RedisModuleIO *io) { ...@@ -3302,7 +3472,7 @@ double RM_LoadDouble(RedisModuleIO *io) {
loaderr: loaderr:
moduleRDBLoadError(io); moduleRDBLoadError(io);
return 0; /* Never reached. */ return 0;
} }
/* In the context of the rdb_save method of a module data type, saves a float /* In the context of the rdb_save method of a module data type, saves a float
...@@ -3327,6 +3497,7 @@ saveerr: ...@@ -3327,6 +3497,7 @@ saveerr:
/* In the context of the rdb_save method of a module data type, loads back the /* In the context of the rdb_save method of a module data type, loads back the
* float value saved by RedisModule_SaveFloat(). */ * float value saved by RedisModule_SaveFloat(). */
float RM_LoadFloat(RedisModuleIO *io) { float RM_LoadFloat(RedisModuleIO *io) {
if (io->error) return 0;
if (io->ver == 2) { if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL); uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr; if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr;
...@@ -3338,7 +3509,37 @@ float RM_LoadFloat(RedisModuleIO *io) { ...@@ -3338,7 +3509,37 @@ float RM_LoadFloat(RedisModuleIO *io) {
loaderr: loaderr:
moduleRDBLoadError(io); moduleRDBLoadError(io);
return 0; /* Never reached. */ return 0;
}
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
* who asked for it. */
ssize_t rdbSaveModulesAux(rio *rdb, int when) {
size_t total_written = 0;
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
listIter li;
listNode *ln;
listRewind(module->types,&li);
while((ln = listNext(&li))) {
moduleType *mt = ln->value;
if (!mt->aux_save || !(mt->aux_save_triggers & when))
continue;
ssize_t ret = rdbSaveSingleModuleAux(rdb, when, mt);
if (ret==-1) {
dictReleaseIterator(di);
return -1;
}
total_written += ret;
}
}
dictReleaseIterator(di);
return total_written;
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
...@@ -3501,7 +3702,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li ...@@ -3501,7 +3702,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
if (level < server.verbosity) return; if (level < server.verbosity) return;
name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name); name_len = snprintf(msg, sizeof(msg),"<%s> ", module? module->name: "module");
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap); vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
serverLogRaw(level,msg); serverLogRaw(level,msg);
} }
...@@ -3519,13 +3720,15 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li ...@@ -3519,13 +3720,15 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
* There is a fixed limit to the length of the log line this function is able * There is a fixed limit to the length of the log line this function is able
* to emit, this limit is not specified but is guaranteed to be more than * to emit, this limit is not specified but is guaranteed to be more than
* a few lines of text. * a few lines of text.
*
* The ctx argument may be NULL if cannot be provided in the context of the
* caller for instance threads or callbacks, in which case a generic "module"
* will be used instead of the module name.
*/ */
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) { void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
if (!ctx->module) return; /* Can only log if module is initialized */
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
RM_LogRaw(ctx->module,levelstr,fmt,ap); RM_LogRaw(ctx? ctx->module: NULL,levelstr,fmt,ap);
va_end(ap); va_end(ap);
} }
...@@ -3541,6 +3744,15 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ... ...@@ -3541,6 +3744,15 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
va_end(ap); va_end(ap);
} }
/* Redis-like assert function.
*
* A failed assertion will shut down the server and produce logging information
* that looks identical to information generated by Redis itself.
*/
void RM__Assert(const char *estr, const char *file, int line) {
_serverAssert(estr, file, line);
}
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Blocking clients from modules * Blocking clients from modules
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
...@@ -4672,6 +4884,194 @@ int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *k ...@@ -4672,6 +4884,194 @@ int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *k
return res ? REDISMODULE_OK : REDISMODULE_ERR; return res ? REDISMODULE_OK : REDISMODULE_ERR;
} }
/* --------------------------------------------------------------------------
* Modules Info fields
* -------------------------------------------------------------------------- */
int RM_InfoEndDictField(RedisModuleInfoCtx *ctx);
/* Used to start a new section, before adding any fields. the section name will
* be prefixed by "<modulename>_" and must only include A-Z,a-z,0-9.
* NULL or empty string indicates the default section (only <modulename>) is used.
* When return value is REDISMODULE_ERR, the section should and will be skipped. */
int RM_InfoAddSection(RedisModuleInfoCtx *ctx, char *name) {
sds full_name = sdsdup(ctx->module->name);
if (name != NULL && strlen(name) > 0)
full_name = sdscatfmt(full_name, "_%s", name);
/* Implicitly end dicts, instead of returning an error which is likely un checked. */
if (ctx->in_dict_field)
RM_InfoEndDictField(ctx);
/* proceed only if:
* 1) no section was requested (emit all)
* 2) the module name was requested (emit all)
* 3) this specific section was requested. */
if (ctx->requested_section) {
if (strcasecmp(ctx->requested_section, full_name) &&
strcasecmp(ctx->requested_section, ctx->module->name)) {
sdsfree(full_name);
ctx->in_section = 0;
return REDISMODULE_ERR;
}
}
if (ctx->sections++) ctx->info = sdscat(ctx->info,"\r\n");
ctx->info = sdscatfmt(ctx->info, "# %S\r\n", full_name);
ctx->in_section = 1;
sdsfree(full_name);
return REDISMODULE_OK;
}
/* Starts a dict field, similar to the ones in INFO KEYSPACE. Use normal
* RedisModule_InfoAddField* functions to add the items to this field, and
* terminate with RedisModule_InfoEndDictField. */
int RM_InfoBeginDictField(RedisModuleInfoCtx *ctx, char *name) {
if (!ctx->in_section)
return REDISMODULE_ERR;
/* Implicitly end dicts, instead of returning an error which is likely un checked. */
if (ctx->in_dict_field)
RM_InfoEndDictField(ctx);
ctx->info = sdscatfmt(ctx->info,
"%s_%s:",
ctx->module->name,
name);
ctx->in_dict_field = 1;
return REDISMODULE_OK;
}
/* Ends a dict field, see RedisModule_InfoBeginDictField */
int RM_InfoEndDictField(RedisModuleInfoCtx *ctx) {
if (!ctx->in_dict_field)
return REDISMODULE_ERR;
/* trim the last ',' if found. */
if (ctx->info[sdslen(ctx->info)-1]==',')
sdsIncrLen(ctx->info, -1);
ctx->info = sdscat(ctx->info, "\r\n");
ctx->in_dict_field = 0;
return REDISMODULE_OK;
}
/* Used by RedisModuleInfoFunc to add info fields.
* Each field will be automatically prefixed by "<modulename>_".
* Field names or values must not include \r\n of ":" */
int RM_InfoAddFieldString(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
if (ctx->in_dict_field) {
ctx->info = sdscatfmt(ctx->info,
"%s=%S,",
field,
(sds)value->ptr);
return REDISMODULE_OK;
}
ctx->info = sdscatfmt(ctx->info,
"%s_%s:%S\r\n",
ctx->module->name,
field,
(sds)value->ptr);
return REDISMODULE_OK;
}
int RM_InfoAddFieldCString(RedisModuleInfoCtx *ctx, char *field, char *value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
if (ctx->in_dict_field) {
ctx->info = sdscatfmt(ctx->info,
"%s=%s,",
field,
value);
return REDISMODULE_OK;
}
ctx->info = sdscatfmt(ctx->info,
"%s_%s:%s\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_InfoAddFieldDouble(RedisModuleInfoCtx *ctx, char *field, double value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
if (ctx->in_dict_field) {
ctx->info = sdscatprintf(ctx->info,
"%s=%.17g,",
field,
value);
return REDISMODULE_OK;
}
ctx->info = sdscatprintf(ctx->info,
"%s_%s:%.17g\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_InfoAddFieldLongLong(RedisModuleInfoCtx *ctx, char *field, long long value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
if (ctx->in_dict_field) {
ctx->info = sdscatfmt(ctx->info,
"%s=%I,",
field,
value);
return REDISMODULE_OK;
}
ctx->info = sdscatfmt(ctx->info,
"%s_%s:%I\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_InfoAddFieldULongLong(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) {
if (!ctx->in_section)
return REDISMODULE_ERR;
if (ctx->in_dict_field) {
ctx->info = sdscatfmt(ctx->info,
"%s=%U,",
field,
value);
return REDISMODULE_OK;
}
ctx->info = sdscatfmt(ctx->info,
"%s_%s:%U\r\n",
ctx->module->name,
field,
value);
return REDISMODULE_OK;
}
int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
ctx->module->info_cb = cb;
return REDISMODULE_OK;
}
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections) {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct RedisModule *module = dictGetVal(de);
if (!module->info_cb)
continue;
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0};
module->info_cb(&info_ctx, for_crash_report);
/* Implicitly end dicts (no way to handle errors, and we must add the newline). */
if (info_ctx.in_dict_field)
RM_InfoEndDictField(&info_ctx);
info = info_ctx.info;
sections = info_ctx.sections;
}
dictReleaseIterator(di);
return info;
}
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules utility APIs * Modules utility APIs
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
...@@ -4906,11 +5306,13 @@ int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *fi ...@@ -4906,11 +5306,13 @@ int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *fi
ln = listSearchKey(moduleCommandFilters,filter); ln = listSearchKey(moduleCommandFilters,filter);
if (!ln) return REDISMODULE_ERR; if (!ln) return REDISMODULE_ERR;
listDelNode(moduleCommandFilters,ln); listDelNode(moduleCommandFilters,ln);
ln = listSearchKey(ctx->module->filters,filter); ln = listSearchKey(ctx->module->filters,filter);
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */ if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
listDelNode(ctx->module->filters,ln); listDelNode(ctx->module->filters,ln);
zfree(filter);
return REDISMODULE_OK; return REDISMODULE_OK;
} }
...@@ -5014,6 +5416,100 @@ int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos) ...@@ -5014,6 +5416,100 @@ int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
return REDISMODULE_OK; return REDISMODULE_OK;
} }
/* --------------------------------------------------------------------------
* Module fork API
* -------------------------------------------------------------------------- */
/* Create a background child process with the current frozen snaphost of the
* main process where you can do some processing in the background without
* affecting / freezing the traffic and no need for threads and GIL locking.
* Note that Redis allows for only one concurrent fork.
* When the child wants to exit, it should call RedisModule_ExitFromChild.
* If the parent wants to kill the child it should call RedisModule_KillForkChild
* The done handler callback will be executed on the parent process when the
* child existed (but not when killed)
* Return: -1 on failure, on success the parent process will get a positive PID
* of the child, and the child process will get 0.
*/
int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
pid_t childpid;
if (hasActiveChildProcess()) {
return -1;
}
openChildInfoPipe();
if ((childpid = redisFork()) == 0) {
/* Child */
redisSetProcTitle("redis-module-fork");
} else if (childpid == -1) {
closeChildInfoPipe();
serverLog(LL_WARNING,"Can't fork for module: %s", strerror(errno));
} else {
/* Parent */
server.module_child_pid = childpid;
moduleForkInfo.done_handler = cb;
moduleForkInfo.done_handler_user_data = user_data;
serverLog(LL_NOTICE, "Module fork started pid: %d ", childpid);
}
return childpid;
}
/* Call from the child process when you want to terminate it.
* retcode will be provided to the done handler executed on the parent process.
*/
int RM_ExitFromChild(int retcode) {
sendChildCOWInfo(CHILD_INFO_TYPE_MODULE, "Module fork");
exitFromChild(retcode);
return REDISMODULE_OK;
}
/* Kill the active module forked child, if there is one active and the
* pid matches, and returns C_OK. Otherwise if there is no active module
* child or the pid does not match, return C_ERR without doing anything. */
int TerminateModuleForkChild(int child_pid, int wait) {
/* Module child should be active and pid should match. */
if (server.module_child_pid == -1 ||
server.module_child_pid != child_pid) return C_ERR;
int statloc;
serverLog(LL_NOTICE,"Killing running module fork child: %ld",
(long) server.module_child_pid);
if (kill(server.module_child_pid,SIGUSR1) != -1 && wait) {
while(wait4(server.module_child_pid,&statloc,0,NULL) !=
server.module_child_pid);
}
/* Reset the buffer accumulating changes while the child saves. */
server.module_child_pid = -1;
moduleForkInfo.done_handler = NULL;
moduleForkInfo.done_handler_user_data = NULL;
closeChildInfoPipe();
updateDictResizePolicy();
return C_OK;
}
/* Can be used to kill the forked child process from the parent process.
* child_pid whould be the return value of RedisModule_Fork. */
int RM_KillForkChild(int child_pid) {
/* Kill module child, wait for child exit. */
if (TerminateModuleForkChild(child_pid,1) == C_OK)
return REDISMODULE_OK;
else
return REDISMODULE_ERR;
}
void ModuleForkDoneHandler(int exitcode, int bysignal) {
serverLog(LL_NOTICE,
"Module fork exited pid: %d, retcode: %d, bysignal: %d",
server.module_child_pid, exitcode, bysignal);
if (moduleForkInfo.done_handler) {
moduleForkInfo.done_handler(exitcode, bysignal,
moduleForkInfo.done_handler_user_data);
}
server.module_child_pid = -1;
moduleForkInfo.done_handler = NULL;
moduleForkInfo.done_handler_user_data = NULL;
}
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Modules API internals * Modules API internals
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
...@@ -5113,6 +5609,8 @@ void moduleLoadFromQueue(void) { ...@@ -5113,6 +5609,8 @@ void moduleLoadFromQueue(void) {
void moduleFreeModuleStructure(struct RedisModule *module) { void moduleFreeModuleStructure(struct RedisModule *module) {
listRelease(module->types); listRelease(module->types);
listRelease(module->filters); listRelease(module->filters);
listRelease(module->usedby);
listRelease(module->using);
sdsfree(module->name); sdsfree(module->name);
zfree(module); zfree(module);
} }
...@@ -5247,6 +5745,62 @@ void addReplyLoadedModules(client *c) { ...@@ -5247,6 +5745,62 @@ void addReplyLoadedModules(client *c) {
dictReleaseIterator(di); dictReleaseIterator(di);
} }
/* Helper for genModulesInfoString(): given a list of modules, return
* am SDS string in the form "[modulename|modulename2|...]" */
sds genModulesInfoStringRenderModulesList(list *l) {
listIter li;
listNode *ln;
listRewind(l,&li);
sds output = sdsnew("[");
while((ln = listNext(&li))) {
RedisModule *module = ln->value;
output = sdscat(output,module->name);
}
output = sdstrim(output,"|");
output = sdscat(output,"]");
return output;
}
/* Helper for genModulesInfoString(): render module options as an SDS string. */
sds genModulesInfoStringRenderModuleOptions(struct RedisModule *module) {
sds output = sdsnew("[");
if (module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS)
output = sdscat(output,"handle-io-errors|");
output = sdstrim(output,"|");
output = sdscat(output,"]");
return output;
}
/* Helper function for the INFO command: adds loaded modules as to info's
* output.
*
* After the call, the passed sds info string is no longer valid and all the
* references must be substituted with the new pointer returned by the call. */
sds genModulesInfoString(sds info) {
dictIterator *di = dictGetIterator(modules);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
sds name = dictGetKey(de);
struct RedisModule *module = dictGetVal(de);
sds usedby = genModulesInfoStringRenderModulesList(module->usedby);
sds using = genModulesInfoStringRenderModulesList(module->using);
sds options = genModulesInfoStringRenderModuleOptions(module);
info = sdscatfmt(info,
"module:name=%S,ver=%i,api=%i,filters=%i,"
"usedby=%S,using=%S,options=%S\r\n",
name, module->ver, module->apiver,
(int)listLength(module->filters), usedby, using, options);
sdsfree(usedby);
sdsfree(using);
sdsfree(options);
}
dictReleaseIterator(di);
return info;
}
/* Redis MODULE command. /* Redis MODULE command.
* *
* MODULE LOAD <path> [args...] */ * MODULE LOAD <path> [args...] */
...@@ -5332,6 +5886,7 @@ void moduleRegisterCoreAPI(void) { ...@@ -5332,6 +5886,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ReplySetArrayLength); REGISTER_API(ReplySetArrayLength);
REGISTER_API(ReplyWithString); REGISTER_API(ReplyWithString);
REGISTER_API(ReplyWithStringBuffer); REGISTER_API(ReplyWithStringBuffer);
REGISTER_API(ReplyWithCString);
REGISTER_API(ReplyWithNull); REGISTER_API(ReplyWithNull);
REGISTER_API(ReplyWithCallReply); REGISTER_API(ReplyWithCallReply);
REGISTER_API(ReplyWithDouble); REGISTER_API(ReplyWithDouble);
...@@ -5394,6 +5949,8 @@ void moduleRegisterCoreAPI(void) { ...@@ -5394,6 +5949,8 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ModuleTypeSetValue); REGISTER_API(ModuleTypeSetValue);
REGISTER_API(ModuleTypeGetType); REGISTER_API(ModuleTypeGetType);
REGISTER_API(ModuleTypeGetValue); REGISTER_API(ModuleTypeGetValue);
REGISTER_API(IsIOError);
REGISTER_API(SetModuleOptions);
REGISTER_API(SaveUnsigned); REGISTER_API(SaveUnsigned);
REGISTER_API(LoadUnsigned); REGISTER_API(LoadUnsigned);
REGISTER_API(SaveSigned); REGISTER_API(SaveSigned);
...@@ -5409,6 +5966,7 @@ void moduleRegisterCoreAPI(void) { ...@@ -5409,6 +5966,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(EmitAOF); REGISTER_API(EmitAOF);
REGISTER_API(Log); REGISTER_API(Log);
REGISTER_API(LogIOError); REGISTER_API(LogIOError);
REGISTER_API(_Assert);
REGISTER_API(StringAppendBuffer); REGISTER_API(StringAppendBuffer);
REGISTER_API(RetainString); REGISTER_API(RetainString);
REGISTER_API(StringCompare); REGISTER_API(StringCompare);
...@@ -5476,4 +6034,16 @@ void moduleRegisterCoreAPI(void) { ...@@ -5476,4 +6034,16 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(CommandFilterArgInsert); REGISTER_API(CommandFilterArgInsert);
REGISTER_API(CommandFilterArgReplace); REGISTER_API(CommandFilterArgReplace);
REGISTER_API(CommandFilterArgDelete); REGISTER_API(CommandFilterArgDelete);
REGISTER_API(Fork);
REGISTER_API(ExitFromChild);
REGISTER_API(KillForkChild);
REGISTER_API(RegisterInfoFunc);
REGISTER_API(InfoAddSection);
REGISTER_API(InfoBeginDictField);
REGISTER_API(InfoEndDictField);
REGISTER_API(InfoAddFieldString);
REGISTER_API(InfoAddFieldCString);
REGISTER_API(InfoAddFieldDouble);
REGISTER_API(InfoAddFieldLongLong);
REGISTER_API(InfoAddFieldULongLong);
} }
...@@ -175,7 +175,19 @@ void execCommand(client *c) { ...@@ -175,7 +175,19 @@ void execCommand(client *c) {
must_propagate = 1; must_propagate = 1;
} }
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL); int acl_retval = ACLCheckCommandPerm(c);
if (acl_retval != ACL_OK) {
addReplyErrorFormat(c,
"-NOPERM ACLs rules changed between the moment the "
"transaction was accumulated and the EXEC call. "
"This command is no longer allowed for the "
"following reason: %s",
(acl_retval == ACL_DENIED_CMD) ?
"no permission to execute the command or subcommand" :
"no permission to touch the specified keys");
} else {
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
}
/* Commands may alter argc/argv, restore mstate. */ /* Commands may alter argc/argv, restore mstate. */
c->mstate.commands[j].argc = c->argc; c->mstate.commands[j].argc = c->argc;
......
...@@ -158,6 +158,7 @@ client *createClient(int fd) { ...@@ -158,6 +158,7 @@ client *createClient(int fd) {
c->pubsub_patterns = listCreate(); c->pubsub_patterns = listCreate();
c->peerid = NULL; c->peerid = NULL;
c->client_list_node = NULL; c->client_list_node = NULL;
c->client_tracking_redirection = 0;
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid); listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects); listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
if (fd != -1) linkClient(c); if (fd != -1) linkClient(c);
...@@ -506,7 +507,7 @@ void addReplyDouble(client *c, double d) { ...@@ -506,7 +507,7 @@ void addReplyDouble(client *c, double d) {
if (c->resp == 2) { if (c->resp == 2) {
addReplyBulkCString(c, d > 0 ? "inf" : "-inf"); addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
} else { } else {
addReplyProto(c, d > 0 ? ",inf\r\n" : "-inf\r\n", addReplyProto(c, d > 0 ? ",inf\r\n" : ",-inf\r\n",
d > 0 ? 6 : 7); d > 0 ? 6 : 7);
} }
} else { } else {
...@@ -966,6 +967,9 @@ void unlinkClient(client *c) { ...@@ -966,6 +967,9 @@ void unlinkClient(client *c) {
listDelNode(server.unblocked_clients,ln); listDelNode(server.unblocked_clients,ln);
c->flags &= ~CLIENT_UNBLOCKED; c->flags &= ~CLIENT_UNBLOCKED;
} }
/* Clear the tracking status. */
if (c->flags & CLIENT_TRACKING) disableTracking(c);
} }
void freeClient(client *c) { void freeClient(client *c) {
...@@ -1849,6 +1853,8 @@ sds catClientInfoString(sds s, client *client) { ...@@ -1849,6 +1853,8 @@ sds catClientInfoString(sds s, client *client) {
if (client->flags & CLIENT_PUBSUB) *p++ = 'P'; if (client->flags & CLIENT_PUBSUB) *p++ = 'P';
if (client->flags & CLIENT_MULTI) *p++ = 'x'; if (client->flags & CLIENT_MULTI) *p++ = 'x';
if (client->flags & CLIENT_BLOCKED) *p++ = 'b'; if (client->flags & CLIENT_BLOCKED) *p++ = 'b';
if (client->flags & CLIENT_TRACKING) *p++ = 't';
if (client->flags & CLIENT_TRACKING_BROKEN_REDIR) *p++ = 'R';
if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd'; if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd';
if (client->flags & CLIENT_CLOSE_AFTER_REPLY) *p++ = 'c'; if (client->flags & CLIENT_CLOSE_AFTER_REPLY) *p++ = 'c';
if (client->flags & CLIENT_UNBLOCKED) *p++ = 'u'; if (client->flags & CLIENT_UNBLOCKED) *p++ = 'u';
...@@ -1948,19 +1954,21 @@ void clientCommand(client *c) { ...@@ -1948,19 +1954,21 @@ void clientCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"id -- Return the ID of the current connection.", "ID -- Return the ID of the current connection.",
"getname -- Return the name of the current connection.", "GETNAME -- Return the name of the current connection.",
"kill <ip:port> -- Kill connection made from <ip:port>.", "KILL <ip:port> -- Kill connection made from <ip:port>.",
"kill <option> <value> [option value ...] -- Kill connections. Options are:", "KILL <option> <value> [option value ...] -- Kill connections. Options are:",
" addr <ip:port> -- Kill connection made from <ip:port>", " ADDR <ip:port> -- Kill connection made from <ip:port>",
" type (normal|master|replica|pubsub) -- Kill connections by type.", " TYPE (normal|master|replica|pubsub) -- Kill connections by type.",
" skipme (yes|no) -- Skip killing current connection (default: yes).", " SKIPME (yes|no) -- Skip killing current connection (default: yes).",
"list [options ...] -- Return information about client connections. Options:", "LIST [options ...] -- Return information about client connections. Options:",
" type (normal|master|replica|pubsub) -- Return clients of specified type.", " TYPE (normal|master|replica|pubsub) -- Return clients of specified type.",
"pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.", "PAUSE <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
"reply (on|off|skip) -- Control the replies sent to the current connection.", "REPLY (on|off|skip) -- Control the replies sent to the current connection.",
"setname <name> -- Assign the name <name> to the current connection.", "SETNAME <name> -- Assign the name <name> to the current connection.",
"unblock <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.", "UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
"TRACKING (on|off) [REDIRECT <id>] -- Enable client keys tracking for client side caching.",
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -1982,7 +1990,7 @@ NULL ...@@ -1982,7 +1990,7 @@ NULL
return; return;
} }
sds o = getAllClientsInfoString(type); sds o = getAllClientsInfoString(type);
addReplyBulkCBuffer(c,o,sdslen(o)); addReplyVerbatim(c,o,sdslen(o),"txt");
sdsfree(o); sdsfree(o);
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
/* CLIENT REPLY ON|OFF|SKIP */ /* CLIENT REPLY ON|OFF|SKIP */
...@@ -2117,20 +2125,63 @@ NULL ...@@ -2117,20 +2125,63 @@ NULL
addReply(c,shared.czero); addReply(c,shared.czero);
} }
} else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) {
/* CLIENT SETNAME */
if (clientSetNameOrReply(c,c->argv[2]) == C_OK) if (clientSetNameOrReply(c,c->argv[2]) == C_OK)
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"getname") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"getname") && c->argc == 2) {
/* CLIENT GETNAME */
if (c->name) if (c->name)
addReplyBulk(c,c->name); addReplyBulk(c,c->name);
else else
addReplyNull(c); addReplyNull(c);
} else if (!strcasecmp(c->argv[1]->ptr,"pause") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"pause") && c->argc == 3) {
/* CLIENT PAUSE */
long long duration; long long duration;
if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,UNIT_MILLISECONDS) if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,
!= C_OK) return; UNIT_MILLISECONDS) != C_OK) return;
pauseClients(duration); pauseClients(duration);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"tracking") &&
(c->argc == 3 || c->argc == 5))
{
/* CLIENT TRACKING (on|off) [REDIRECT <id>] */
long long redir = 0;
/* Parse the redirection option: we'll require the client with
* the specified ID to exist right now, even if it is possible
* it will get disconnected later. */
if (c->argc == 5) {
if (strcasecmp(c->argv[3]->ptr,"redirect") != 0) {
addReply(c,shared.syntaxerr);
return;
} else {
if (getLongLongFromObjectOrReply(c,c->argv[4],&redir,NULL) !=
C_OK) return;
if (lookupClientByID(redir) == NULL) {
addReplyError(c,"The client ID you want redirect to "
"does not exist");
return;
}
}
}
if (!strcasecmp(c->argv[2]->ptr,"on")) {
enableTracking(c,redir);
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
disableTracking(c);
} else {
addReply(c,shared.syntaxerr);
return;
}
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"getredir") && c->argc == 2) {
/* CLIENT GETREDIR */
if (c->flags & CLIENT_TRACKING) {
addReplyLongLong(c,c->client_tracking_redirection);
} else {
addReplyLongLong(c,-1);
}
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLIENT HELP", (char*)c->argv[1]->ptr); addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLIENT HELP", (char*)c->argv[1]->ptr);
} }
...@@ -2417,17 +2468,27 @@ void flushSlavesOutputBuffers(void) { ...@@ -2417,17 +2468,27 @@ void flushSlavesOutputBuffers(void) {
listRewind(server.slaves,&li); listRewind(server.slaves,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *slave = listNodeValue(ln); client *slave = listNodeValue(ln);
int events; int events = aeGetFileEvents(server.el,slave->fd);
int can_receive_writes = (events & AE_WRITABLE) ||
/* Note that the following will not flush output buffers of slaves (slave->flags & CLIENT_PENDING_WRITE);
* in STATE_ONLINE but having put_online_on_ack set to true: in this
* case the writable event is never installed, since the purpose /* We don't want to send the pending data to the replica in a few
* of put_online_on_ack is to postpone the moment it is installed. * cases:
* This is what we want since slaves in this state should not receive *
* writes before the first ACK. */ * 1. For some reason there is neither the write handler installed
events = aeGetFileEvents(server.el,slave->fd); * nor the client is flagged as to have pending writes: for some
if (events & AE_WRITABLE && * reason this replica may not be set to receive data. This is
slave->replstate == SLAVE_STATE_ONLINE && * just for the sake of defensive programming.
*
* 2. The put_online_on_ack flag is true. To know why we don't want
* to send data to the replica in this case, please grep for the
* flag for this flag.
*
* 3. Obviously if the slave is not ONLINE.
*/
if (slave->replstate == SLAVE_STATE_ONLINE &&
can_receive_writes &&
!slave->repl_put_online_on_ack &&
clientHasPendingReplies(slave)) clientHasPendingReplies(slave))
{ {
writeToClient(slave->fd,slave,0); writeToClient(slave->fd,slave,0);
......
...@@ -467,10 +467,15 @@ robj *tryObjectEncoding(robj *o) { ...@@ -467,10 +467,15 @@ robj *tryObjectEncoding(robj *o) {
incrRefCount(shared.integers[value]); incrRefCount(shared.integers[value]);
return shared.integers[value]; return shared.integers[value];
} else { } else {
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr); if (o->encoding == OBJ_ENCODING_RAW) {
o->encoding = OBJ_ENCODING_INT; sdsfree(o->ptr);
o->ptr = (void*) value; o->encoding = OBJ_ENCODING_INT;
return o; o->ptr = (void*) value;
return o;
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {
decrRefCount(o);
return createStringObjectFromLongLongForValue(value);
}
} }
} }
...@@ -834,7 +839,9 @@ size_t objectComputeSize(robj *o, size_t sample_size) { ...@@ -834,7 +839,9 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
d = ((zset*)o->ptr)->dict; d = ((zset*)o->ptr)->dict;
zskiplist *zsl = ((zset*)o->ptr)->zsl; zskiplist *zsl = ((zset*)o->ptr)->zsl;
zskiplistNode *znode = zsl->header->level[0].forward; zskiplistNode *znode = zsl->header->level[0].forward;
asize = sizeof(*o)+sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d)); asize = sizeof(*o)+sizeof(zset)+sizeof(zskiplist)+sizeof(dict)+
(sizeof(struct dictEntry*)*dictSlots(d))+
zmalloc_size(zsl->header);
while(znode != NULL && samples < sample_size) { while(znode != NULL && samples < sample_size) {
elesize += sdsAllocSize(znode->ele); elesize += sdsAllocSize(znode->ele);
elesize += sizeof(struct dictEntry) + zmalloc_size(znode); elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
...@@ -1433,13 +1440,15 @@ NULL ...@@ -1433,13 +1440,15 @@ NULL
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
sds info = sdsempty(); sds info = sdsempty();
je_malloc_stats_print(inputCatSds, &info, NULL); je_malloc_stats_print(inputCatSds, &info, NULL);
addReplyBulkSds(c, info); addReplyVerbatim(c,info,sdslen(info),"txt");
sdsfree(info);
#else #else
addReplyBulkCString(c,"Stats not supported for the current allocator"); addReplyBulkCString(c,"Stats not supported for the current allocator");
#endif #endif
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
sds report = getMemoryDoctorReport(); sds report = getMemoryDoctorReport();
addReplyBulkSds(c,report); addReplyVerbatim(c,report,sdslen(report),"txt");
sdsfree(report);
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
char tmp[32]; char tmp[32];
......
...@@ -42,30 +42,41 @@ ...@@ -42,30 +42,41 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/param.h> #include <sys/param.h>
#define rdbExitReportCorruptRDB(...) rdbCheckThenExit(__LINE__,__VA_ARGS__) /* This macro is called when the internal RDB stracture is corrupt */
#define rdbExitReportCorruptRDB(...) rdbReportError(1, __LINE__,__VA_ARGS__)
/* This macro is called when RDB read failed (possibly a short read) */
#define rdbReportReadError(...) rdbReportError(0, __LINE__,__VA_ARGS__)
char* rdbFileBeingLoaded = NULL; /* used for rdb checking on read error */
extern int rdbCheckMode; extern int rdbCheckMode;
void rdbCheckError(const char *fmt, ...); void rdbCheckError(const char *fmt, ...);
void rdbCheckSetError(const char *fmt, ...); void rdbCheckSetError(const char *fmt, ...);
void rdbCheckThenExit(int linenum, char *reason, ...) { void rdbReportError(int corruption_error, int linenum, char *reason, ...) {
va_list ap; va_list ap;
char msg[1024]; char msg[1024];
int len; int len;
len = snprintf(msg,sizeof(msg), len = snprintf(msg,sizeof(msg),
"Internal error in RDB reading function at rdb.c:%d -> ", linenum); "Internal error in RDB reading offset %llu, function at rdb.c:%d -> ",
(unsigned long long)server.loading_loaded_bytes, linenum);
va_start(ap,reason); va_start(ap,reason);
vsnprintf(msg+len,sizeof(msg)-len,reason,ap); vsnprintf(msg+len,sizeof(msg)-len,reason,ap);
va_end(ap); va_end(ap);
if (!rdbCheckMode) { if (!rdbCheckMode) {
serverLog(LL_WARNING, "%s", msg); if (rdbFileBeingLoaded || corruption_error) {
char *argv[2] = {"",server.rdb_filename}; serverLog(LL_WARNING, "%s", msg);
redis_check_rdb_main(2,argv,NULL); char *argv[2] = {"",rdbFileBeingLoaded};
redis_check_rdb_main(2,argv,NULL);
} else {
serverLog(LL_WARNING, "%s. Failure loading rdb format from socket, assuming connection error, resuming operation.", msg);
return;
}
} else { } else {
rdbCheckError("%s",msg); rdbCheckError("%s",msg);
} }
serverLog(LL_WARNING, "Terminating server after rdb file reading failure.");
exit(1); exit(1);
} }
...@@ -75,18 +86,6 @@ static int rdbWriteRaw(rio *rdb, void *p, size_t len) { ...@@ -75,18 +86,6 @@ static int rdbWriteRaw(rio *rdb, void *p, size_t len) {
return len; return len;
} }
/* This is just a wrapper for the low level function rioRead() that will
* automatically abort if it is not possible to read the specified amount
* of bytes. */
void rdbLoadRaw(rio *rdb, void *buf, uint64_t len) {
if (rioRead(rdb,buf,len) == 0) {
rdbExitReportCorruptRDB(
"Impossible to read %llu bytes in rdbLoadRaw()",
(unsigned long long) len);
return; /* Not reached. */
}
}
int rdbSaveType(rio *rdb, unsigned char type) { int rdbSaveType(rio *rdb, unsigned char type) {
return rdbWriteRaw(rdb,&type,1); return rdbWriteRaw(rdb,&type,1);
} }
...@@ -102,10 +101,12 @@ int rdbLoadType(rio *rdb) { ...@@ -102,10 +101,12 @@ int rdbLoadType(rio *rdb) {
/* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME /* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME
* opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS * opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS
* opcode. */ * opcode. On error -1 is returned, however this could be a valid time, so
* to check for loading errors the caller should call rioGetReadError() after
* calling this function. */
time_t rdbLoadTime(rio *rdb) { time_t rdbLoadTime(rio *rdb) {
int32_t t32; int32_t t32;
rdbLoadRaw(rdb,&t32,4); if (rioRead(rdb,&t32,4) == 0) return -1;
return (time_t)t32; return (time_t)t32;
} }
...@@ -125,10 +126,14 @@ int rdbSaveMillisecondTime(rio *rdb, long long t) { ...@@ -125,10 +126,14 @@ int rdbSaveMillisecondTime(rio *rdb, long long t) {
* after upgrading to Redis version 5 they will no longer be able to load their * after upgrading to Redis version 5 they will no longer be able to load their
* own old RDB files. Because of that, we instead fix the function only for new * own old RDB files. Because of that, we instead fix the function only for new
* RDB versions, and load older RDB versions as we used to do in the past, * RDB versions, and load older RDB versions as we used to do in the past,
* allowing big endian systems to load their own old RDB files. */ * allowing big endian systems to load their own old RDB files.
*
* On I/O error the function returns LLONG_MAX, however if this is also a
* valid stored value, the caller should use rioGetReadError() to check for
* errors after calling this function. */
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) { long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
int64_t t64; int64_t t64;
rdbLoadRaw(rdb,&t64,8); if (rioRead(rdb,&t64,8) == 0) return LLONG_MAX;
if (rdbver >= 9) /* Check the top comment of this function. */ if (rdbver >= 9) /* Check the top comment of this function. */
memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */ memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
return (long long)t64; return (long long)t64;
...@@ -255,7 +260,7 @@ int rdbEncodeInteger(long long value, unsigned char *enc) { ...@@ -255,7 +260,7 @@ int rdbEncodeInteger(long long value, unsigned char *enc) {
/* Loads an integer-encoded object with the specified encoding type "enctype". /* Loads an integer-encoded object with the specified encoding type "enctype".
* The returned value changes according to the flags, see * The returned value changes according to the flags, see
* rdbGenerincLoadStringObject() for more info. */ * rdbGenericLoadStringObject() for more info. */
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) { void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
int plain = flags & RDB_LOAD_PLAIN; int plain = flags & RDB_LOAD_PLAIN;
int sds = flags & RDB_LOAD_SDS; int sds = flags & RDB_LOAD_SDS;
...@@ -277,8 +282,8 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) { ...@@ -277,8 +282,8 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24); v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
val = (int32_t)v; val = (int32_t)v;
} else { } else {
val = 0; /* anti-warning */
rdbExitReportCorruptRDB("Unknown RDB integer encoding type %d",enctype); rdbExitReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
return NULL; /* Never reached. */
} }
if (plain || sds) { if (plain || sds) {
char buf[LONG_STR_SIZE], *p; char buf[LONG_STR_SIZE], *p;
...@@ -381,8 +386,7 @@ void *rdbLoadLzfStringObject(rio *rdb, int flags, size_t *lenptr) { ...@@ -381,8 +386,7 @@ void *rdbLoadLzfStringObject(rio *rdb, int flags, size_t *lenptr) {
/* Load the compressed representation and uncompress it to target. */ /* Load the compressed representation and uncompress it to target. */
if (rioRead(rdb,c,clen) == 0) goto err; if (rioRead(rdb,c,clen) == 0) goto err;
if (lzf_decompress(c,clen,val,len) == 0) { if (lzf_decompress(c,clen,val,len) == 0) {
if (rdbCheckMode) rdbCheckSetError("Invalid LZF compressed string"); rdbExitReportCorruptRDB("Invalid LZF compressed string");
goto err;
} }
zfree(c); zfree(c);
...@@ -496,6 +500,7 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) { ...@@ -496,6 +500,7 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
return rdbLoadLzfStringObject(rdb,flags,lenptr); return rdbLoadLzfStringObject(rdb,flags,lenptr);
default: default:
rdbExitReportCorruptRDB("Unknown RDB string encoding type %d",len); rdbExitReportCorruptRDB("Unknown RDB string encoding type %d",len);
return NULL; /* Never reached. */
} }
} }
...@@ -966,7 +971,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) { ...@@ -966,7 +971,6 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
RedisModuleIO io; RedisModuleIO io;
moduleValue *mv = o->ptr; moduleValue *mv = o->ptr;
moduleType *mt = mv->type; moduleType *mt = mv->type;
moduleInitIOContext(io,mt,rdb,key);
/* Write the "module" identifier as prefix, so that we'll be able /* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */ * to call the right module during loading. */
...@@ -975,10 +979,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) { ...@@ -975,10 +979,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
io.bytes += retval; io.bytes += retval;
/* Then write the module-specific representation + EOF marker. */ /* Then write the module-specific representation + EOF marker. */
moduleInitIOContext(io,mt,rdb,key);
mt->rdb_save(&io,mv->value); mt->rdb_save(&io,mv->value);
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF); retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
if (retval == -1) return -1; if (retval == -1)
io.bytes += retval; io.error = 1;
else
io.bytes += retval;
if (io.ctx) { if (io.ctx) {
moduleFreeContext(io.ctx); moduleFreeContext(io.ctx);
...@@ -1039,6 +1046,11 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) { ...@@ -1039,6 +1046,11 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
if (rdbSaveObjectType(rdb,val) == -1) return -1; if (rdbSaveObjectType(rdb,val) == -1) return -1;
if (rdbSaveStringObject(rdb,key) == -1) return -1; if (rdbSaveStringObject(rdb,key) == -1) return -1;
if (rdbSaveObject(rdb,val,key) == -1) return -1; if (rdbSaveObject(rdb,val,key) == -1) return -1;
/* Delay return if required (for testing) */
if (server.rdb_key_save_delay)
usleep(server.rdb_key_save_delay);
return 1; return 1;
} }
...@@ -1091,6 +1103,45 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) { ...@@ -1091,6 +1103,45 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
return 1; return 1;
} }
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
/* Save a module-specific aux value. */
RedisModuleIO io;
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
/* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */
retval = rdbSaveLen(rdb,mt->id);
if (retval == -1) return -1;
io.bytes += retval;
/* write the 'when' so that we can provide it on loading. add a UINT opcode
* for backwards compatibility, everything after the MT needs to be prefixed
* by an opcode. */
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_UINT);
if (retval == -1) return -1;
io.bytes += retval;
retval = rdbSaveLen(rdb,when);
if (retval == -1) return -1;
io.bytes += retval;
/* Then write the module-specific representation + EOF marker. */
moduleInitIOContext(io,mt,rdb,NULL);
mt->aux_save(&io,when);
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
if (retval == -1)
io.error = 1;
else
io.bytes += retval;
if (io.ctx) {
moduleFreeContext(io.ctx);
zfree(io.ctx);
}
if (io.error)
return -1;
return io.bytes;
}
/* Produces a dump of the database in RDB format sending it to the specified /* Produces a dump of the database in RDB format sending it to the specified
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR * Redis I/O channel. On success C_OK is returned, otherwise C_ERR
* is returned and part of the output, or all the output, can be * is returned and part of the output, or all the output, can be
...@@ -1112,6 +1163,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) { ...@@ -1112,6 +1163,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION); snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr; if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr; if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
for (j = 0; j < server.dbnum; j++) { for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j; redisDb *db = server.db+j;
...@@ -1173,6 +1225,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) { ...@@ -1173,6 +1225,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
di = NULL; /* So that we don't release it again on error. */ di = NULL; /* So that we don't release it again on error. */
} }
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
/* EOF opcode */ /* EOF opcode */
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr; if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
...@@ -1281,40 +1335,25 @@ werr: ...@@ -1281,40 +1335,25 @@ werr:
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) { int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid; pid_t childpid;
long long start;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR; if (hasActiveChildProcess()) return C_ERR;
server.dirty_before_bgsave = server.dirty; server.dirty_before_bgsave = server.dirty;
server.lastbgsave_try = time(NULL); server.lastbgsave_try = time(NULL);
openChildInfoPipe(); openChildInfoPipe();
start = ustime(); if ((childpid = redisFork()) == 0) {
if ((childpid = fork()) == 0) {
int retval; int retval;
/* Child */ /* Child */
closeListeningSockets(0);
redisSetProcTitle("redis-rdb-bgsave"); redisSetProcTitle("redis-rdb-bgsave");
retval = rdbSave(filename,rsi); retval = rdbSave(filename,rsi);
if (retval == C_OK) { if (retval == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1); sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
if (private_dirty) {
serverLog(LL_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
sendChildInfo(CHILD_INFO_TYPE_RDB);
} }
exitFromChild((retval == C_OK) ? 0 : 1); exitFromChild((retval == C_OK) ? 0 : 1);
} else { } else {
/* Parent */ /* Parent */
server.stat_fork_time = ustime()-start;
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) { if (childpid == -1) {
closeChildInfoPipe(); closeChildInfoPipe();
server.lastbgsave_status = C_ERR; server.lastbgsave_status = C_ERR;
...@@ -1326,7 +1365,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) { ...@@ -1326,7 +1365,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
server.rdb_save_time_start = time(NULL); server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid; server.rdb_child_pid = childpid;
server.rdb_child_type = RDB_CHILD_TYPE_DISK; server.rdb_child_type = RDB_CHILD_TYPE_DISK;
updateDictResizePolicy();
return C_OK; return C_OK;
} }
return C_OK; /* unreached */ return C_OK; /* unreached */
...@@ -1632,6 +1670,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1632,6 +1670,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
hashTypeConvert(o, OBJ_ENCODING_HT); hashTypeConvert(o, OBJ_ENCODING_HT);
break; break;
default: default:
/* totally unreachable */
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype); rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
break; break;
} }
...@@ -1639,6 +1678,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1639,6 +1678,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
o = createStreamObject(); o = createStreamObject();
stream *s = o->ptr; stream *s = o->ptr;
uint64_t listpacks = rdbLoadLen(rdb,NULL); uint64_t listpacks = rdbLoadLen(rdb,NULL);
if (listpacks == RDB_LENERR) {
rdbReportReadError("Stream listpacks len loading failed.");
decrRefCount(o);
return NULL;
}
while(listpacks--) { while(listpacks--) {
/* Get the master ID, the one we'll use as key of the radix tree /* Get the master ID, the one we'll use as key of the radix tree
...@@ -1646,7 +1690,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1646,7 +1690,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
* relatively to this ID. */ * relatively to this ID. */
sds nodekey = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL); sds nodekey = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
if (nodekey == NULL) { if (nodekey == NULL) {
rdbExitReportCorruptRDB("Stream master ID loading failed: invalid encoding or I/O error."); rdbReportReadError("Stream master ID loading failed: invalid encoding or I/O error.");
decrRefCount(o);
return NULL;
} }
if (sdslen(nodekey) != sizeof(streamID)) { if (sdslen(nodekey) != sizeof(streamID)) {
rdbExitReportCorruptRDB("Stream node key entry is not the " rdbExitReportCorruptRDB("Stream node key entry is not the "
...@@ -1656,7 +1702,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1656,7 +1702,12 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
/* Load the listpack. */ /* Load the listpack. */
unsigned char *lp = unsigned char *lp =
rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL); rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL);
if (lp == NULL) return NULL; if (lp == NULL) {
rdbReportReadError("Stream listpacks loading failed.");
sdsfree(nodekey);
decrRefCount(o);
return NULL;
}
unsigned char *first = lpFirst(lp); unsigned char *first = lpFirst(lp);
if (first == NULL) { if (first == NULL) {
/* Serialized listpacks should never be empty, since on /* Serialized listpacks should never be empty, since on
...@@ -1674,12 +1725,24 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1674,12 +1725,24 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
} }
/* Load total number of items inside the stream. */ /* Load total number of items inside the stream. */
s->length = rdbLoadLen(rdb,NULL); s->length = rdbLoadLen(rdb,NULL);
/* Load the last entry ID. */ /* Load the last entry ID. */
s->last_id.ms = rdbLoadLen(rdb,NULL); s->last_id.ms = rdbLoadLen(rdb,NULL);
s->last_id.seq = rdbLoadLen(rdb,NULL); s->last_id.seq = rdbLoadLen(rdb,NULL);
if (rioGetReadError(rdb)) {
rdbReportReadError("Stream object metadata loading failed.");
decrRefCount(o);
return NULL;
}
/* Consumer groups loading */ /* Consumer groups loading */
size_t cgroups_count = rdbLoadLen(rdb,NULL); uint64_t cgroups_count = rdbLoadLen(rdb,NULL);
if (cgroups_count == RDB_LENERR) {
rdbReportReadError("Stream cgroup count loading failed.");
decrRefCount(o);
return NULL;
}
while(cgroups_count--) { while(cgroups_count--) {
/* Get the consumer group name and ID. We can then create the /* Get the consumer group name and ID. We can then create the
* consumer group ASAP and populate its structure as * consumer group ASAP and populate its structure as
...@@ -1687,11 +1750,21 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1687,11 +1750,21 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
streamID cg_id; streamID cg_id;
sds cgname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL); sds cgname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
if (cgname == NULL) { if (cgname == NULL) {
rdbExitReportCorruptRDB( rdbReportReadError(
"Error reading the consumer group name from Stream"); "Error reading the consumer group name from Stream");
decrRefCount(o);
return NULL;
} }
cg_id.ms = rdbLoadLen(rdb,NULL); cg_id.ms = rdbLoadLen(rdb,NULL);
cg_id.seq = rdbLoadLen(rdb,NULL); cg_id.seq = rdbLoadLen(rdb,NULL);
if (rioGetReadError(rdb)) {
rdbReportReadError("Stream cgroup ID loading failed.");
sdsfree(cgname);
decrRefCount(o);
return NULL;
}
streamCG *cgroup = streamCreateCG(s,cgname,sdslen(cgname),&cg_id); streamCG *cgroup = streamCreateCG(s,cgname,sdslen(cgname),&cg_id);
if (cgroup == NULL) if (cgroup == NULL)
rdbExitReportCorruptRDB("Duplicated consumer group name %s", rdbExitReportCorruptRDB("Duplicated consumer group name %s",
...@@ -1703,13 +1776,28 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1703,13 +1776,28 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
* owner, since consumers for this group and their messages will * owner, since consumers for this group and their messages will
* be read as a next step. So for now leave them not resolved * be read as a next step. So for now leave them not resolved
* and later populate it. */ * and later populate it. */
size_t pel_size = rdbLoadLen(rdb,NULL); uint64_t pel_size = rdbLoadLen(rdb,NULL);
if (pel_size == RDB_LENERR) {
rdbReportReadError("Stream PEL size loading failed.");
decrRefCount(o);
return NULL;
}
while(pel_size--) { while(pel_size--) {
unsigned char rawid[sizeof(streamID)]; unsigned char rawid[sizeof(streamID)];
rdbLoadRaw(rdb,rawid,sizeof(rawid)); if (rioRead(rdb,rawid,sizeof(rawid)) == 0) {
rdbReportReadError("Stream PEL ID loading failed.");
decrRefCount(o);
return NULL;
}
streamNACK *nack = streamCreateNACK(NULL); streamNACK *nack = streamCreateNACK(NULL);
nack->delivery_time = rdbLoadMillisecondTime(rdb,RDB_VERSION); nack->delivery_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
nack->delivery_count = rdbLoadLen(rdb,NULL); nack->delivery_count = rdbLoadLen(rdb,NULL);
if (rioGetReadError(rdb)) {
rdbReportReadError("Stream PEL NACK loading failed.");
decrRefCount(o);
streamFreeNACK(nack);
return NULL;
}
if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL)) if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL))
rdbExitReportCorruptRDB("Duplicated gobal PEL entry " rdbExitReportCorruptRDB("Duplicated gobal PEL entry "
"loading stream consumer group"); "loading stream consumer group");
...@@ -1717,24 +1805,47 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1717,24 +1805,47 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
/* Now that we loaded our global PEL, we need to load the /* Now that we loaded our global PEL, we need to load the
* consumers and their local PELs. */ * consumers and their local PELs. */
size_t consumers_num = rdbLoadLen(rdb,NULL); uint64_t consumers_num = rdbLoadLen(rdb,NULL);
if (consumers_num == RDB_LENERR) {
rdbReportReadError("Stream consumers num loading failed.");
decrRefCount(o);
return NULL;
}
while(consumers_num--) { while(consumers_num--) {
sds cname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL); sds cname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
if (cname == NULL) { if (cname == NULL) {
rdbExitReportCorruptRDB( rdbReportReadError(
"Error reading the consumer name from Stream group"); "Error reading the consumer name from Stream group.");
decrRefCount(o);
return NULL;
} }
streamConsumer *consumer = streamLookupConsumer(cgroup,cname, streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
1); 1);
sdsfree(cname); sdsfree(cname);
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION); consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
if (rioGetReadError(rdb)) {
rdbReportReadError("Stream short read reading seen time.");
decrRefCount(o);
return NULL;
}
/* Load the PEL about entries owned by this specific /* Load the PEL about entries owned by this specific
* consumer. */ * consumer. */
pel_size = rdbLoadLen(rdb,NULL); pel_size = rdbLoadLen(rdb,NULL);
if (pel_size == RDB_LENERR) {
rdbReportReadError(
"Stream consumer PEL num loading failed.");
decrRefCount(o);
return NULL;
}
while(pel_size--) { while(pel_size--) {
unsigned char rawid[sizeof(streamID)]; unsigned char rawid[sizeof(streamID)];
rdbLoadRaw(rdb,rawid,sizeof(rawid)); if (rioRead(rdb,rawid,sizeof(rawid)) == 0) {
rdbReportReadError(
"Stream short read reading PEL streamID.");
decrRefCount(o);
return NULL;
}
streamNACK *nack = raxFind(cgroup->pel,rawid,sizeof(rawid)); streamNACK *nack = raxFind(cgroup->pel,rawid,sizeof(rawid));
if (nack == raxNotFound) if (nack == raxNotFound)
rdbExitReportCorruptRDB("Consumer entry not found in " rdbExitReportCorruptRDB("Consumer entry not found in "
...@@ -1753,6 +1864,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1753,6 +1864,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
} }
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) { } else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
uint64_t moduleid = rdbLoadLen(rdb,NULL); uint64_t moduleid = rdbLoadLen(rdb,NULL);
if (rioGetReadError(rdb)) return NULL;
moduleType *mt = moduleTypeLookupModuleByID(moduleid); moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10]; char name[10];
...@@ -1780,6 +1892,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1780,6 +1892,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
/* Module v2 serialization has an EOF mark at the end. */ /* Module v2 serialization has an EOF mark at the end. */
if (io.ver == 2) { if (io.ver == 2) {
uint64_t eof = rdbLoadLen(rdb,NULL); uint64_t eof = rdbLoadLen(rdb,NULL);
if (eof == RDB_LENERR) {
o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */
decrRefCount(o);
return NULL;
}
if (eof != RDB_MODULE_OPCODE_EOF) { if (eof != RDB_MODULE_OPCODE_EOF) {
serverLog(LL_WARNING,"The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name); serverLog(LL_WARNING,"The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name);
exit(1); exit(1);
...@@ -1793,25 +1910,31 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) { ...@@ -1793,25 +1910,31 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
} }
o = createModuleObject(mt,ptr); o = createModuleObject(mt,ptr);
} else { } else {
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype); rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
return NULL;
} }
return o; return o;
} }
/* Mark that we are loading in the global state and setup the fields /* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */ * needed to provide loading stats. */
void startLoading(FILE *fp) { void startLoading(size_t size) {
struct stat sb;
/* Load the DB */ /* Load the DB */
server.loading = 1; server.loading = 1;
server.loading_start_time = time(NULL); server.loading_start_time = time(NULL);
server.loading_loaded_bytes = 0; server.loading_loaded_bytes = 0;
if (fstat(fileno(fp), &sb) == -1) { server.loading_total_bytes = size;
server.loading_total_bytes = 0; }
} else {
server.loading_total_bytes = sb.st_size; /* Mark that we are loading in the global state and setup the fields
} * needed to provide loading stats.
* 'filename' is optional and used for rdb-check on error */
void startLoadingFile(FILE *fp, char* filename) {
struct stat sb;
if (fstat(fileno(fp), &sb) == -1)
sb.st_size = 0;
rdbFileBeingLoaded = filename;
startLoading(sb.st_size);
} }
/* Refresh the loading progress info */ /* Refresh the loading progress info */
...@@ -1824,6 +1947,7 @@ void loadingProgress(off_t pos) { ...@@ -1824,6 +1947,7 @@ void loadingProgress(off_t pos) {
/* Loading finished */ /* Loading finished */
void stopLoading(void) { void stopLoading(void) {
server.loading = 0; server.loading = 0;
rdbFileBeingLoaded = NULL;
} }
/* Track loading progress in order to serve client's from time to time /* Track loading progress in order to serve client's from time to time
...@@ -1886,11 +2010,13 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1886,11 +2010,13 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
* load the actual type, and continue. */ * load the actual type, and continue. */
expiretime = rdbLoadTime(rdb); expiretime = rdbLoadTime(rdb);
expiretime *= 1000; expiretime *= 1000;
if (rioGetReadError(rdb)) goto eoferr;
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EXPIRETIME_MS) { } else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced /* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */ * with RDB v3. Like EXPIRETIME but no with more precision. */
expiretime = rdbLoadMillisecondTime(rdb,rdbver); expiretime = rdbLoadMillisecondTime(rdb,rdbver);
if (rioGetReadError(rdb)) goto eoferr;
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_FREQ) { } else if (type == RDB_OPCODE_FREQ) {
/* FREQ: LFU frequency. */ /* FREQ: LFU frequency. */
...@@ -1991,15 +2117,15 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1991,15 +2117,15 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
decrRefCount(auxval); decrRefCount(auxval);
continue; /* Read type again. */ continue; /* Read type again. */
} else if (type == RDB_OPCODE_MODULE_AUX) { } else if (type == RDB_OPCODE_MODULE_AUX) {
/* This is just for compatibility with the future: we have plans /* Load module data that is not related to the Redis key space.
* to add the ability for modules to store anything in the RDB * Such data can be potentially be stored both before and after the
* file, like data that is not related to the Redis key space. * RDB keys-values section. */
* Such data will potentially be stored both before and after the
* RDB keys-values section. For this reason since RDB version 9,
* we have the ability to read a MODULE_AUX opcode followed by an
* identifier of the module, and a serialized value in "MODULE V2"
* format. */
uint64_t moduleid = rdbLoadLen(rdb,NULL); uint64_t moduleid = rdbLoadLen(rdb,NULL);
int when_opcode = rdbLoadLen(rdb,NULL);
int when = rdbLoadLen(rdb,NULL);
if (rioGetReadError(rdb)) goto eoferr;
if (when_opcode != RDB_MODULE_OPCODE_UINT)
rdbReportReadError("bad when_opcode");
moduleType *mt = moduleTypeLookupModuleByID(moduleid); moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10]; char name[10];
moduleTypeNameByID(name,moduleid); moduleTypeNameByID(name,moduleid);
...@@ -2009,14 +2135,37 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -2009,14 +2135,37 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name); serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
exit(1); exit(1);
} else if (!rdbCheckMode && mt != NULL) { } else if (!rdbCheckMode && mt != NULL) {
/* This version of Redis actually does not know what to do if (!mt->aux_load) {
* with modules AUX data... */ /* Module doesn't support AUX. */
serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load for the module '%s'. Probably you want to use a newer version of Redis which implements aux data callbacks", name); serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
exit(1); exit(1);
}
RedisModuleIO io;
moduleInitIOContext(io,mt,rdb,NULL);
io.ver = 2;
/* Call the rdb_load method of the module providing the 10 bit
* encoding version in the lower 10 bits of the module ID. */
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
moduleTypeNameByID(name,moduleid);
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
exit(1);
}
if (io.ctx) {
moduleFreeContext(io.ctx);
zfree(io.ctx);
}
uint64_t eof = rdbLoadLen(rdb,NULL);
if (eof != RDB_MODULE_OPCODE_EOF) {
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
exit(1);
}
continue;
} else { } else {
/* RDB check mode. */ /* RDB check mode. */
robj *aux = rdbLoadCheckModuleValue(rdb,name); robj *aux = rdbLoadCheckModuleValue(rdb,name);
decrRefCount(aux); decrRefCount(aux);
continue; /* Read next opcode. */
} }
} }
...@@ -2070,10 +2219,15 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -2070,10 +2219,15 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
} }
return C_OK; return C_OK;
eoferr: /* unexpected end of file is handled here with a fatal exit */ /* Unexpected end of file is handled here calling rdbReportReadError():
serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now."); * this will in turn either abort Redis in most cases, or if we are loading
rdbExitReportCorruptRDB("Unexpected EOF reading RDB file"); * the RDB file from a socket during initial SYNC (diskless replica mode),
return C_ERR; /* Just to avoid warning */ * we'll report the error to the caller, so that we can retry. */
eoferr:
serverLog(LL_WARNING,
"Short read or OOM loading DB. Unrecoverable error, aborting now.");
rdbReportReadError("Unexpected EOF reading RDB file");
return C_ERR;
} }
/* Like rdbLoadRio() but takes a filename instead of a rio stream. The /* Like rdbLoadRio() but takes a filename instead of a rio stream. The
...@@ -2089,7 +2243,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi) { ...@@ -2089,7 +2243,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi) {
int retval; int retval;
if ((fp = fopen(filename,"r")) == NULL) return C_ERR; if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
startLoading(fp); startLoadingFile(fp, filename);
rioInitWithFile(&rdb,fp); rioInitWithFile(&rdb,fp);
retval = rdbLoadRio(&rdb,rsi,0); retval = rdbLoadRio(&rdb,rsi,0);
fclose(fp); fclose(fp);
...@@ -2261,10 +2415,9 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) { ...@@ -2261,10 +2415,9 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
listNode *ln; listNode *ln;
listIter li; listIter li;
pid_t childpid; pid_t childpid;
long long start;
int pipefds[2]; int pipefds[2];
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR; if (hasActiveChildProcess()) return C_ERR;
/* Before to fork, create a pipe that will be used in order to /* Before to fork, create a pipe that will be used in order to
* send back to the parent the IDs of the slaves that successfully * send back to the parent the IDs of the slaves that successfully
...@@ -2300,8 +2453,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) { ...@@ -2300,8 +2453,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
/* Create the child process. */ /* Create the child process. */
openChildInfoPipe(); openChildInfoPipe();
start = ustime(); if ((childpid = redisFork()) == 0) {
if ((childpid = fork()) == 0) {
/* Child */ /* Child */
int retval; int retval;
rio slave_sockets; rio slave_sockets;
...@@ -2309,7 +2461,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) { ...@@ -2309,7 +2461,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
rioInitWithFdset(&slave_sockets,fds,numfds); rioInitWithFdset(&slave_sockets,fds,numfds);
zfree(fds); zfree(fds);
closeListeningSockets(0);
redisSetProcTitle("redis-rdb-to-slaves"); redisSetProcTitle("redis-rdb-to-slaves");
retval = rdbSaveRioWithEOFMark(&slave_sockets,NULL,rsi); retval = rdbSaveRioWithEOFMark(&slave_sockets,NULL,rsi);
...@@ -2317,16 +2468,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) { ...@@ -2317,16 +2468,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
retval = C_ERR; retval = C_ERR;
if (retval == C_OK) { if (retval == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1); sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
if (private_dirty) {
serverLog(LL_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
sendChildInfo(CHILD_INFO_TYPE_RDB);
/* If we are returning OK, at least one slave was served /* If we are returning OK, at least one slave was served
* with the RDB file as expected, so we need to send a report * with the RDB file as expected, so we need to send a report
...@@ -2395,16 +2537,11 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) { ...@@ -2395,16 +2537,11 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
close(pipefds[1]); close(pipefds[1]);
closeChildInfoPipe(); closeChildInfoPipe();
} else { } else {
server.stat_fork_time = ustime()-start;
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d", serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
childpid); childpid);
server.rdb_save_time_start = time(NULL); server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid; server.rdb_child_pid = childpid;
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET; server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
updateDictResizePolicy();
} }
zfree(clientids); zfree(clientids);
zfree(fds); zfree(fds);
...@@ -2447,15 +2584,15 @@ void bgsaveCommand(client *c) { ...@@ -2447,15 +2584,15 @@ void bgsaveCommand(client *c) {
if (server.rdb_child_pid != -1) { if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress"); addReplyError(c,"Background save already in progress");
} else if (server.aof_child_pid != -1) { } else if (hasActiveChildProcess()) {
if (schedule) { if (schedule) {
server.rdb_bgsave_scheduled = 1; server.rdb_bgsave_scheduled = 1;
addReplyStatus(c,"Background saving scheduled"); addReplyStatus(c,"Background saving scheduled");
} else { } else {
addReplyError(c, addReplyError(c,
"An AOF log rewriting in progress: can't BGSAVE right now. " "Another child process is active (AOF?): can't BGSAVE right now. "
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever " "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
"possible."); "possible.");
} }
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) { } else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
addReplyStatus(c,"Background saving started"); addReplyStatus(c,"Background saving started");
......
...@@ -145,6 +145,7 @@ size_t rdbSavedObjectLen(robj *o); ...@@ -145,6 +145,7 @@ size_t rdbSavedObjectLen(robj *o);
robj *rdbLoadObject(int type, rio *rdb, robj *key); robj *rdbLoadObject(int type, rio *rdb, robj *key);
void backgroundSaveDoneHandler(int exitcode, int bysignal); void backgroundSaveDoneHandler(int exitcode, int bysignal);
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime); int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
robj *rdbLoadStringObject(rio *rdb); robj *rdbLoadStringObject(rio *rdb);
ssize_t rdbSaveStringObject(rio *rdb, robj *obj); ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len); ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment