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
...@@ -104,6 +104,7 @@ static struct config { ...@@ -104,6 +104,7 @@ static struct config {
int is_fetching_slots; int is_fetching_slots;
int is_updating_slots; int is_updating_slots;
int slots_last_update; int slots_last_update;
int enable_tracking;
/* Thread mutexes to be used as fallbacks by atomicvar.h */ /* Thread mutexes to be used as fallbacks by atomicvar.h */
pthread_mutex_t requests_issued_mutex; pthread_mutex_t requests_issued_mutex;
pthread_mutex_t requests_finished_mutex; pthread_mutex_t requests_finished_mutex;
...@@ -255,7 +256,7 @@ static redisConfig *getRedisConfig(const char *ip, int port, ...@@ -255,7 +256,7 @@ static redisConfig *getRedisConfig(const char *ip, int port,
goto fail; goto fail;
} }
if(config.auth){ if(config.auth) {
void *authReply = NULL; void *authReply = NULL;
redisAppendCommand(c, "AUTH %s", config.auth); redisAppendCommand(c, "AUTH %s", config.auth);
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail; if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
...@@ -633,6 +634,14 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) { ...@@ -633,6 +634,14 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
c->prefix_pending++; c->prefix_pending++;
} }
if (config.enable_tracking) {
char *buf = NULL;
int len = redisFormatCommand(&buf, "CLIENT TRACKING on");
c->obuf = sdscatlen(c->obuf, buf, len);
free(buf);
c->prefix_pending++;
}
/* If a DB number different than zero is selected, prefix our request /* If a DB number different than zero is selected, prefix our request
* buffer with the SELECT command, that will be discarded the first * buffer with the SELECT command, that will be discarded the first
* time the replies are received, so if the client is reused the * time the replies are received, so if the client is reused the
...@@ -1350,6 +1359,8 @@ int parseOptions(int argc, const char **argv) { ...@@ -1350,6 +1359,8 @@ int parseOptions(int argc, const char **argv) {
} else if (config.num_threads < 0) config.num_threads = 0; } else if (config.num_threads < 0) config.num_threads = 0;
} else if (!strcmp(argv[i],"--cluster")) { } else if (!strcmp(argv[i],"--cluster")) {
config.cluster_mode = 1; config.cluster_mode = 1;
} else if (!strcmp(argv[i],"--enable-tracking")) {
config.enable_tracking = 1;
} else if (!strcmp(argv[i],"--help")) { } else if (!strcmp(argv[i],"--help")) {
exit_status = 0; exit_status = 0;
goto usage; goto usage;
...@@ -1380,6 +1391,7 @@ usage: ...@@ -1380,6 +1391,7 @@ usage:
" --dbnum <db> SELECT the specified db number (default 0)\n" " --dbnum <db> SELECT the specified db number (default 0)\n"
" --threads <num> Enable multi-thread mode.\n" " --threads <num> Enable multi-thread mode.\n"
" --cluster Enable cluster mode.\n" " --cluster Enable cluster mode.\n"
" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n"
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n" " -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n" " -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n"
" Using this option the benchmark will expand the string __rand_int__\n" " Using this option the benchmark will expand the string __rand_int__\n"
...@@ -1504,6 +1516,7 @@ int main(int argc, const char **argv) { ...@@ -1504,6 +1516,7 @@ int main(int argc, const char **argv) {
config.is_fetching_slots = 0; config.is_fetching_slots = 0;
config.is_updating_slots = 0; config.is_updating_slots = 0;
config.slots_last_update = 0; config.slots_last_update = 0;
config.enable_tracking = 0;
i = parseOptions(argc,argv); i = parseOptions(argc,argv);
argc -= i; argc -= i;
...@@ -1540,7 +1553,10 @@ int main(int argc, const char **argv) { ...@@ -1540,7 +1553,10 @@ int main(int argc, const char **argv) {
if (node->name) printf("%s ", node->name); if (node->name) printf("%s ", node->name);
printf("%s:%d\n", node->ip, node->port); printf("%s:%d\n", node->ip, node->port);
node->redis_config = getRedisConfig(node->ip, node->port, NULL); node->redis_config = getRedisConfig(node->ip, node->port, NULL);
if (node->redis_config == NULL) exit(1); if (node->redis_config == NULL) {
fprintf(stderr, "WARN: could not fetch node CONFIG %s:%d\n",
node->ip, node->port);
}
} }
printf("\n"); printf("\n");
/* Automatically set thread number to node count if not specified /* Automatically set thread number to node count if not specified
...@@ -1550,7 +1566,8 @@ int main(int argc, const char **argv) { ...@@ -1550,7 +1566,8 @@ int main(int argc, const char **argv) {
} else { } else {
config.redis_config = config.redis_config =
getRedisConfig(config.hostip, config.hostport, config.hostsocket); getRedisConfig(config.hostip, config.hostport, config.hostsocket);
if (config.redis_config == NULL) exit(1); if (config.redis_config == NULL)
fprintf(stderr, "WARN: could not fetch server CONFIG\n");
} }
if (config.num_threads > 0) { if (config.num_threads > 0) {
......
...@@ -202,7 +202,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -202,7 +202,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
} }
expiretime = -1; expiretime = -1;
startLoading(fp); startLoadingFile(fp, rdbfilename);
while(1) { while(1) {
robj *key, *val; robj *key, *val;
...@@ -216,14 +216,16 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -216,14 +216,16 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
/* EXPIRETIME: load an expire associated with the next key /* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to * to load. Note that after loading an expire we need to
* load the actual type, and continue. */ * load the actual type, and continue. */
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr; 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. */
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE; rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
if ((expiretime = rdbLoadMillisecondTime(&rdb, rdbver)) == -1) goto eoferr; 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. */
...@@ -314,6 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -314,6 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
} }
if (closefile) fclose(fp); if (closefile) fclose(fp);
stopLoading();
return 0; return 0;
eoferr: /* unexpected end of file is handled here with a fatal exit */ eoferr: /* unexpected end of file is handled here with a fatal exit */
...@@ -324,6 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */ ...@@ -324,6 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
} }
err: err:
if (closefile) fclose(fp); if (closefile) fclose(fp);
stopLoading();
return 1; return 1;
} }
......
...@@ -218,6 +218,7 @@ static struct config { ...@@ -218,6 +218,7 @@ static struct config {
int hotkeys; int hotkeys;
int stdinarg; /* get last arg from stdin. (-x option) */ int stdinarg; /* get last arg from stdin. (-x option) */
char *auth; char *auth;
char *user;
int output; /* output mode, see OUTPUT_* defines */ int output; /* output mode, see OUTPUT_* defines */
sds mb_delim; sds mb_delim;
char prompt[128]; char prompt[128];
...@@ -230,6 +231,7 @@ static struct config { ...@@ -230,6 +231,7 @@ static struct config {
int verbose; int verbose;
clusterManagerCommand cluster_manager_command; clusterManagerCommand cluster_manager_command;
int no_auth_warning; int no_auth_warning;
int resp3;
} config; } config;
/* User preferences. */ /* User preferences. */
...@@ -728,8 +730,13 @@ static int cliAuth(void) { ...@@ -728,8 +730,13 @@ static int cliAuth(void) {
redisReply *reply; redisReply *reply;
if (config.auth == NULL) return REDIS_OK; if (config.auth == NULL) return REDIS_OK;
if (config.user == NULL)
reply = redisCommand(context,"AUTH %s",config.auth); reply = redisCommand(context,"AUTH %s",config.auth);
else
reply = redisCommand(context,"AUTH %s %s",config.user,config.auth);
if (reply != NULL) { if (reply != NULL) {
if (reply->type == REDIS_REPLY_ERROR)
fprintf(stderr,"Warning: AUTH failed\n");
freeReplyObject(reply); freeReplyObject(reply);
return REDIS_OK; return REDIS_OK;
} }
...@@ -751,6 +758,21 @@ static int cliSelect(void) { ...@@ -751,6 +758,21 @@ static int cliSelect(void) {
return REDIS_ERR; return REDIS_ERR;
} }
/* Select RESP3 mode if redis-cli was started with the -3 option. */
static int cliSwitchProto(void) {
redisReply *reply;
if (config.resp3 == 0) return REDIS_OK;
reply = redisCommand(context,"HELLO 3");
if (reply != NULL) {
int result = REDIS_OK;
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
freeReplyObject(reply);
return result;
}
return REDIS_ERR;
}
/* Connect to the server. It is possible to pass certain flags to the function: /* Connect to the server. It is possible to pass certain flags to the function:
* CC_FORCE: The connection is performed even if there is already * CC_FORCE: The connection is performed even if there is already
* a connected socket. * a connected socket.
...@@ -788,11 +810,13 @@ static int cliConnect(int flags) { ...@@ -788,11 +810,13 @@ static int cliConnect(int flags) {
* errors. */ * errors. */
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
/* Do AUTH and select the right DB. */ /* Do AUTH, select the right DB, switch to RESP3 if needed. */
if (cliAuth() != REDIS_OK) if (cliAuth() != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
if (cliSelect() != REDIS_OK) if (cliSelect() != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
if (cliSwitchProto() != REDIS_OK)
return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
} }
...@@ -819,10 +843,17 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) { ...@@ -819,10 +843,17 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
out = sdscatprintf(out,"(double) %s\n",r->str); out = sdscatprintf(out,"(double) %s\n",r->str);
break; break;
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB:
/* If you are producing output for the standard output we want /* If you are producing output for the standard output we want
* a more interesting output with quoted characters and so forth */ * a more interesting output with quoted characters and so forth,
* unless it's a verbatim string type. */
if (r->type == REDIS_REPLY_STRING) {
out = sdscatrepr(out,r->str,r->len); out = sdscatrepr(out,r->str,r->len);
out = sdscat(out,"\n"); out = sdscat(out,"\n");
} else {
out = sdscatlen(out,r->str,r->len);
out = sdscat(out,"\n");
}
break; break;
case REDIS_REPLY_NIL: case REDIS_REPLY_NIL:
out = sdscat(out,"(nil)\n"); out = sdscat(out,"(nil)\n");
...@@ -961,6 +992,7 @@ static sds cliFormatReplyRaw(redisReply *r) { ...@@ -961,6 +992,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
break; break;
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB:
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) { if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
/* The Lua debugger replies with arrays of simple (status) /* The Lua debugger replies with arrays of simple (status)
* strings. We colorize the output for more fun if this * strings. We colorize the output for more fun if this
...@@ -980,9 +1012,15 @@ static sds cliFormatReplyRaw(redisReply *r) { ...@@ -980,9 +1012,15 @@ static sds cliFormatReplyRaw(redisReply *r) {
out = sdscatlen(out,r->str,r->len); out = sdscatlen(out,r->str,r->len);
} }
break; break;
case REDIS_REPLY_BOOL:
out = sdscat(out,r->integer ? "(true)" : "(false)");
break;
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer); out = sdscatprintf(out,"%lld",r->integer);
break; break;
case REDIS_REPLY_DOUBLE:
out = sdscatprintf(out,"%s",r->str);
break;
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
for (i = 0; i < r->elements; i++) { for (i = 0; i < r->elements; i++) {
if (i > 0) out = sdscat(out,config.mb_delim); if (i > 0) out = sdscat(out,config.mb_delim);
...@@ -991,6 +1029,19 @@ static sds cliFormatReplyRaw(redisReply *r) { ...@@ -991,6 +1029,19 @@ static sds cliFormatReplyRaw(redisReply *r) {
sdsfree(tmp); sdsfree(tmp);
} }
break; break;
case REDIS_REPLY_MAP:
for (i = 0; i < r->elements; i += 2) {
if (i > 0) out = sdscat(out,config.mb_delim);
tmp = cliFormatReplyRaw(r->element[i]);
out = sdscatlen(out,tmp,sdslen(tmp));
sdsfree(tmp);
out = sdscatlen(out," ",1);
tmp = cliFormatReplyRaw(r->element[i+1]);
out = sdscatlen(out,tmp,sdslen(tmp));
sdsfree(tmp);
}
break;
default: default:
fprintf(stderr,"Unknown reply type: %d\n", r->type); fprintf(stderr,"Unknown reply type: %d\n", r->type);
exit(1); exit(1);
...@@ -1013,13 +1064,21 @@ static sds cliFormatReplyCSV(redisReply *r) { ...@@ -1013,13 +1064,21 @@ static sds cliFormatReplyCSV(redisReply *r) {
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer); out = sdscatprintf(out,"%lld",r->integer);
break; break;
case REDIS_REPLY_DOUBLE:
out = sdscatprintf(out,"%s",r->str);
break;
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB:
out = sdscatrepr(out,r->str,r->len); out = sdscatrepr(out,r->str,r->len);
break; break;
case REDIS_REPLY_NIL: case REDIS_REPLY_NIL:
out = sdscat(out,"NIL"); out = sdscat(out,"NULL");
break;
case REDIS_REPLY_BOOL:
out = sdscat(out,r->integer ? "true" : "false");
break; break;
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
for (i = 0; i < r->elements; i++) { for (i = 0; i < r->elements; i++) {
sds tmp = cliFormatReplyCSV(r->element[i]); sds tmp = cliFormatReplyCSV(r->element[i]);
out = sdscatlen(out,tmp,sdslen(tmp)); out = sdscatlen(out,tmp,sdslen(tmp));
...@@ -1213,7 +1272,8 @@ static int cliSendCommand(int argc, char **argv, long repeat) { ...@@ -1213,7 +1272,8 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) { if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
config.dbnum = atoi(argv[1]); config.dbnum = atoi(argv[1]);
cliRefreshPrompt(); cliRefreshPrompt();
} else if (!strcasecmp(command,"auth") && argc == 2) { } else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3))
{
cliSelect(); cliSelect();
} }
} }
...@@ -1296,8 +1356,12 @@ static int parseOptions(int argc, char **argv) { ...@@ -1296,8 +1356,12 @@ static int parseOptions(int argc, char **argv) {
config.dbnum = atoi(argv[++i]); config.dbnum = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-auth-warning")) { } else if (!strcmp(argv[i], "--no-auth-warning")) {
config.no_auth_warning = 1; config.no_auth_warning = 1;
} else if (!strcmp(argv[i],"-a") && !lastarg) { } else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
&& !lastarg)
{
config.auth = argv[++i]; config.auth = argv[++i];
} else if (!strcmp(argv[i],"--user") && !lastarg) {
config.user = argv[++i];
} else if (!strcmp(argv[i],"-u") && !lastarg) { } else if (!strcmp(argv[i],"-u") && !lastarg) {
parseRedisUri(argv[++i]); parseRedisUri(argv[++i]);
} else if (!strcmp(argv[i],"--raw")) { } else if (!strcmp(argv[i],"--raw")) {
...@@ -1439,6 +1503,8 @@ static int parseOptions(int argc, char **argv) { ...@@ -1439,6 +1503,8 @@ static int parseOptions(int argc, char **argv) {
printf("redis-cli %s\n", version); printf("redis-cli %s\n", version);
sdsfree(version); sdsfree(version);
exit(0); exit(0);
} else if (!strcmp(argv[i],"-3")) {
config.resp3 = 1;
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') { } else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
if (config.cluster_manager_command.argc == 0) { if (config.cluster_manager_command.argc == 0) {
int j = i + 1; int j = i + 1;
...@@ -1514,11 +1580,14 @@ static void usage(void) { ...@@ -1514,11 +1580,14 @@ static void usage(void) {
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n" " You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
" variable to pass this password more safely\n" " variable to pass this password more safely\n"
" (if both are used, this argument takes predecence).\n" " (if both are used, this argument takes predecence).\n"
" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
" -pass <password> Alias of -a for consistency with the new --user option.\n"
" -u <uri> Server URI.\n" " -u <uri> Server URI.\n"
" -r <repeat> Execute specified command N times.\n" " -r <repeat> Execute specified command N times.\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n" " -i <interval> When -r is used, waits <interval> seconds per command.\n"
" It is possible to specify sub-second times like -i 0.1.\n" " It is possible to specify sub-second times like -i 0.1.\n"
" -n <db> Database number.\n" " -n <db> Database number.\n"
" -3 Start session in RESP3 protocol mode.\n"
" -x Read last argument from STDIN.\n" " -x Read last argument from STDIN.\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n" " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n" " -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
...@@ -1533,7 +1602,9 @@ static void usage(void) { ...@@ -1533,7 +1602,9 @@ static void usage(void) {
" --csv is specified, or if you redirect the output to a non\n" " --csv is specified, or if you redirect the output to a non\n"
" TTY, it samples the latency for 1 second (you can use\n" " TTY, it samples the latency for 1 second (you can use\n"
" -i to change the interval), then produces a single output\n" " -i to change the interval), then produces a single output\n"
" and exits.\n" " and exits.\n",version);
fprintf(stderr,
" --latency-history Like --latency but tracking latency changes over time.\n" " --latency-history Like --latency but tracking latency changes over time.\n"
" Default time interval is 15 sec. Change it using -i.\n" " Default time interval is 15 sec. Change it using -i.\n"
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n" " --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
...@@ -1568,7 +1639,7 @@ static void usage(void) { ...@@ -1568,7 +1639,7 @@ static void usage(void) {
" --help Output this help and exit.\n" " --help Output this help and exit.\n"
" --version Output version and exit.\n" " --version Output version and exit.\n"
"\n", "\n",
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT); REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
/* Using another fprintf call to avoid -Woverlength-strings compile warning */ /* Using another fprintf call to avoid -Woverlength-strings compile warning */
fprintf(stderr, fprintf(stderr,
"Cluster Manager Commands:\n" "Cluster Manager Commands:\n"
...@@ -2350,7 +2421,12 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) { ...@@ -2350,7 +2421,12 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
* errors. */ * errors. */
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL); anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
if (config.auth) { if (config.auth) {
redisReply *reply = redisCommand(node->context,"AUTH %s",config.auth); redisReply *reply;
if (config.user == NULL)
reply = redisCommand(node->context,"AUTH %s", config.auth);
else
reply = redisCommand(node->context,"AUTH %s %s",
config.user,config.auth);
int ok = clusterManagerCheckRedisReply(node, reply, NULL); int ok = clusterManagerCheckRedisReply(node, reply, NULL);
if (reply != NULL) freeReplyObject(reply); if (reply != NULL) freeReplyObject(reply);
if (!ok) return 0; if (!ok) return 0;
...@@ -3222,7 +3298,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source, ...@@ -3222,7 +3298,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
redisReply *entry = reply->element[i]; redisReply *entry = reply->element[i];
size_t idx = i + offset; size_t idx = i + offset;
assert(entry->type == REDIS_REPLY_STRING); assert(entry->type == REDIS_REPLY_STRING);
argv[idx] = (char *) sdsnew(entry->str); argv[idx] = (char *) sdsnewlen(entry->str, entry->len);
argv_len[idx] = entry->len; argv_len[idx] = entry->len;
if (dots) dots[i] = '.'; if (dots) dots[i] = '.';
} }
...@@ -6724,6 +6800,7 @@ static void pipeMode(void) { ...@@ -6724,6 +6800,7 @@ static void pipeMode(void) {
/* Handle the readable state: we can read replies from the server. */ /* Handle the readable state: we can read replies from the server. */
if (mask & AE_READABLE) { if (mask & AE_READABLE) {
ssize_t nread; ssize_t nread;
int read_error = 0;
/* Read from socket and feed the hiredis reader. */ /* Read from socket and feed the hiredis reader. */
do { do {
...@@ -6731,7 +6808,8 @@ static void pipeMode(void) { ...@@ -6731,7 +6808,8 @@ static void pipeMode(void) {
if (nread == -1 && errno != EAGAIN && errno != EINTR) { if (nread == -1 && errno != EAGAIN && errno != EINTR) {
fprintf(stderr, "Error reading from the server: %s\n", fprintf(stderr, "Error reading from the server: %s\n",
strerror(errno)); strerror(errno));
exit(1); read_error = 1;
break;
} }
if (nread > 0) { if (nread > 0) {
redisReaderFeed(reader,ibuf,nread); redisReaderFeed(reader,ibuf,nread);
...@@ -6764,6 +6842,11 @@ static void pipeMode(void) { ...@@ -6764,6 +6842,11 @@ static void pipeMode(void) {
freeReplyObject(reply); freeReplyObject(reply);
} }
} while(reply); } while(reply);
/* Abort on read errors. We abort here because it is important
* to consume replies even after a read error: this way we can
* show a potential problem to the user. */
if (read_error) exit(1);
} }
/* Handle the writable state: we can send protocol to the server. */ /* Handle the writable state: we can send protocol to the server. */
...@@ -7671,6 +7754,7 @@ int main(int argc, char **argv) { ...@@ -7671,6 +7754,7 @@ int main(int argc, char **argv) {
config.hotkeys = 0; config.hotkeys = 0;
config.stdinarg = 0; config.stdinarg = 0;
config.auth = NULL; config.auth = NULL;
config.user = NULL;
config.eval = NULL; config.eval = NULL;
config.eval_ldb = 0; config.eval_ldb = 0;
config.eval_ldb_end = 0; config.eval_ldb_end = 0;
......
...@@ -87,6 +87,8 @@ ...@@ -87,6 +87,8 @@
#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11) #define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11)
/* The command was sent over the replication link. */ /* The command was sent over the replication link. */
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12) #define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
/* Redis is currently loading either from AOF or RDB. */
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */ #define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
...@@ -127,6 +129,10 @@ ...@@ -127,6 +129,10 @@
#define REDISMODULE_NOT_USED(V) ((void) V) #define REDISMODULE_NOT_USED(V) ((void) V)
/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
/* This type represents a timer handle, and is returned when a timer is /* This type represents a timer handle, and is returned when a timer is
* registered and used in order to invalidate a timer. It's just a 64 bit * registered and used in order to invalidate a timer. It's just a 64 bit
* number, because this is how each timer is represented inside the radix tree * number, because this is how each timer is represented inside the radix tree
...@@ -138,6 +144,9 @@ typedef uint64_t RedisModuleTimerID; ...@@ -138,6 +144,9 @@ typedef uint64_t RedisModuleTimerID;
/* Do filter RedisModule_Call() commands initiated by module itself. */ /* Do filter RedisModule_Call() commands initiated by module itself. */
#define REDISMODULE_CMDFILTER_NOSELF (1<<0) #define REDISMODULE_CMDFILTER_NOSELF (1<<0)
/* Declare that the module can handle errors with RedisModule_SetModuleOptions. */
#define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0)
/* ------------------------- End of common defines ------------------------ */ /* ------------------------- End of common defines ------------------------ */
#ifndef REDISMODULE_CORE #ifndef REDISMODULE_CORE
...@@ -158,12 +167,15 @@ typedef struct RedisModuleDict RedisModuleDict; ...@@ -158,12 +167,15 @@ typedef struct RedisModuleDict RedisModuleDict;
typedef struct RedisModuleDictIter RedisModuleDictIter; typedef struct RedisModuleDictIter RedisModuleDictIter;
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx; typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter; typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc); typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value); typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
...@@ -171,8 +183,10 @@ typedef void (*RedisModuleTypeFreeFunc)(void *value); ...@@ -171,8 +183,10 @@ typedef void (*RedisModuleTypeFreeFunc)(void *value);
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter); typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
#define REDISMODULE_TYPE_METHOD_VERSION 1 #define REDISMODULE_TYPE_METHOD_VERSION 2
typedef struct RedisModuleTypeMethods { typedef struct RedisModuleTypeMethods {
uint64_t version; uint64_t version;
RedisModuleTypeLoadFunc rdb_load; RedisModuleTypeLoadFunc rdb_load;
...@@ -181,6 +195,9 @@ typedef struct RedisModuleTypeMethods { ...@@ -181,6 +195,9 @@ typedef struct RedisModuleTypeMethods {
RedisModuleTypeMemUsageFunc mem_usage; RedisModuleTypeMemUsageFunc mem_usage;
RedisModuleTypeDigestFunc digest; RedisModuleTypeDigestFunc digest;
RedisModuleTypeFreeFunc free; RedisModuleTypeFreeFunc free;
RedisModuleTypeAuxLoadFunc aux_load;
RedisModuleTypeAuxSaveFunc aux_save;
int aux_save_triggers;
} RedisModuleTypeMethods; } RedisModuleTypeMethods;
#define REDISMODULE_GET_API(name) \ #define REDISMODULE_GET_API(name) \
...@@ -226,6 +243,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, ...@@ -226,6 +243,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx,
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
...@@ -268,6 +286,8 @@ RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx ...@@ -268,6 +286,8 @@ RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options);
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
...@@ -283,6 +303,7 @@ void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value) ...@@ -283,6 +303,7 @@ void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value)
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
...@@ -314,6 +335,15 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ct ...@@ -314,6 +335,15 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ct
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb);
int REDISMODULE_API_FUNC(RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name);
int REDISMODULE_API_FUNC(RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name);
int REDISMODULE_API_FUNC(RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
/* Experimental APIs */ /* Experimental APIs */
#ifdef REDISMODULE_EXPERIMENTAL_API #ifdef REDISMODULE_EXPERIMENTAL_API
...@@ -354,6 +384,9 @@ const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(R ...@@ -354,6 +384,9 @@ const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(R
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg); int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg); int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos); int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
#endif #endif
/* This is included inline inside each Redis module. */ /* This is included inline inside each Redis module. */
...@@ -376,6 +409,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -376,6 +409,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ReplyWithArray); REDISMODULE_GET_API(ReplyWithArray);
REDISMODULE_GET_API(ReplySetArrayLength); REDISMODULE_GET_API(ReplySetArrayLength);
REDISMODULE_GET_API(ReplyWithStringBuffer); REDISMODULE_GET_API(ReplyWithStringBuffer);
REDISMODULE_GET_API(ReplyWithCString);
REDISMODULE_GET_API(ReplyWithString); REDISMODULE_GET_API(ReplyWithString);
REDISMODULE_GET_API(ReplyWithNull); REDISMODULE_GET_API(ReplyWithNull);
REDISMODULE_GET_API(ReplyWithCallReply); REDISMODULE_GET_API(ReplyWithCallReply);
...@@ -440,6 +474,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -440,6 +474,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ModuleTypeSetValue); REDISMODULE_GET_API(ModuleTypeSetValue);
REDISMODULE_GET_API(ModuleTypeGetType); REDISMODULE_GET_API(ModuleTypeGetType);
REDISMODULE_GET_API(ModuleTypeGetValue); REDISMODULE_GET_API(ModuleTypeGetValue);
REDISMODULE_GET_API(IsIOError);
REDISMODULE_GET_API(SetModuleOptions);
REDISMODULE_GET_API(SaveUnsigned); REDISMODULE_GET_API(SaveUnsigned);
REDISMODULE_GET_API(LoadUnsigned); REDISMODULE_GET_API(LoadUnsigned);
REDISMODULE_GET_API(SaveSigned); REDISMODULE_GET_API(SaveSigned);
...@@ -455,6 +491,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -455,6 +491,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(EmitAOF); REDISMODULE_GET_API(EmitAOF);
REDISMODULE_GET_API(Log); REDISMODULE_GET_API(Log);
REDISMODULE_GET_API(LogIOError); REDISMODULE_GET_API(LogIOError);
REDISMODULE_GET_API(_Assert);
REDISMODULE_GET_API(StringAppendBuffer); REDISMODULE_GET_API(StringAppendBuffer);
REDISMODULE_GET_API(RetainString); REDISMODULE_GET_API(RetainString);
REDISMODULE_GET_API(StringCompare); REDISMODULE_GET_API(StringCompare);
...@@ -486,6 +523,15 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -486,6 +523,15 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(DictPrev); REDISMODULE_GET_API(DictPrev);
REDISMODULE_GET_API(DictCompare); REDISMODULE_GET_API(DictCompare);
REDISMODULE_GET_API(DictCompareC); REDISMODULE_GET_API(DictCompareC);
REDISMODULE_GET_API(RegisterInfoFunc);
REDISMODULE_GET_API(InfoAddSection);
REDISMODULE_GET_API(InfoBeginDictField);
REDISMODULE_GET_API(InfoEndDictField);
REDISMODULE_GET_API(InfoAddFieldString);
REDISMODULE_GET_API(InfoAddFieldCString);
REDISMODULE_GET_API(InfoAddFieldDouble);
REDISMODULE_GET_API(InfoAddFieldLongLong);
REDISMODULE_GET_API(InfoAddFieldULongLong);
#ifdef REDISMODULE_EXPERIMENTAL_API #ifdef REDISMODULE_EXPERIMENTAL_API
REDISMODULE_GET_API(GetThreadSafeContext); REDISMODULE_GET_API(GetThreadSafeContext);
...@@ -524,6 +570,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -524,6 +570,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(CommandFilterArgInsert); REDISMODULE_GET_API(CommandFilterArgInsert);
REDISMODULE_GET_API(CommandFilterArgReplace); REDISMODULE_GET_API(CommandFilterArgReplace);
REDISMODULE_GET_API(CommandFilterArgDelete); REDISMODULE_GET_API(CommandFilterArgDelete);
REDISMODULE_GET_API(Fork);
REDISMODULE_GET_API(ExitFromChild);
REDISMODULE_GET_API(KillForkChild);
#endif #endif
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
...@@ -531,6 +580,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -531,6 +580,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
return REDISMODULE_OK; return REDISMODULE_OK;
} }
#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1)))
#else #else
/* Things only defined for the modules core, not exported to modules /* Things only defined for the modules core, not exported to modules
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
* files using this functions. */ * files using this functions. */
#include <string.h> #include <string.h>
#include <stdio.h>
#include "release.h" #include "release.h"
#include "version.h" #include "version.h"
...@@ -50,3 +51,16 @@ uint64_t redisBuildId(void) { ...@@ -50,3 +51,16 @@ uint64_t redisBuildId(void) {
return crc64(0,(unsigned char*)buildid,strlen(buildid)); return crc64(0,(unsigned char*)buildid,strlen(buildid));
} }
/* Return a cached value of the build string in order to avoid recomputing
* and converting it in hex every time: this string is shown in the INFO
* output that should be fast. */
char *redisBuildIdString(void) {
static char buf[32];
static int cached = 0;
if (!cached) {
snprintf(buf,sizeof(buf),"%llx",(unsigned long long) redisBuildId());
cached = 1;
}
return buf;
}
...@@ -751,11 +751,11 @@ void syncCommand(client *c) { ...@@ -751,11 +751,11 @@ void syncCommand(client *c) {
/* Target is disk (or the slave is not capable of supporting /* Target is disk (or the slave is not capable of supporting
* diskless replication) and we don't have a BGSAVE in progress, * diskless replication) and we don't have a BGSAVE in progress,
* let's start one. */ * let's start one. */
if (server.aof_child_pid == -1) { if (!hasActiveChildProcess()) {
startBgsaveForReplication(c->slave_capa); startBgsaveForReplication(c->slave_capa);
} else { } else {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"No BGSAVE in progress, but an AOF rewrite is active. " "No BGSAVE in progress, but another BG operation is active. "
"BGSAVE for replication delayed"); "BGSAVE for replication delayed");
} }
} }
...@@ -823,7 +823,9 @@ void replconfCommand(client *c) { ...@@ -823,7 +823,9 @@ void replconfCommand(client *c) {
c->repl_ack_time = server.unixtime; c->repl_ack_time = server.unixtime;
/* If this was a diskless replication, we need to really put /* If this was a diskless replication, we need to really put
* the slave online when the first ACK is received (which * the slave online when the first ACK is received (which
* confirms slave is online and ready to get more data). */ * confirms slave is online and ready to get more data). This
* allows for simpler and less CPU intensive EOF detection
* when streaming RDB files. */
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE) if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
putSlaveOnline(c); putSlaveOnline(c);
/* Note: this command does not reply anything! */ /* Note: this command does not reply anything! */
...@@ -842,18 +844,20 @@ void replconfCommand(client *c) { ...@@ -842,18 +844,20 @@ void replconfCommand(client *c) {
addReply(c,shared.ok); addReply(c,shared.ok);
} }
/* This function puts a slave in the online state, and should be called just /* This function puts a replica in the online state, and should be called just
* after a slave received the RDB file for the initial synchronization, and * after a replica received the RDB file for the initial synchronization, and
* we are finally ready to send the incremental stream of commands. * we are finally ready to send the incremental stream of commands.
* *
* It does a few things: * It does a few things:
* *
* 1) Put the slave in ONLINE state (useless when the function is called * 1) Put the slave in ONLINE state. Note that the function may also be called
* because state is already ONLINE but repl_put_online_on_ack is true). * for a replicas that are already in ONLINE state, but having the flag
* repl_put_online_on_ack set to true: we still have to install the write
* handler in that case. This function will take care of that.
* 2) Make sure the writable event is re-installed, since calling the SYNC * 2) Make sure the writable event is re-installed, since calling the SYNC
* command disables it, so that we can accumulate output buffer without * command disables it, so that we can accumulate output buffer without
* sending it to the slave. * sending it to the replica.
* 3) Update the count of good slaves. */ * 3) Update the count of "good replicas". */
void putSlaveOnline(client *slave) { void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE; slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0; slave->repl_put_online_on_ack = 0;
...@@ -965,11 +969,31 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) { ...@@ -965,11 +969,31 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming", "Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
replicationGetSlaveName(slave)); replicationGetSlaveName(slave));
/* Note: we wait for a REPLCONF ACK message from slave in /* Note: we wait for a REPLCONF ACK message from the replica in
* order to really put it online (install the write handler * order to really put it online (install the write handler
* so that the accumulated data can be transferred). However * so that the accumulated data can be transferred). However
* we change the replication state ASAP, since our slave * we change the replication state ASAP, since our slave
* is technically online now. */ * is technically online now.
*
* So things work like that:
*
* 1. We end trasnferring the RDB file via socket.
* 2. The replica is put ONLINE but the write handler
* is not installed.
* 3. The replica however goes really online, and pings us
* back via REPLCONF ACK commands.
* 4. Now we finally install the write handler, and send
* the buffers accumulated so far to the replica.
*
* But why we do that? Because the replica, when we stream
* the RDB directly via the socket, must detect the RDB
* EOF (end of file), that is a special random string at the
* end of the RDB (for streamed RDBs we don't know the length
* in advance). Detecting such final EOF string is much
* simpler and less CPU intensive if no more data is sent
* after such final EOF. So we don't want to glue the end of
* the RDB trasfer with the start of the other replication
* data. */
slave->replstate = SLAVE_STATE_ONLINE; slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 1; slave->repl_put_online_on_ack = 1;
slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */ slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
...@@ -1113,11 +1137,72 @@ void restartAOFAfterSYNC() { ...@@ -1113,11 +1137,72 @@ void restartAOFAfterSYNC() {
} }
} }
static int useDisklessLoad() {
/* compute boolean decision to use diskless load */
int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
(server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
/* Check all modules handle read errors, otherwise it's not safe to use diskless load. */
if (enabled && !moduleAllDatatypesHandleErrors()) {
serverLog(LL_WARNING,
"Skipping diskless-load because there are modules that don't handle read errors.");
enabled = 0;
}
return enabled;
}
/* Helper function for readSyncBulkPayload() to make backups of the current
* DBs before socket-loading the new ones. The backups may be restored later
* or freed by disklessLoadRestoreBackups(). */
redisDb *disklessLoadMakeBackups(void) {
redisDb *backups = zmalloc(sizeof(redisDb)*server.dbnum);
for (int i=0; i<server.dbnum; i++) {
backups[i] = server.db[i];
server.db[i].dict = dictCreate(&dbDictType,NULL);
server.db[i].expires = dictCreate(&keyptrDictType,NULL);
}
return backups;
}
/* Helper function for readSyncBulkPayload(): when replica-side diskless
* database loading is used, Redis makes a backup of the existing databases
* before loading the new ones from the socket.
*
* If the socket loading went wrong, we want to restore the old backups
* into the server databases. This function does just that in the case
* the 'restore' argument (the number of DBs to replace) is non-zero.
*
* When instead the loading succeeded we want just to free our old backups,
* in that case the funciton will do just that when 'restore' is 0. */
void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags)
{
if (restore) {
/* Restore. */
emptyDbGeneric(server.db,-1,empty_db_flags,replicationEmptyDbCallback);
for (int i=0; i<server.dbnum; i++) {
dictRelease(server.db[i].dict);
dictRelease(server.db[i].expires);
server.db[i] = backup[i];
}
} else {
/* Delete. */
emptyDbGeneric(backup,-1,empty_db_flags,replicationEmptyDbCallback);
for (int i=0; i<server.dbnum; i++) {
dictRelease(backup[i].dict);
dictRelease(backup[i].expires);
}
}
zfree(backup);
}
/* Asynchronously read the SYNC payload we receive from a master */ /* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
char buf[4096]; char buf[4096];
ssize_t nread, readlen, nwritten; ssize_t nread, readlen, nwritten;
int use_diskless_load;
redisDb *diskless_load_backup = NULL;
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
EMPTYDB_NO_FLAGS;
off_t left; off_t left;
UNUSED(el); UNUSED(el);
UNUSED(privdata); UNUSED(privdata);
...@@ -1173,18 +1258,23 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1173,18 +1258,23 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* at the next call. */ * at the next call. */
server.repl_transfer_size = 0; server.repl_transfer_size = 0;
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"MASTER <-> REPLICA sync: receiving streamed RDB from master"); "MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF %s",
useDisklessLoad()? "to parser":"to disk");
} else { } else {
usemark = 0; usemark = 0;
server.repl_transfer_size = strtol(buf+1,NULL,10); server.repl_transfer_size = strtol(buf+1,NULL,10);
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"MASTER <-> REPLICA sync: receiving %lld bytes from master", "MASTER <-> REPLICA sync: receiving %lld bytes from master %s",
(long long) server.repl_transfer_size); (long long) server.repl_transfer_size,
useDisklessLoad()? "to parser":"to disk");
} }
return; return;
} }
/* Read bulk data */ use_diskless_load = useDisklessLoad();
if (!use_diskless_load) {
/* Read the data from the socket, store it to a file and search
* for the EOF. */
if (usemark) { if (usemark) {
readlen = sizeof(buf); readlen = sizeof(buf);
} else { } else {
...@@ -1206,20 +1296,28 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1206,20 +1296,28 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
int eof_reached = 0; int eof_reached = 0;
if (usemark) { if (usemark) {
/* Update the last bytes array, and check if it matches our delimiter.*/ /* Update the last bytes array, and check if it matches our
* delimiter. */
if (nread >= CONFIG_RUN_ID_SIZE) { if (nread >= CONFIG_RUN_ID_SIZE) {
memcpy(lastbytes,buf+nread-CONFIG_RUN_ID_SIZE,CONFIG_RUN_ID_SIZE); memcpy(lastbytes,buf+nread-CONFIG_RUN_ID_SIZE,
CONFIG_RUN_ID_SIZE);
} else { } else {
int rem = CONFIG_RUN_ID_SIZE-nread; int rem = CONFIG_RUN_ID_SIZE-nread;
memmove(lastbytes,lastbytes+nread,rem); memmove(lastbytes,lastbytes+nread,rem);
memcpy(lastbytes+rem,buf,nread); memcpy(lastbytes+rem,buf,nread);
} }
if (memcmp(lastbytes,eofmark,CONFIG_RUN_ID_SIZE) == 0) eof_reached = 1; if (memcmp(lastbytes,eofmark,CONFIG_RUN_ID_SIZE) == 0)
eof_reached = 1;
} }
/* Update the last I/O time for the replication transfer (used in
* order to detect timeouts during replication), and write what we
* got from the socket to the dump file on disk. */
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) { if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) {
serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> REPLICA synchronization: %s", serverLog(LL_WARNING,
"Write error or short write writing to the DB dump file "
"needed for MASTER <-> REPLICA synchronization: %s",
(nwritten == -1) ? strerror(errno) : "short write"); (nwritten == -1) ? strerror(errno) : "short write");
goto error; goto error;
} }
...@@ -1230,14 +1328,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1230,14 +1328,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
if (ftruncate(server.repl_transfer_fd, if (ftruncate(server.repl_transfer_fd,
server.repl_transfer_read - CONFIG_RUN_ID_SIZE) == -1) server.repl_transfer_read - CONFIG_RUN_ID_SIZE) == -1)
{ {
serverLog(LL_WARNING,"Error truncating the RDB file received from the master for SYNC: %s", strerror(errno)); serverLog(LL_WARNING,
"Error truncating the RDB file received from the master "
"for SYNC: %s", strerror(errno));
goto error; goto error;
} }
} }
/* Sync data on disk from time to time, otherwise at the end of the transfer /* Sync data on disk from time to time, otherwise at the end of the
* we may suffer a big delay as the memory buffers are copied into the * transfer we may suffer a big delay as the memory buffers are copied
* actual disk. */ * into the actual disk. */
if (server.repl_transfer_read >= if (server.repl_transfer_read >=
server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC) server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
{ {
...@@ -1254,9 +1354,106 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1254,9 +1354,106 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
eof_reached = 1; eof_reached = 1;
} }
if (eof_reached) { /* If the transfer is yet not complete, we need to read more, so
int aof_is_enabled = server.aof_state != AOF_OFF; * return ASAP and wait for the handler to be called again. */
if (!eof_reached) return;
}
/* We reach this point in one of the following cases:
*
* 1. The replica is using diskless replication, that is, it reads data
* directly from the socket to the Redis memory, without using
* a temporary RDB file on disk. In that case we just block and
* read everything from the socket.
*
* 2. Or when we are done reading from the socket to the RDB file, in
* such case we want just to read the RDB file in memory. */
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
/* We need to stop any AOF rewriting child before flusing and parsing
* the RDB, otherwise we'll create a copy-on-write disaster. */
if (server.aof_state != AOF_OFF) stopAppendOnly();
signalFlushedDb(-1);
/* When diskless RDB loading is used by replicas, it may be configured
* in order to save the current DB instead of throwing it away,
* so that we can restore it in case of failed transfer. */
if (use_diskless_load &&
server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB)
{
diskless_load_backup = disklessLoadMakeBackups();
} else {
emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
}
/* Before loading the DB into memory we need to delete the readable
* handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (use_diskless_load) {
rio rdb;
rioInitWithFd(&rdb,fd,server.repl_transfer_size);
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */
anetBlock(NULL,fd);
anetRecvTimeout(NULL,fd,server.repl_timeout*1000);
startLoading(server.repl_transfer_size);
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
/* RDB loading failed. */
stopLoading();
serverLog(LL_WARNING,
"Failed trying to load the MASTER synchronization DB "
"from socket");
cancelReplicationHandshake();
rioFreeFd(&rdb, NULL);
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
/* Restore the backed up databases. */
disklessLoadRestoreBackups(diskless_load_backup,1,
empty_db_flags);
} else {
/* Remove the half-loaded data in case we started with
* an empty replica. */
emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
}
/* Note that there's no point in restarting the AOF on SYNC
* failure, it'll be restarted when sync succeeds or the replica
* gets promoted. */
return;
}
stopLoading();
/* RDB loading succeeded if we reach this point. */
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
/* Delete the backup databases we created before starting to load
* the new RDB. Now the RDB was loaded with success so the old
* data is useless. */
disklessLoadRestoreBackups(diskless_load_backup,0,empty_db_flags);
}
/* Verify the end mark is correct. */
if (usemark) {
if (!rioRead(&rdb,buf,CONFIG_RUN_ID_SIZE) ||
memcmp(buf,eofmark,CONFIG_RUN_ID_SIZE) != 0)
{
serverLog(LL_WARNING,"Replication stream EOF marker is broken");
cancelReplicationHandshake();
rioFreeFd(&rdb, NULL);
return;
}
}
/* Cleanup and restore the socket to the original state to continue
* with the normal replication. */
rioFreeFd(&rdb, NULL);
anetNonBlock(NULL,fd);
anetRecvTimeout(NULL,fd,0);
} else {
/* Ensure background save doesn't overwrite synced data */ /* Ensure background save doesn't overwrite synced data */
if (server.rdb_child_pid != -1) { if (server.rdb_child_pid != -1) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
...@@ -1269,59 +1466,53 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1269,59 +1466,53 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) { if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
serverLog(LL_WARNING,"Failed trying to rename the temp DB into %s in MASTER <-> REPLICA synchronization: %s", serverLog(LL_WARNING,
"Failed trying to rename the temp DB into %s in "
"MASTER <-> REPLICA synchronization: %s",
server.rdb_filename, strerror(errno)); server.rdb_filename, strerror(errno));
cancelReplicationHandshake(); cancelReplicationHandshake();
return; return;
} }
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
/* We need to stop any AOFRW fork before flusing and parsing
* RDB, otherwise we'll create a copy-on-write disaster. */
if(aof_is_enabled) stopAppendOnly();
signalFlushedDb(-1);
emptyDb(
-1,
server.repl_slave_lazy_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS,
replicationEmptyDbCallback);
/* Before loading the DB into memory we need to delete the readable
* handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) { if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
serverLog(LL_WARNING,"Failed trying to load the MASTER synchronization DB from disk"); serverLog(LL_WARNING,
"Failed trying to load the MASTER synchronization "
"DB from disk");
cancelReplicationHandshake(); cancelReplicationHandshake();
/* Re-enable the AOF if we disabled it earlier, in order to restore /* Note that there's no point in restarting the AOF on sync failure,
* the original configuration. */ it'll be restarted when sync succeeds or replica promoted. */
if (aof_is_enabled) restartAOFAfterSYNC();
return; return;
} }
/* Final setup of the connected slave <- master link */
/* Cleanup. */
zfree(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile);
close(server.repl_transfer_fd); close(server.repl_transfer_fd);
server.repl_transfer_fd = -1;
server.repl_transfer_tmpfile = NULL;
}
/* Final setup of the connected slave <- master link */
replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db); replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db);
server.repl_state = REPL_STATE_CONNECTED; server.repl_state = REPL_STATE_CONNECTED;
server.repl_down_since = 0; server.repl_down_since = 0;
/* After a full resynchroniziation we use the replication ID and /* After a full resynchroniziation we use the replication ID and
* offset of the master. The secondary ID / offset are cleared since * offset of the master. The secondary ID / offset are cleared since
* we are starting a new history. */ * we are starting a new history. */
memcpy(server.replid,server.master->replid,sizeof(server.replid)); memcpy(server.replid,server.master->replid,sizeof(server.replid));
server.master_repl_offset = server.master->reploff; server.master_repl_offset = server.master->reploff;
clearReplicationId2(); clearReplicationId2();
/* Let's create the replication backlog if needed. Slaves need to /* Let's create the replication backlog if needed. Slaves need to
* accumulate the backlog regardless of the fact they have sub-slaves * accumulate the backlog regardless of the fact they have sub-slaves
* or not, in order to behave correctly if they are promoted to * or not, in order to behave correctly if they are promoted to
* masters after a failover. */ * masters after a failover. */
if (server.repl_backlog == NULL) createReplicationBacklog(); if (server.repl_backlog == NULL) createReplicationBacklog();
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success"); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
/* Restart the AOF subsystem now that we finished the sync. This /* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending * will trigger an AOF rewrite, and when done will start appending
* to the new file. */ * to the new file. */
if (aof_is_enabled) restartAOFAfterSYNC(); if (server.aof_enabled) restartAOFAfterSYNC();
}
return; return;
error: error:
...@@ -1845,6 +2036,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1845,6 +2036,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
/* Prepare a suitable temp file for bulk transfer */ /* Prepare a suitable temp file for bulk transfer */
if (!useDisklessLoad()) {
while(maxtries--) { while(maxtries--) {
snprintf(tmpfile,256, snprintf(tmpfile,256,
"temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid()); "temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
...@@ -1856,6 +2048,9 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1856,6 +2048,9 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> REPLICA synchronization: %s",strerror(errno)); serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> REPLICA synchronization: %s",strerror(errno));
goto error; goto error;
} }
server.repl_transfer_tmpfile = zstrdup(tmpfile);
server.repl_transfer_fd = dfd;
}
/* Setup the non blocking download of the bulk file. */ /* Setup the non blocking download of the bulk file. */
if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL) if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL)
...@@ -1871,15 +2066,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1871,15 +2066,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
server.repl_transfer_size = -1; server.repl_transfer_size = -1;
server.repl_transfer_read = 0; server.repl_transfer_read = 0;
server.repl_transfer_last_fsync_off = 0; server.repl_transfer_last_fsync_off = 0;
server.repl_transfer_fd = dfd;
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_tmpfile = zstrdup(tmpfile);
return; return;
error: error:
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE); aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
if (dfd != -1) close(dfd); if (dfd != -1) close(dfd);
close(fd); close(fd);
if (server.repl_transfer_fd != -1)
close(server.repl_transfer_fd);
if (server.repl_transfer_tmpfile)
zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
server.repl_transfer_s = -1; server.repl_transfer_s = -1;
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
return; return;
...@@ -1933,9 +2132,13 @@ void undoConnectWithMaster(void) { ...@@ -1933,9 +2132,13 @@ void undoConnectWithMaster(void) {
void replicationAbortSyncTransfer(void) { void replicationAbortSyncTransfer(void) {
serverAssert(server.repl_state == REPL_STATE_TRANSFER); serverAssert(server.repl_state == REPL_STATE_TRANSFER);
undoConnectWithMaster(); undoConnectWithMaster();
if (server.repl_transfer_fd!=-1) {
close(server.repl_transfer_fd); close(server.repl_transfer_fd);
unlink(server.repl_transfer_tmpfile); unlink(server.repl_transfer_tmpfile);
zfree(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
}
} }
/* This function aborts a non blocking replication attempt if there is one /* This function aborts a non blocking replication attempt if there is one
...@@ -2045,6 +2248,9 @@ void replicaofCommand(client *c) { ...@@ -2045,6 +2248,9 @@ void replicaofCommand(client *c) {
serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')", serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')",
client); client);
sdsfree(client); sdsfree(client);
/* Restart the AOF subsystem in case we shut it down during a sync when
* we were still a slave. */
if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
} }
} else { } else {
long port; long port;
...@@ -2724,7 +2930,7 @@ void replicationCron(void) { ...@@ -2724,7 +2930,7 @@ void replicationCron(void) {
* In case of diskless replication, we make sure to wait the specified * In case of diskless replication, we make sure to wait the specified
* number of seconds (according to configuration) so that other slaves * number of seconds (according to configuration) so that other slaves
* have the time to arrive before we start streaming. */ * have the time to arrive before we start streaming. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) { if (!hasActiveChildProcess()) {
time_t idle, max_idle = 0; time_t idle, max_idle = 0;
int slaves_waiting = 0; int slaves_waiting = 0;
int mincapa = -1; int mincapa = -1;
......
...@@ -92,6 +92,7 @@ static const rio rioBufferIO = { ...@@ -92,6 +92,7 @@ static const rio rioBufferIO = {
rioBufferFlush, rioBufferFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */
0, /* bytes read or written */ 0, /* bytes read or written */
0, /* read/write chunk size */ 0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */ { { NULL, 0 } } /* union for io-specific vars */
...@@ -145,6 +146,7 @@ static const rio rioFileIO = { ...@@ -145,6 +146,7 @@ static const rio rioFileIO = {
rioFileFlush, rioFileFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */
0, /* bytes read or written */ 0, /* bytes read or written */
0, /* read/write chunk size */ 0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */ { { NULL, 0 } } /* union for io-specific vars */
...@@ -157,7 +159,124 @@ void rioInitWithFile(rio *r, FILE *fp) { ...@@ -157,7 +159,124 @@ void rioInitWithFile(rio *r, FILE *fp) {
r->io.file.autosync = 0; r->io.file.autosync = 0;
} }
/* ------------------- File descriptors set implementation ------------------- */ /* ------------------- File descriptor implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from
* the socket to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a file descriptor that is, normally,
* just a socket. */
static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
UNUSED(r);
UNUSED(buf);
UNUSED(len);
return 0; /* Error, this target does not yet support writing. */
}
/* Returns 1 or 0 for success/failure. */
static size_t rioFdRead(rio *r, void *buf, size_t len) {
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos;
/* If the buffer is too small for the entire request: realloc. */
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len)
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf));
/* If the remaining unused buffer is not large enough: memmove so that we
* can read the rest. */
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) {
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
r->io.fd.pos = 0;
}
/* If we don't already have all the data in the sds, read more */
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) {
size_t buffered = sdslen(r->io.fd.buf) - r->io.fd.pos;
size_t toread = len - buffered;
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
* the two. */
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
if (toread > sdsavail(r->io.fd.buf)) toread = sdsavail(r->io.fd.buf);
if (r->io.fd.read_limit != 0 &&
r->io.fd.read_so_far + buffered + toread > r->io.fd.read_limit)
{
if (r->io.fd.read_limit >= r->io.fd.read_so_far - buffered)
toread = r->io.fd.read_limit - r->io.fd.read_so_far - buffered;
else {
errno = EOVERFLOW;
return 0;
}
}
int retval = read(r->io.fd.fd,
(char*)r->io.fd.buf + sdslen(r->io.fd.buf),
toread);
if (retval <= 0) {
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
return 0;
}
sdsIncrLen(r->io.fd.buf, retval);
}
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len);
r->io.fd.read_so_far += len;
r->io.fd.pos += len;
return len;
}
/* Returns read/write position in file. */
static off_t rioFdTell(rio *r) {
return r->io.fd.read_so_far;
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */
static int rioFdFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */
return rioFdWrite(r,NULL,0);
}
static const rio rioFdIO = {
rioFdRead,
rioFdWrite,
rioFdTell,
rioFdFlush,
NULL, /* update_checksum */
0, /* current checksum */
0, /* flags */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
};
/* Create an RIO that implements a buffered read from an fd
* read_limit argument stops buffering when the reaching the limit. */
void rioInitWithFd(rio *r, int fd, size_t read_limit) {
*r = rioFdIO;
r->io.fd.fd = fd;
r->io.fd.pos = 0;
r->io.fd.read_limit = read_limit;
r->io.fd.read_so_far = 0;
r->io.fd.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
sdsclear(r->io.fd.buf);
}
/* Release the RIO tream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */
void rioFreeFd(rio *r, sds *remaining) {
if (remaining && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) {
if (r->io.fd.pos > 0) sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
*remaining = r->io.fd.buf;
} else {
sdsfree(r->io.fd.buf);
if (remaining) *remaining = NULL;
}
r->io.fd.buf = NULL;
}
/* ------------------- File descriptors set implementation ------------------
* This target is used to write the RDB file to N different replicas via
* sockets, when the master just streams the data to the replicas without
* creating an RDB on-disk image (diskless replication option).
* It only implements writes. */
/* Returns 1 or 0 for success/failure. /* Returns 1 or 0 for success/failure.
* The function returns success as long as we are able to correctly write * The function returns success as long as we are able to correctly write
...@@ -258,6 +377,7 @@ static const rio rioFdsetIO = { ...@@ -258,6 +377,7 @@ static const rio rioFdsetIO = {
rioFdsetFlush, rioFdsetFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */
0, /* bytes read or written */ 0, /* bytes read or written */
0, /* read/write chunk size */ 0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */ { { NULL, 0 } } /* union for io-specific vars */
...@@ -300,7 +420,7 @@ void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) { ...@@ -300,7 +420,7 @@ void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
* disk I/O concentrated in very little time. When we fsync in an explicit * disk I/O concentrated in very little time. When we fsync in an explicit
* way instead the I/O pressure is more distributed across time. */ * way instead the I/O pressure is more distributed across time. */
void rioSetAutoSync(rio *r, off_t bytes) { void rioSetAutoSync(rio *r, off_t bytes) {
serverAssert(r->read == rioFileIO.read); if(r->write != rioFileIO.write) return;
r->io.file.autosync = bytes; r->io.file.autosync = bytes;
} }
......
/* /*
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com> * Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com> * Copyright (c) 2009-2019, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
...@@ -36,6 +36,9 @@ ...@@ -36,6 +36,9 @@
#include <stdint.h> #include <stdint.h>
#include "sds.h" #include "sds.h"
#define RIO_FLAG_READ_ERROR (1<<0)
#define RIO_FLAG_WRITE_ERROR (1<<1)
struct _rio { struct _rio {
/* Backend functions. /* Backend functions.
* Since this functions do not tolerate short writes or reads the return * Since this functions do not tolerate short writes or reads the return
...@@ -51,8 +54,8 @@ struct _rio { ...@@ -51,8 +54,8 @@ struct _rio {
* computation. */ * computation. */
void (*update_cksum)(struct _rio *, const void *buf, size_t len); void (*update_cksum)(struct _rio *, const void *buf, size_t len);
/* The current checksum */ /* The current checksum and flags (see RIO_FLAG_*) */
uint64_t cksum; uint64_t cksum, flags;
/* number of bytes read or written */ /* number of bytes read or written */
size_t processed_bytes; size_t processed_bytes;
...@@ -73,6 +76,14 @@ struct _rio { ...@@ -73,6 +76,14 @@ struct _rio {
off_t buffered; /* Bytes written since last fsync. */ off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */ off_t autosync; /* fsync after 'autosync' bytes written. */
} file; } file;
/* file descriptor */
struct {
int fd; /* File descriptor. */
off_t pos; /* pos in buf that was returned */
sds buf; /* buffered data */
size_t read_limit; /* don't allow to buffer/read more than that */
size_t read_so_far; /* amount of data read from the rio (not buffered) */
} fd;
/* Multiple FDs target (used to write to N sockets). */ /* Multiple FDs target (used to write to N sockets). */
struct { struct {
int *fds; /* File descriptors. */ int *fds; /* File descriptors. */
...@@ -91,11 +102,14 @@ typedef struct _rio rio; ...@@ -91,11 +102,14 @@ typedef struct _rio rio;
* if needed. */ * if needed. */
static inline size_t rioWrite(rio *r, const void *buf, size_t len) { static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
if (r->flags & RIO_FLAG_WRITE_ERROR) return 0;
while (len) { while (len) {
size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len; size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write); if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
if (r->write(r,buf,bytes_to_write) == 0) if (r->write(r,buf,bytes_to_write) == 0) {
r->flags |= RIO_FLAG_WRITE_ERROR;
return 0; return 0;
}
buf = (char*)buf + bytes_to_write; buf = (char*)buf + bytes_to_write;
len -= bytes_to_write; len -= bytes_to_write;
r->processed_bytes += bytes_to_write; r->processed_bytes += bytes_to_write;
...@@ -104,10 +118,13 @@ static inline size_t rioWrite(rio *r, const void *buf, size_t len) { ...@@ -104,10 +118,13 @@ static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
} }
static inline size_t rioRead(rio *r, void *buf, size_t len) { static inline size_t rioRead(rio *r, void *buf, size_t len) {
if (r->flags & RIO_FLAG_READ_ERROR) return 0;
while (len) { while (len) {
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len; size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
if (r->read(r,buf,bytes_to_read) == 0) if (r->read(r,buf,bytes_to_read) == 0) {
r->flags |= RIO_FLAG_READ_ERROR;
return 0; return 0;
}
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read); if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
buf = (char*)buf + bytes_to_read; buf = (char*)buf + bytes_to_read;
len -= bytes_to_read; len -= bytes_to_read;
...@@ -124,11 +141,29 @@ static inline int rioFlush(rio *r) { ...@@ -124,11 +141,29 @@ static inline int rioFlush(rio *r) {
return r->flush(r); return r->flush(r);
} }
/* This function allows to know if there was a read error in any past
* operation, since the rio stream was created or since the last call
* to rioClearError(). */
static inline int rioGetReadError(rio *r) {
return (r->flags & RIO_FLAG_READ_ERROR) != 0;
}
/* Like rioGetReadError() but for write errors. */
static inline int rioGetWriteError(rio *r) {
return (r->flags & RIO_FLAG_WRITE_ERROR) != 0;
}
static inline void rioClearErrors(rio *r) {
r->flags &= ~(RIO_FLAG_READ_ERROR|RIO_FLAG_WRITE_ERROR);
}
void rioInitWithFile(rio *r, FILE *fp); void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s); void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFd(rio *r, int fd, size_t read_limit);
void rioInitWithFdset(rio *r, int *fds, int numfds); void rioInitWithFdset(rio *r, int *fds, int numfds);
void rioFreeFdset(rio *r); void rioFreeFdset(rio *r);
void rioFreeFd(rio *r, sds* out_remainingBufferedData);
size_t rioWriteBulkCount(rio *r, char prefix, long count); size_t rioWriteBulkCount(rio *r, char prefix, long count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len); size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
......
...@@ -42,7 +42,10 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply); ...@@ -42,7 +42,10 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply); char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply); char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply); char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype); char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype);
char *redisProtocolToLuaType_Null(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf);
char *redisProtocolToLuaType_Double(lua_State *lua, char *reply);
int redis_math_random (lua_State *L); int redis_math_random (lua_State *L);
int redis_math_randomseed (lua_State *L); int redis_math_randomseed (lua_State *L);
void ldbInit(void); void ldbInit(void);
...@@ -132,9 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) { ...@@ -132,9 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break; case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
case '+': p = redisProtocolToLuaType_Status(lua,reply); break; case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
case '-': p = redisProtocolToLuaType_Error(lua,reply); break; case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break; case '*': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break; case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break; case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break;
case ',': p = redisProtocolToLuaType_Double(lua,reply); break;
} }
return p; return p;
} }
...@@ -182,13 +188,13 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) { ...@@ -182,13 +188,13 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
return p+2; return p+2;
} }
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) { char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
char *p = strchr(reply+1,'\r'); char *p = strchr(reply+1,'\r');
long long mbulklen; long long mbulklen;
int j = 0; int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen); string2ll(reply+1,p-reply-1,&mbulklen);
if (server.lua_caller->resp == 2 || atype == '*') { if (server.lua_client->resp == 2 || atype == '*') {
p += 2; p += 2;
if (mbulklen == -1) { if (mbulklen == -1) {
lua_pushboolean(lua,0); lua_pushboolean(lua,0);
...@@ -200,11 +206,15 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) { ...@@ -200,11 +206,15 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
p = redisProtocolToLuaType(lua,p); p = redisProtocolToLuaType(lua,p);
lua_settable(lua,-3); lua_settable(lua,-3);
} }
} else if (server.lua_caller->resp == 3) { } else if (server.lua_client->resp == 3) {
/* Here we handle only Set and Map replies in RESP3 mode, since arrays /* Here we handle only Set and Map replies in RESP3 mode, since arrays
* follow the above RESP2 code path. */ * follow the above RESP2 code path. Note that those are represented
* as a table with the "map" or "set" field populated with the actual
* table representing the set or the map type. */
p += 2; p += 2;
lua_newtable(lua); lua_newtable(lua);
lua_pushstring(lua,atype == '%' ? "map" : "set");
lua_newtable(lua);
for (j = 0; j < mbulklen; j++) { for (j = 0; j < mbulklen; j++) {
p = redisProtocolToLuaType(lua,p); p = redisProtocolToLuaType(lua,p);
if (atype == '%') { if (atype == '%') {
...@@ -214,10 +224,44 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) { ...@@ -214,10 +224,44 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
} }
lua_settable(lua,-3); lua_settable(lua,-3);
} }
lua_settable(lua,-3);
} }
return p; return p;
} }
char *redisProtocolToLuaType_Null(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
lua_pushnil(lua);
return p+2;
}
char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf) {
char *p = strchr(reply+1,'\r');
lua_pushboolean(lua,tf == 't');
return p+2;
}
char *redisProtocolToLuaType_Double(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
char buf[MAX_LONG_DOUBLE_CHARS+1];
size_t len = p-reply-1;
double d;
if (len <= MAX_LONG_DOUBLE_CHARS) {
memcpy(buf,reply+1,len);
buf[len] = '\0';
d = strtod(buf,NULL); /* We expect a valid representation. */
} else {
d = 0;
}
lua_newtable(lua);
lua_pushstring(lua,"double");
lua_pushnumber(lua,d);
lua_settable(lua,-3);
return p+2;
}
/* This function is used in order to push an error on the Lua stack in the /* This function is used in order to push an error on the Lua stack in the
* format used by redis.pcall to return errors, which is a lua table * format used by redis.pcall to return errors, which is a lua table
* with a single "err" field set to the error string. Note that this * with a single "err" field set to the error string. Note that this
...@@ -292,6 +336,8 @@ void luaSortArray(lua_State *lua) { ...@@ -292,6 +336,8 @@ void luaSortArray(lua_State *lua) {
* Lua reply to Redis reply conversion functions. * Lua reply to Redis reply conversion functions.
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/* Reply to client 'c' converting the top element in the Lua stack to a
* Redis reply. As a side effect the element is consumed from the stack. */
void luaReplyToRedisReply(client *c, lua_State *lua) { void luaReplyToRedisReply(client *c, lua_State *lua) {
int t = lua_type(lua,-1); int t = lua_type(lua,-1);
...@@ -300,7 +346,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { ...@@ -300,7 +346,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1)); addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
break; break;
case LUA_TBOOLEAN: case LUA_TBOOLEAN:
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.null[c->resp]); if (server.lua_client->resp == 2)
addReply(c,lua_toboolean(lua,-1) ? shared.cone :
shared.null[c->resp]);
else
addReplyBool(c,lua_toboolean(lua,-1));
break; break;
case LUA_TNUMBER: case LUA_TNUMBER:
addReplyLongLong(c,(long long)lua_tonumber(lua,-1)); addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
...@@ -310,6 +360,8 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { ...@@ -310,6 +360,8 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
* Error are returned as a single element table with 'err' field. * Error are returned as a single element table with 'err' field.
* Status replies are returned as single element table with 'ok' * Status replies are returned as single element table with 'ok'
* field. */ * field. */
/* Handle error reply. */
lua_pushstring(lua,"err"); lua_pushstring(lua,"err");
lua_gettable(lua,-2); lua_gettable(lua,-2);
t = lua_type(lua,-1); t = lua_type(lua,-1);
...@@ -321,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { ...@@ -321,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
lua_pop(lua,2); lua_pop(lua,2);
return; return;
} }
lua_pop(lua,1); /* Discard field name pushed before. */
lua_pop(lua,1); /* Handle status reply. */
lua_pushstring(lua,"ok"); lua_pushstring(lua,"ok");
lua_gettable(lua,-2); lua_gettable(lua,-2);
t = lua_type(lua,-1); t = lua_type(lua,-1);
...@@ -331,12 +384,69 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { ...@@ -331,12 +384,69 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
sdsmapchars(ok,"\r\n"," ",2); sdsmapchars(ok,"\r\n"," ",2);
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok)); addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
sdsfree(ok); sdsfree(ok);
lua_pop(lua,1); lua_pop(lua,2);
} else { return;
}
lua_pop(lua,1); /* Discard field name pushed before. */
/* Handle double reply. */
lua_pushstring(lua,"double");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
if (t == LUA_TNUMBER) {
addReplyDouble(c,lua_tonumber(lua,-1));
lua_pop(lua,2);
return;
}
lua_pop(lua,1); /* Discard field name pushed before. */
/* Handle map reply. */
lua_pushstring(lua,"map");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
if (t == LUA_TTABLE) {
int maplen = 0;
void *replylen = addReplyDeferredLen(c); void *replylen = addReplyDeferredLen(c);
int j = 1, mbulklen = 0; lua_pushnil(lua); /* Use nil to start iteration. */
while (lua_next(lua,-2)) {
/* Stack now: table, key, value */
luaReplyToRedisReply(c, lua); /* Return value. */
lua_pushvalue(lua,-1); /* Dup key before consuming. */
luaReplyToRedisReply(c, lua); /* Return key. */
/* Stack now: table, key. */
maplen++;
}
setDeferredMapLen(c,replylen,maplen);
lua_pop(lua,2);
return;
}
lua_pop(lua,1); /* Discard field name pushed before. */
lua_pop(lua,1); /* Discard the 'ok' field value we popped */ /* Handle set reply. */
lua_pushstring(lua,"set");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
if (t == LUA_TTABLE) {
int setlen = 0;
void *replylen = addReplyDeferredLen(c);
lua_pushnil(lua); /* Use nil to start iteration. */
while (lua_next(lua,-2)) {
/* Stack now: table, key, true */
lua_pop(lua,1); /* Discard the boolean value. */
lua_pushvalue(lua,-1); /* Dup key before consuming. */
luaReplyToRedisReply(c, lua); /* Return key. */
/* Stack now: table, key. */
setlen++;
}
setDeferredSetLen(c,replylen,setlen);
lua_pop(lua,2);
return;
}
lua_pop(lua,1); /* Discard field name pushed before. */
/* Handle the array reply. */
void *replylen = addReplyDeferredLen(c);
int j = 1, mbulklen = 0;
while(1) { while(1) {
lua_pushnumber(lua,j++); lua_pushnumber(lua,j++);
lua_gettable(lua,-2); lua_gettable(lua,-2);
...@@ -349,7 +459,6 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { ...@@ -349,7 +459,6 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
mbulklen++; mbulklen++;
} }
setDeferredArrayLen(c,replylen,mbulklen); setDeferredArrayLen(c,replylen,mbulklen);
}
break; break;
default: default:
addReplyNull(c); addReplyNull(c);
...@@ -859,6 +968,25 @@ int luaLogCommand(lua_State *lua) { ...@@ -859,6 +968,25 @@ int luaLogCommand(lua_State *lua) {
return 0; return 0;
} }
/* redis.setresp() */
int luaSetResp(lua_State *lua) {
int argc = lua_gettop(lua);
if (argc != 1) {
lua_pushstring(lua, "redis.setresp() requires one argument.");
return lua_error(lua);
}
int resp = lua_tonumber(lua,-argc);
if (resp != 2 && resp != 3) {
lua_pushstring(lua, "RESP version must be 2 or 3.");
return lua_error(lua);
}
server.lua_client->resp = resp;
return 0;
}
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
* Lua engine initialization and reset. * Lua engine initialization and reset.
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
...@@ -955,6 +1083,7 @@ void scriptingInit(int setup) { ...@@ -955,6 +1083,7 @@ void scriptingInit(int setup) {
if (setup) { if (setup) {
server.lua_client = NULL; server.lua_client = NULL;
server.lua_caller = NULL; server.lua_caller = NULL;
server.lua_cur_script = NULL;
server.lua_timedout = 0; server.lua_timedout = 0;
ldbInit(); ldbInit();
} }
...@@ -986,6 +1115,11 @@ void scriptingInit(int setup) { ...@@ -986,6 +1115,11 @@ void scriptingInit(int setup) {
lua_pushcfunction(lua,luaLogCommand); lua_pushcfunction(lua,luaLogCommand);
lua_settable(lua,-3); lua_settable(lua,-3);
/* redis.setresp */
lua_pushstring(lua,"setresp");
lua_pushcfunction(lua,luaSetResp);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_DEBUG"); lua_pushstring(lua,"LOG_DEBUG");
lua_pushnumber(lua,LL_DEBUG); lua_pushnumber(lua,LL_DEBUG);
lua_settable(lua,-3); lua_settable(lua,-3);
...@@ -1274,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { ...@@ -1274,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
/* Set the timeout condition if not already set and the maximum /* Set the timeout condition if not already set and the maximum
* execution time was reached. */ * execution time was reached. */
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed); serverLog(LL_WARNING,
"Lua slow script detected: still in execution after %lld milliseconds. "
"You can try killing the script using the SCRIPT KILL command. "
"Script SHA1 is: %s",
elapsed, server.lua_cur_script);
server.lua_timedout = 1; server.lua_timedout = 1;
/* Once the script timeouts we reenter the event loop to permit others /* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason * to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
...@@ -1379,8 +1517,9 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -1379,8 +1517,9 @@ void evalGenericCommand(client *c, int evalsha) {
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
/* Select the right DB in the context of the Lua client */ /* Set the Lua client database and protocol. */
selectDb(server.lua_client,c->db->id); selectDb(server.lua_client,c->db->id);
server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
/* Set a hook in order to be able to stop the script execution if it /* Set a hook in order to be able to stop the script execution if it
* is running for too much time. * is running for too much time.
...@@ -1390,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -1390,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) {
* If we are debugging, we set instead a "line" hook so that the * If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */ * debugger is call-back at every line executed by the script. */
server.lua_caller = c; server.lua_caller = c;
server.lua_cur_script = funcname + 2;
server.lua_time_start = mstime(); server.lua_time_start = mstime();
server.lua_kill = 0; server.lua_kill = 0;
if (server.lua_time_limit > 0 && ldb.active == 0) { if (server.lua_time_limit > 0 && ldb.active == 0) {
...@@ -1416,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -1416,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) {
queueClientForReprocessing(server.master); queueClientForReprocessing(server.master);
} }
server.lua_caller = NULL; server.lua_caller = NULL;
server.lua_cur_script = NULL;
/* Call the Lua garbage collector from time to time to avoid a /* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua, which adds too latency. * full cycle performed by Lua, which adds too latency.
...@@ -1693,7 +1834,7 @@ void ldbSendLogs(void) { ...@@ -1693,7 +1834,7 @@ void ldbSendLogs(void) {
int ldbStartSession(client *c) { int ldbStartSession(client *c) {
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0; ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
if (ldb.forked) { if (ldb.forked) {
pid_t cp = fork(); pid_t cp = redisFork();
if (cp == -1) { if (cp == -1) {
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode."); addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
return 0; return 0;
...@@ -1710,7 +1851,6 @@ int ldbStartSession(client *c) { ...@@ -1710,7 +1851,6 @@ int ldbStartSession(client *c) {
* socket to make sure if the parent crashes a reset is sent * socket to make sure if the parent crashes a reset is sent
* to the clients. */ * to the clients. */
serverLog(LL_WARNING,"Redis forked for debugging eval"); serverLog(LL_WARNING,"Redis forked for debugging eval");
closeListeningSockets(0);
} else { } else {
/* Parent */ /* Parent */
listAddNodeTail(ldb.children,(void*)(unsigned long)cp); listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
...@@ -2053,6 +2193,11 @@ char *ldbRedisProtocolToHuman_Int(sds *o, char *reply); ...@@ -2053,6 +2193,11 @@ char *ldbRedisProtocolToHuman_Int(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply); char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply); char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply); char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Set(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Map(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Null(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Double(sds *o, char *reply);
/* Get Redis protocol from 'reply' and appends it in human readable form to /* Get Redis protocol from 'reply' and appends it in human readable form to
* the passed SDS string 'o'. * the passed SDS string 'o'.
...@@ -2067,6 +2212,11 @@ char *ldbRedisProtocolToHuman(sds *o, char *reply) { ...@@ -2067,6 +2212,11 @@ char *ldbRedisProtocolToHuman(sds *o, char *reply) {
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break; case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break; case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break; case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
case '~': p = ldbRedisProtocolToHuman_Set(o,reply); break;
case '%': p = ldbRedisProtocolToHuman_Map(o,reply); break;
case '_': p = ldbRedisProtocolToHuman_Null(o,reply); break;
case '#': p = ldbRedisProtocolToHuman_Bool(o,reply); break;
case ',': p = ldbRedisProtocolToHuman_Double(o,reply); break;
} }
return p; return p;
} }
...@@ -2121,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) { ...@@ -2121,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
return p; return p;
} }
char *ldbRedisProtocolToHuman_Set(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
long long mbulklen;
int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen);
p += 2;
*o = sdscatlen(*o,"~(",2);
for (j = 0; j < mbulklen; j++) {
p = ldbRedisProtocolToHuman(o,p);
if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
}
*o = sdscatlen(*o,")",1);
return p;
}
char *ldbRedisProtocolToHuman_Map(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
long long mbulklen;
int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen);
p += 2;
*o = sdscatlen(*o,"{",1);
for (j = 0; j < mbulklen; j++) {
p = ldbRedisProtocolToHuman(o,p);
*o = sdscatlen(*o," => ",4);
p = ldbRedisProtocolToHuman(o,p);
if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
}
*o = sdscatlen(*o,"}",1);
return p;
}
char *ldbRedisProtocolToHuman_Null(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
*o = sdscatlen(*o,"(null)",6);
return p+2;
}
char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
if (reply[1] == 't')
*o = sdscatlen(*o,"#true",5);
else
*o = sdscatlen(*o,"#false",6);
return p+2;
}
char *ldbRedisProtocolToHuman_Double(sds *o, char *reply) {
char *p = strchr(reply+1,'\r');
*o = sdscatlen(*o,"(double) ",9);
*o = sdscatlen(*o,reply+1,p-reply-1);
return p+2;
}
/* Log a Redis reply as debugger output, in an human readable format. /* Log a Redis reply as debugger output, in an human readable format.
* If the resulting string is longer than 'len' plus a few more chars * If the resulting string is longer than 'len' plus a few more chars
* used as prefix, it gets truncated. */ * used as prefix, it gets truncated. */
......
...@@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
long i; long i;
va_list ap; va_list ap;
/* To avoid continuous reallocations, let's start with a buffer that
* can hold at least two times the format string itself. It's not the
* best heuristic but seems to work in practice. */
s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
va_start(ap,fmt); va_start(ap,fmt);
f = fmt; /* Next format specifier byte to process. */ f = fmt; /* Next format specifier byte to process. */
i = initlen; /* Position of the next byte to write to dest str. */ i = initlen; /* Position of the next byte to write to dest str. */
......
...@@ -147,6 +147,8 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */ ...@@ -147,6 +147,8 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
* *
* no-monitor: Do not automatically propagate the command on MONITOR. * no-monitor: Do not automatically propagate the command on MONITOR.
* *
* no-slowlog: Do not automatically propagate the command to the slowlog.
*
* cluster-asking: Perform an implicit ASKING for this command, so the * cluster-asking: Perform an implicit ASKING for this command, so the
* command will be accepted in cluster mode if the slot is marked * command will be accepted in cluster mode if the slot is marked
* as 'importing'. * as 'importing'.
...@@ -627,7 +629,7 @@ struct redisCommand redisCommandTable[] = { ...@@ -627,7 +629,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"auth",authCommand,-2, {"auth",authCommand,-2,
"no-script ok-loading ok-stale fast @connection", "no-script ok-loading ok-stale fast no-monitor no-slowlog @connection",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
/* We don't allow PING during loading since in Redis PING is used as /* We don't allow PING during loading since in Redis PING is used as
...@@ -670,7 +672,7 @@ struct redisCommand redisCommandTable[] = { ...@@ -670,7 +672,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"exec",execCommand,1, {"exec",execCommand,1,
"no-script no-monitor @transaction", "no-script no-monitor no-slowlog @transaction",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"discard",discardCommand,1, {"discard",discardCommand,1,
...@@ -822,7 +824,7 @@ struct redisCommand redisCommandTable[] = { ...@@ -822,7 +824,7 @@ struct redisCommand redisCommandTable[] = {
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"hello",helloCommand,-2, {"hello",helloCommand,-2,
"no-script fast @connection", "no-script fast no-monitor no-slowlog @connection",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
/* EVAL can modify the dataset, however it is not flagged as a write /* EVAL can modify the dataset, however it is not flagged as a write
...@@ -1447,12 +1449,18 @@ int incrementallyRehash(int dbid) { ...@@ -1447,12 +1449,18 @@ int incrementallyRehash(int dbid) {
* for dict.c to resize the hash tables accordingly to the fact we have o not * for dict.c to resize the hash tables accordingly to the fact we have o not
* running childs. */ * running childs. */
void updateDictResizePolicy(void) { void updateDictResizePolicy(void) {
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) if (!hasActiveChildProcess())
dictEnableResize(); dictEnableResize();
else else
dictDisableResize(); dictDisableResize();
} }
int hasActiveChildProcess() {
return server.rdb_child_pid != -1 ||
server.aof_child_pid != -1 ||
server.module_child_pid != -1;
}
/* ======================= Cron: called every 100 ms ======================== */ /* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */ /* Add a sample to the operations per second array of samples. */
...@@ -1689,7 +1697,7 @@ void databasesCron(void) { ...@@ -1689,7 +1697,7 @@ void databasesCron(void) {
/* Perform hash tables rehashing if needed, but only if there are no /* Perform hash tables rehashing if needed, but only if there are no
* other processes saving the DB on disk. Otherwise rehashing is bad * other processes saving the DB on disk. Otherwise rehashing is bad
* as will cause a lot of copy-on-write of memory pages. */ * as will cause a lot of copy-on-write of memory pages. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) { if (!hasActiveChildProcess()) {
/* We use global counters so if we stop the computation at a given /* We use global counters so if we stop the computation at a given
* DB we'll be able to start from the successive in the next * DB we'll be able to start from the successive in the next
* cron loop iteration. */ * cron loop iteration. */
...@@ -1886,15 +1894,14 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1886,15 +1894,14 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Start a scheduled AOF rewrite if this was requested by the user while /* Start a scheduled AOF rewrite if this was requested by the user while
* a BGSAVE was in progress. */ * a BGSAVE was in progress. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && if (!hasActiveChildProcess() &&
server.aof_rewrite_scheduled) server.aof_rewrite_scheduled)
{ {
rewriteAppendOnlyFileBackground(); rewriteAppendOnlyFileBackground();
} }
/* Check if a background saving or AOF rewrite in progress terminated. */ /* Check if a background saving or AOF rewrite in progress terminated. */
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 || if (hasActiveChildProcess() || ldbPendingChildren())
ldbPendingChildren())
{ {
int statloc; int statloc;
pid_t pid; pid_t pid;
...@@ -1905,18 +1912,32 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1905,18 +1912,32 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc); if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
/* sigKillChildHandler catches the signal and calls exit(), but we
* must make sure not to flag lastbgsave_status, etc incorrectly.
* We could directly terminate the child process via SIGUSR1
* without handling it, but in this case Valgrind will log an
* annoying error. */
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
bysignal = SIGUSR1;
exitcode = 1;
}
if (pid == -1) { if (pid == -1) {
serverLog(LL_WARNING,"wait3() returned an error: %s. " serverLog(LL_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d", "rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
strerror(errno), strerror(errno),
(int) server.rdb_child_pid, (int) server.rdb_child_pid,
(int) server.aof_child_pid); (int) server.aof_child_pid,
(int) server.module_child_pid);
} else if (pid == server.rdb_child_pid) { } else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal); backgroundSaveDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo(); if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.aof_child_pid) { } else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal); backgroundRewriteDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo(); if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.module_child_pid) {
ModuleForkDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else { } else {
if (!ldbRemoveChild(pid)) { if (!ldbRemoveChild(pid)) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
...@@ -1954,8 +1975,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1954,8 +1975,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Trigger an AOF rewrite if needed. */ /* Trigger an AOF rewrite if needed. */
if (server.aof_state == AOF_ON && if (server.aof_state == AOF_ON &&
server.rdb_child_pid == -1 && !hasActiveChildProcess() &&
server.aof_child_pid == -1 &&
server.aof_rewrite_perc && server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size) server.aof_current_size > server.aof_rewrite_min_size)
{ {
...@@ -2013,7 +2033,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -2013,7 +2033,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* Note: this code must be after the replicationCron() call above so * Note: this code must be after the replicationCron() call above so
* make sure when refactoring this file to keep this order. This is useful * make sure when refactoring this file to keep this order. This is useful
* because we want to give priority to RDB savings for replication. */ * because we want to give priority to RDB savings for replication. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && if (!hasActiveChildProcess() &&
server.rdb_bgsave_scheduled && server.rdb_bgsave_scheduled &&
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
server.lastbgsave_status == C_OK)) server.lastbgsave_status == C_OK))
...@@ -2159,6 +2179,16 @@ void createSharedObjects(void) { ...@@ -2159,6 +2179,16 @@ void createSharedObjects(void) {
shared.nullarray[2] = createObject(OBJ_STRING,sdsnew("*-1\r\n")); shared.nullarray[2] = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
shared.nullarray[3] = createObject(OBJ_STRING,sdsnew("_\r\n")); shared.nullarray[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
shared.emptymap[0] = NULL;
shared.emptymap[1] = NULL;
shared.emptymap[2] = createObject(OBJ_STRING,sdsnew("*0\r\n"));
shared.emptymap[3] = createObject(OBJ_STRING,sdsnew("%0\r\n"));
shared.emptyset[0] = NULL;
shared.emptyset[1] = NULL;
shared.emptyset[2] = createObject(OBJ_STRING,sdsnew("*0\r\n"));
shared.emptyset[3] = createObject(OBJ_STRING,sdsnew("~0\r\n"));
for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) { for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) {
char dictid_str[64]; char dictid_str[64];
int dictid_len; int dictid_len;
...@@ -2266,6 +2296,7 @@ void initServerConfig(void) { ...@@ -2266,6 +2296,7 @@ void initServerConfig(void) {
server.aof_flush_postponed_start = 0; server.aof_flush_postponed_start = 0;
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC; server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC; server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED; server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE; server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
server.pidfile = NULL; server.pidfile = NULL;
...@@ -2335,6 +2366,9 @@ void initServerConfig(void) { ...@@ -2335,6 +2366,9 @@ void initServerConfig(void) {
server.cached_master = NULL; server.cached_master = NULL;
server.master_initial_offset = -1; server.master_initial_offset = -1;
server.repl_state = REPL_STATE_NONE; server.repl_state = REPL_STATE_NONE;
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
server.repl_transfer_s = -1;
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT; server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA; server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY; server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
...@@ -2343,6 +2377,7 @@ void initServerConfig(void) { ...@@ -2343,6 +2377,7 @@ void initServerConfig(void) {
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */ server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
server.repl_disable_tcp_nodelay = CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY; server.repl_disable_tcp_nodelay = CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY;
server.repl_diskless_sync = CONFIG_DEFAULT_REPL_DISKLESS_SYNC; server.repl_diskless_sync = CONFIG_DEFAULT_REPL_DISKLESS_SYNC;
server.repl_diskless_load = CONFIG_DEFAULT_REPL_DISKLESS_LOAD;
server.repl_diskless_sync_delay = CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY; server.repl_diskless_sync_delay = CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY;
server.repl_ping_slave_period = CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD; server.repl_ping_slave_period = CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD;
server.repl_timeout = CONFIG_DEFAULT_REPL_TIMEOUT; server.repl_timeout = CONFIG_DEFAULT_REPL_TIMEOUT;
...@@ -2399,6 +2434,9 @@ void initServerConfig(void) { ...@@ -2399,6 +2434,9 @@ void initServerConfig(void) {
/* Latency monitor */ /* Latency monitor */
server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD; server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD;
/* Tracking. */
server.tracking_table_max_fill = CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL;
/* Debugging */ /* Debugging */
server.assert_failed = "<no assertion failed>"; server.assert_failed = "<no assertion failed>";
server.assert_file = "<no file>"; server.assert_file = "<no file>";
...@@ -2780,6 +2818,7 @@ void initServer(void) { ...@@ -2780,6 +2818,7 @@ void initServer(void) {
server.cronloops = 0; server.cronloops = 0;
server.rdb_child_pid = -1; server.rdb_child_pid = -1;
server.aof_child_pid = -1; server.aof_child_pid = -1;
server.module_child_pid = -1;
server.rdb_child_type = RDB_CHILD_TYPE_NONE; server.rdb_child_type = RDB_CHILD_TYPE_NONE;
server.rdb_bgsave_scheduled = 0; server.rdb_bgsave_scheduled = 0;
server.child_info_pipe[0] = -1; server.child_info_pipe[0] = -1;
...@@ -2798,6 +2837,7 @@ void initServer(void) { ...@@ -2798,6 +2837,7 @@ void initServer(void) {
server.stat_peak_memory = 0; server.stat_peak_memory = 0;
server.stat_rdb_cow_bytes = 0; server.stat_rdb_cow_bytes = 0;
server.stat_aof_cow_bytes = 0; server.stat_aof_cow_bytes = 0;
server.stat_module_cow_bytes = 0;
server.cron_malloc_stats.zmalloc_used = 0; server.cron_malloc_stats.zmalloc_used = 0;
server.cron_malloc_stats.process_rss = 0; server.cron_malloc_stats.process_rss = 0;
server.cron_malloc_stats.allocator_allocated = 0; server.cron_malloc_stats.allocator_allocated = 0;
...@@ -2914,6 +2954,8 @@ int populateCommandTableParseFlags(struct redisCommand *c, char *strflags) { ...@@ -2914,6 +2954,8 @@ int populateCommandTableParseFlags(struct redisCommand *c, char *strflags) {
c->flags |= CMD_STALE; c->flags |= CMD_STALE;
} else if (!strcasecmp(flag,"no-monitor")) { } else if (!strcasecmp(flag,"no-monitor")) {
c->flags |= CMD_SKIP_MONITOR; c->flags |= CMD_SKIP_MONITOR;
} else if (!strcasecmp(flag,"no-slowlog")) {
c->flags |= CMD_SKIP_SLOWLOG;
} else if (!strcasecmp(flag,"cluster-asking")) { } else if (!strcasecmp(flag,"cluster-asking")) {
c->flags |= CMD_ASKING; c->flags |= CMD_ASKING;
} else if (!strcasecmp(flag,"fast")) { } else if (!strcasecmp(flag,"fast")) {
...@@ -3198,12 +3240,13 @@ void call(client *c, int flags) { ...@@ -3198,12 +3240,13 @@ void call(client *c, int flags) {
/* Log the command into the Slow log if needed, and populate the /* Log the command into the Slow log if needed, and populate the
* per-command statistics that we show in INFO commandstats. */ * per-command statistics that we show in INFO commandstats. */
if (flags & CMD_CALL_SLOWLOG && c->cmd->proc != execCommand) { if (flags & CMD_CALL_SLOWLOG && !(c->cmd->flags & CMD_SKIP_SLOWLOG)) {
char *latency_event = (c->cmd->flags & CMD_FAST) ? char *latency_event = (c->cmd->flags & CMD_FAST) ?
"fast-command" : "command"; "fast-command" : "command";
latencyAddSampleIfNeeded(latency_event,duration/1000); latencyAddSampleIfNeeded(latency_event,duration/1000);
slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration); slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
} }
if (flags & CMD_CALL_STATS) { if (flags & CMD_CALL_STATS) {
/* use the real command that was executed (cmd and lastamc) may be /* use the real command that was executed (cmd and lastamc) may be
* different, in case of MULTI-EXEC or re-written commands such as * different, in case of MULTI-EXEC or re-written commands such as
...@@ -3271,6 +3314,16 @@ void call(client *c, int flags) { ...@@ -3271,6 +3314,16 @@ void call(client *c, int flags) {
redisOpArrayFree(&server.also_propagate); redisOpArrayFree(&server.also_propagate);
} }
server.also_propagate = prev_also_propagate; server.also_propagate = prev_also_propagate;
/* If the client has keys tracking enabled for client side caching,
* make sure to remember the keys it fetched via this command. */
if (c->cmd->flags & CMD_READONLY) {
client *caller = (c->flags & CLIENT_LUA && server.lua_caller) ?
server.lua_caller : c;
if (caller->flags & CLIENT_TRACKING)
trackingRememberKeys(caller);
}
server.stat_numcommands++; server.stat_numcommands++;
} }
...@@ -3318,9 +3371,10 @@ int processCommand(client *c) { ...@@ -3318,9 +3371,10 @@ int processCommand(client *c) {
/* Check if the user is authenticated. This check is skipped in case /* Check if the user is authenticated. This check is skipped in case
* the default user is flagged as "nopass" and is active. */ * the default user is flagged as "nopass" and is active. */
int auth_required = !(DefaultUser->flags & USER_FLAG_NOPASS) && int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
DefaultUser->flags & USER_FLAG_DISABLED) &&
!c->authenticated; !c->authenticated;
if (auth_required || DefaultUser->flags & USER_FLAG_DISABLED) { if (auth_required) {
/* AUTH and HELLO are valid even in non authenticated state. */ /* AUTH and HELLO are valid even in non authenticated state. */
if (c->cmd->proc != authCommand || c->cmd->proc == helloCommand) { if (c->cmd->proc != authCommand || c->cmd->proc == helloCommand) {
flagTransaction(c); flagTransaction(c);
...@@ -3337,7 +3391,7 @@ int processCommand(client *c) { ...@@ -3337,7 +3391,7 @@ int processCommand(client *c) {
if (acl_retval == ACL_DENIED_CMD) if (acl_retval == ACL_DENIED_CMD)
addReplyErrorFormat(c, addReplyErrorFormat(c,
"-NOPERM this user has no permissions to run " "-NOPERM this user has no permissions to run "
"the '%s' command or its subcommnad", c->cmd->name); "the '%s' command or its subcommand", c->cmd->name);
else else
addReplyErrorFormat(c, addReplyErrorFormat(c,
"-NOPERM this user has no permissions to access " "-NOPERM this user has no permissions to access "
...@@ -3388,13 +3442,20 @@ int processCommand(client *c) { ...@@ -3388,13 +3442,20 @@ int processCommand(client *c) {
* is in MULTI/EXEC context? Error. */ * is in MULTI/EXEC context? Error. */
if (out_of_memory && if (out_of_memory &&
(c->cmd->flags & CMD_DENYOOM || (c->cmd->flags & CMD_DENYOOM ||
(c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) { (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand &&
c->cmd->proc != discardCommand)))
{
flagTransaction(c); flagTransaction(c);
addReply(c, shared.oomerr); addReply(c, shared.oomerr);
return C_OK; return C_OK;
} }
} }
/* Make sure to use a reasonable amount of memory for client side
* caching metadata. */
if (server.tracking_clients) trackingLimitUsedSlots();
/* Don't accept write commands if there are problems persisting on disk /* Don't accept write commands if there are problems persisting on disk
* and if this is a master instance. */ * and if this is a master instance. */
int deny_write_type = writeCommandsDeniedByDiskError(); int deny_write_type = writeCommandsDeniedByDiskError();
...@@ -3535,6 +3596,12 @@ int prepareForShutdown(int flags) { ...@@ -3535,6 +3596,12 @@ int prepareForShutdown(int flags) {
killRDBChild(); killRDBChild();
} }
/* Kill module child if there is one. */
if (server.module_child_pid != -1) {
serverLog(LL_WARNING,"There is a module fork child. Killing it!");
TerminateModuleForkChild(server.module_child_pid,0);
}
if (server.aof_state != AOF_OFF) { if (server.aof_state != AOF_OFF) {
/* Kill the AOF saving child as the AOF we already have may be longer /* Kill the AOF saving child as the AOF we already have may be longer
* but contains the full dataset anyway. */ * but contains the full dataset anyway. */
...@@ -3689,6 +3756,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) { ...@@ -3689,6 +3756,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
flagcount += addReplyCommandFlag(c,cmd,CMD_LOADING, "loading"); flagcount += addReplyCommandFlag(c,cmd,CMD_LOADING, "loading");
flagcount += addReplyCommandFlag(c,cmd,CMD_STALE, "stale"); flagcount += addReplyCommandFlag(c,cmd,CMD_STALE, "stale");
flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_MONITOR, "skip_monitor"); flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_MONITOR, "skip_monitor");
flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_SLOWLOG, "skip_slowlog");
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking"); flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast"); flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) || if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) ||
...@@ -3803,12 +3871,15 @@ sds genRedisInfoString(char *section) { ...@@ -3803,12 +3871,15 @@ sds genRedisInfoString(char *section) {
time_t uptime = server.unixtime-server.stat_starttime; time_t uptime = server.unixtime-server.stat_starttime;
int j; int j;
struct rusage self_ru, c_ru; struct rusage self_ru, c_ru;
int allsections = 0, defsections = 0; int allsections = 0, defsections = 0, everything = 0, modules = 0;
int sections = 0; int sections = 0;
if (section == NULL) section = "default"; if (section == NULL) section = "default";
allsections = strcasecmp(section,"all") == 0; allsections = strcasecmp(section,"all") == 0;
defsections = strcasecmp(section,"default") == 0; defsections = strcasecmp(section,"default") == 0;
everything = strcasecmp(section,"everything") == 0;
modules = strcasecmp(section,"modules") == 0;
if (everything) allsections = 1;
getrusage(RUSAGE_SELF, &self_ru); getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru); getrusage(RUSAGE_CHILDREN, &c_ru);
...@@ -3831,32 +3902,32 @@ sds genRedisInfoString(char *section) { ...@@ -3831,32 +3902,32 @@ sds genRedisInfoString(char *section) {
call_uname = 0; call_uname = 0;
} }
info = sdscatprintf(info, info = sdscatfmt(info,
"# Server\r\n" "# Server\r\n"
"redis_version:%s\r\n" "redis_version:%s\r\n"
"redis_git_sha1:%s\r\n" "redis_git_sha1:%s\r\n"
"redis_git_dirty:%d\r\n" "redis_git_dirty:%i\r\n"
"redis_build_id:%llx\r\n" "redis_build_id:%s\r\n"
"redis_mode:%s\r\n" "redis_mode:%s\r\n"
"os:%s %s %s\r\n" "os:%s %s %s\r\n"
"arch_bits:%d\r\n" "arch_bits:%i\r\n"
"multiplexing_api:%s\r\n" "multiplexing_api:%s\r\n"
"atomicvar_api:%s\r\n" "atomicvar_api:%s\r\n"
"gcc_version:%d.%d.%d\r\n" "gcc_version:%i.%i.%i\r\n"
"process_id:%ld\r\n" "process_id:%I\r\n"
"run_id:%s\r\n" "run_id:%s\r\n"
"tcp_port:%d\r\n" "tcp_port:%i\r\n"
"uptime_in_seconds:%jd\r\n" "uptime_in_seconds:%I\r\n"
"uptime_in_days:%jd\r\n" "uptime_in_days:%I\r\n"
"hz:%d\r\n" "hz:%i\r\n"
"configured_hz:%d\r\n" "configured_hz:%i\r\n"
"lru_clock:%ld\r\n" "lru_clock:%u\r\n"
"executable:%s\r\n" "executable:%s\r\n"
"config_file:%s\r\n", "config_file:%s\r\n",
REDIS_VERSION, REDIS_VERSION,
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
(unsigned long long) redisBuildId(), redisBuildIdString(),
mode, mode,
name.sysname, name.release, name.machine, name.sysname, name.release, name.machine,
server.arch_bits, server.arch_bits,
...@@ -3867,14 +3938,14 @@ sds genRedisInfoString(char *section) { ...@@ -3867,14 +3938,14 @@ sds genRedisInfoString(char *section) {
#else #else
0,0,0, 0,0,0,
#endif #endif
(long) getpid(), (int64_t) getpid(),
server.runid, server.runid,
server.port, server.port,
(intmax_t)uptime, (int64_t)uptime,
(intmax_t)(uptime/(3600*24)), (int64_t)(uptime/(3600*24)),
server.hz, server.hz,
server.config_hz, server.config_hz,
(unsigned long) server.lruclock, server.lruclock,
server.executable ? server.executable : "", server.executable ? server.executable : "",
server.configfile ? server.configfile : ""); server.configfile ? server.configfile : "");
} }
...@@ -3889,10 +3960,12 @@ sds genRedisInfoString(char *section) { ...@@ -3889,10 +3960,12 @@ sds genRedisInfoString(char *section) {
"connected_clients:%lu\r\n" "connected_clients:%lu\r\n"
"client_recent_max_input_buffer:%zu\r\n" "client_recent_max_input_buffer:%zu\r\n"
"client_recent_max_output_buffer:%zu\r\n" "client_recent_max_output_buffer:%zu\r\n"
"blocked_clients:%d\r\n", "blocked_clients:%d\r\n"
"tracking_clients:%d\r\n",
listLength(server.clients)-listLength(server.slaves), listLength(server.clients)-listLength(server.slaves),
maxin, maxout, maxin, maxout,
server.blocked_clients); server.blocked_clients,
server.tracking_clients);
} }
/* Memory */ /* Memory */
...@@ -3998,8 +4071,11 @@ sds genRedisInfoString(char *section) { ...@@ -3998,8 +4071,11 @@ sds genRedisInfoString(char *section) {
mh->allocator_rss_bytes, mh->allocator_rss_bytes,
mh->rss_extra, mh->rss_extra,
mh->rss_extra_bytes, mh->rss_extra_bytes,
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */ mh->total_frag, /* This is the total RSS overhead, including
mh->total_frag_bytes, /* named so for backwards compatibility */ fragmentation, but not just it. This field
(and the next one) is named like that just
for backward compatibility. */
mh->total_frag_bytes,
freeMemoryGetNotCountedMemory(), freeMemoryGetNotCountedMemory(),
mh->repl_backlog, mh->repl_backlog,
mh->clients_slaves, mh->clients_slaves,
...@@ -4032,7 +4108,9 @@ sds genRedisInfoString(char *section) { ...@@ -4032,7 +4108,9 @@ sds genRedisInfoString(char *section) {
"aof_current_rewrite_time_sec:%jd\r\n" "aof_current_rewrite_time_sec:%jd\r\n"
"aof_last_bgrewrite_status:%s\r\n" "aof_last_bgrewrite_status:%s\r\n"
"aof_last_write_status:%s\r\n" "aof_last_write_status:%s\r\n"
"aof_last_cow_size:%zu\r\n", "aof_last_cow_size:%zu\r\n"
"module_fork_in_progress:%d\r\n"
"module_fork_last_cow_size:%zu\r\n",
server.loading, server.loading,
server.dirty, server.dirty,
server.rdb_child_pid != -1, server.rdb_child_pid != -1,
...@@ -4050,9 +4128,11 @@ sds genRedisInfoString(char *section) { ...@@ -4050,9 +4128,11 @@ sds genRedisInfoString(char *section) {
-1 : time(NULL)-server.aof_rewrite_time_start), -1 : time(NULL)-server.aof_rewrite_time_start),
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err", (server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
(server.aof_last_write_status == C_OK) ? "ok" : "err", (server.aof_last_write_status == C_OK) ? "ok" : "err",
server.stat_aof_cow_bytes); server.stat_aof_cow_bytes,
server.module_child_pid != -1,
server.stat_module_cow_bytes);
if (server.aof_state != AOF_OFF) { if (server.aof_enabled) {
info = sdscatprintf(info, info = sdscatprintf(info,
"aof_current_size:%lld\r\n" "aof_current_size:%lld\r\n"
"aof_base_size:%lld\r\n" "aof_base_size:%lld\r\n"
...@@ -4132,7 +4212,8 @@ sds genRedisInfoString(char *section) { ...@@ -4132,7 +4212,8 @@ sds genRedisInfoString(char *section) {
"active_defrag_hits:%lld\r\n" "active_defrag_hits:%lld\r\n"
"active_defrag_misses:%lld\r\n" "active_defrag_misses:%lld\r\n"
"active_defrag_key_hits:%lld\r\n" "active_defrag_key_hits:%lld\r\n"
"active_defrag_key_misses:%lld\r\n", "active_defrag_key_misses:%lld\r\n"
"tracking_used_slots:%lld\r\n",
server.stat_numconnections, server.stat_numconnections,
server.stat_numcommands, server.stat_numcommands,
getInstantaneousMetric(STATS_METRIC_COMMAND), getInstantaneousMetric(STATS_METRIC_COMMAND),
...@@ -4158,7 +4239,8 @@ sds genRedisInfoString(char *section) { ...@@ -4158,7 +4239,8 @@ sds genRedisInfoString(char *section) {
server.stat_active_defrag_hits, server.stat_active_defrag_hits,
server.stat_active_defrag_misses, server.stat_active_defrag_misses,
server.stat_active_defrag_key_hits, server.stat_active_defrag_key_hits,
server.stat_active_defrag_key_misses); server.stat_active_defrag_key_misses,
trackingGetUsedSlots());
} }
/* Replication */ /* Replication */
...@@ -4304,6 +4386,13 @@ sds genRedisInfoString(char *section) { ...@@ -4304,6 +4386,13 @@ sds genRedisInfoString(char *section) {
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec); (long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
} }
/* Modules */
if (allsections || defsections || !strcasecmp(section,"modules")) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info,"# Modules\r\n");
info = genModulesInfoString(info);
}
/* Command statistics */ /* Command statistics */
if (allsections || !strcasecmp(section,"commandstats")) { if (allsections || !strcasecmp(section,"commandstats")) {
if (sections++) info = sdscat(info,"\r\n"); if (sections++) info = sdscat(info,"\r\n");
...@@ -4349,6 +4438,17 @@ sds genRedisInfoString(char *section) { ...@@ -4349,6 +4438,17 @@ sds genRedisInfoString(char *section) {
} }
} }
} }
/* Get info from modules.
* if user asked for "everything" or "modules", or a specific section
* that's not found yet. */
if (everything || modules ||
(!allsections && !defsections && sections==0)) {
info = modulesCollectInfo(info,
everything || modules ? NULL: section,
0, /* not a crash report */
sections);
}
return info; return info;
} }
...@@ -4359,7 +4459,9 @@ void infoCommand(client *c) { ...@@ -4359,7 +4459,9 @@ void infoCommand(client *c) {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
return; return;
} }
addReplyBulkSds(c, genRedisInfoString(section)); sds info = genRedisInfoString(section);
addReplyVerbatim(c,info,sdslen(info),"txt");
sdsfree(info);
} }
void monitorCommand(client *c) { void monitorCommand(client *c) {
...@@ -4546,6 +4648,61 @@ void setupSignalHandlers(void) { ...@@ -4546,6 +4648,61 @@ void setupSignalHandlers(void) {
return; return;
} }
/* This is the signal handler for children process. It is currently useful
* in order to track the SIGUSR1, that we send to a child in order to terminate
* it in a clean way, without the parent detecting an error and stop
* accepting writes because of a write error condition. */
static void sigKillChildHandler(int sig) {
UNUSED(sig);
serverLogFromHandler(LL_WARNING, "Received SIGUSR1 in child, exiting now.");
exitFromChild(SERVER_CHILD_NOERROR_RETVAL);
}
void setupChildSignalHandlers(void) {
struct sigaction act;
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
* Otherwise, sa_handler is used. */
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = sigKillChildHandler;
sigaction(SIGUSR1, &act, NULL);
return;
}
int redisFork() {
int childpid;
long long start = ustime();
if ((childpid = fork()) == 0) {
/* Child */
closeListeningSockets(0);
setupChildSignalHandlers();
} else {
/* 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) {
return -1;
}
updateDictResizePolicy();
}
return childpid;
}
void sendChildCOWInfo(int ptype, char *pname) {
size_t private_dirty = zmalloc_get_private_dirty(-1);
if (private_dirty) {
serverLog(LL_NOTICE,
"%s: %zu MB of memory used by copy-on-write",
pname, private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
sendChildInfo(ptype);
}
void memtest(size_t megabytes, int passes); void memtest(size_t megabytes, int passes);
/* Returns 1 if there is --sentinel among the arguments or if /* Returns 1 if there is --sentinel among the arguments or if
...@@ -4753,9 +4910,9 @@ int main(int argc, char **argv) { ...@@ -4753,9 +4910,9 @@ int main(int argc, char **argv) {
srand(time(NULL)^getpid()); srand(time(NULL)^getpid());
gettimeofday(&tv,NULL); gettimeofday(&tv,NULL);
char hashseed[16]; uint8_t hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed)); getRandomBytes(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed); dictSetHashFunctionSeed(hashseed);
server.sentinel_mode = checkForSentinelMode(argc,argv); server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig(); initServerConfig();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the ACLInit(); /* The ACL subsystem must be initialized ASAP because the
......
...@@ -132,6 +132,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -132,6 +132,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb" #define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0 #define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5 #define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1 #define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1 #define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1 #define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
...@@ -170,6 +171,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -170,6 +171,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */ #define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
#define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */ #define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */ #define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
#define CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL 10 /* 10% tracking table max fill. */
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */ #define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */ #define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
...@@ -177,6 +179,14 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -177,6 +179,14 @@ typedef long long mstime_t; /* millisecond time type. */
#define ACTIVE_EXPIRE_CYCLE_SLOW 0 #define ACTIVE_EXPIRE_CYCLE_SLOW 0
#define ACTIVE_EXPIRE_CYCLE_FAST 1 #define ACTIVE_EXPIRE_CYCLE_FAST 1
/* Children process will exit with this status code to signal that the
* process terminated without an error: this is useful in order to kill
* a saving child (RDB or AOF one), without triggering in the parent the
* write protection that is normally turned on on write errors.
* Usually children that are terminated with SIGUSR1 will exit with this
* special code. */
#define SERVER_CHILD_NOERROR_RETVAL 255
/* Instantaneous metrics tracking. */ /* Instantaneous metrics tracking. */
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */ #define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */ #define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
...@@ -218,35 +228,36 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -218,35 +228,36 @@ typedef long long mstime_t; /* millisecond time type. */
#define CMD_LOADING (1ULL<<9) /* "ok-loading" flag */ #define CMD_LOADING (1ULL<<9) /* "ok-loading" flag */
#define CMD_STALE (1ULL<<10) /* "ok-stale" flag */ #define CMD_STALE (1ULL<<10) /* "ok-stale" flag */
#define CMD_SKIP_MONITOR (1ULL<<11) /* "no-monitor" flag */ #define CMD_SKIP_MONITOR (1ULL<<11) /* "no-monitor" flag */
#define CMD_ASKING (1ULL<<12) /* "cluster-asking" flag */ #define CMD_SKIP_SLOWLOG (1ULL<<12) /* "no-slowlog" flag */
#define CMD_FAST (1ULL<<13) /* "fast" flag */ #define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
#define CMD_FAST (1ULL<<14) /* "fast" flag */
/* Command flags used by the module system. */ /* Command flags used by the module system. */
#define CMD_MODULE_GETKEYS (1ULL<<14) /* Use the modules getkeys interface. */ #define CMD_MODULE_GETKEYS (1ULL<<15) /* Use the modules getkeys interface. */
#define CMD_MODULE_NO_CLUSTER (1ULL<<15) /* Deny on Redis Cluster. */ #define CMD_MODULE_NO_CLUSTER (1ULL<<16) /* Deny on Redis Cluster. */
/* Command flags that describe ACLs categories. */ /* Command flags that describe ACLs categories. */
#define CMD_CATEGORY_KEYSPACE (1ULL<<16) #define CMD_CATEGORY_KEYSPACE (1ULL<<17)
#define CMD_CATEGORY_READ (1ULL<<17) #define CMD_CATEGORY_READ (1ULL<<18)
#define CMD_CATEGORY_WRITE (1ULL<<18) #define CMD_CATEGORY_WRITE (1ULL<<19)
#define CMD_CATEGORY_SET (1ULL<<19) #define CMD_CATEGORY_SET (1ULL<<20)
#define CMD_CATEGORY_SORTEDSET (1ULL<<20) #define CMD_CATEGORY_SORTEDSET (1ULL<<21)
#define CMD_CATEGORY_LIST (1ULL<<21) #define CMD_CATEGORY_LIST (1ULL<<22)
#define CMD_CATEGORY_HASH (1ULL<<22) #define CMD_CATEGORY_HASH (1ULL<<23)
#define CMD_CATEGORY_STRING (1ULL<<23) #define CMD_CATEGORY_STRING (1ULL<<24)
#define CMD_CATEGORY_BITMAP (1ULL<<24) #define CMD_CATEGORY_BITMAP (1ULL<<25)
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<25) #define CMD_CATEGORY_HYPERLOGLOG (1ULL<<26)
#define CMD_CATEGORY_GEO (1ULL<<26) #define CMD_CATEGORY_GEO (1ULL<<27)
#define CMD_CATEGORY_STREAM (1ULL<<27) #define CMD_CATEGORY_STREAM (1ULL<<28)
#define CMD_CATEGORY_PUBSUB (1ULL<<28) #define CMD_CATEGORY_PUBSUB (1ULL<<29)
#define CMD_CATEGORY_ADMIN (1ULL<<29) #define CMD_CATEGORY_ADMIN (1ULL<<30)
#define CMD_CATEGORY_FAST (1ULL<<30) #define CMD_CATEGORY_FAST (1ULL<<31)
#define CMD_CATEGORY_SLOW (1ULL<<31) #define CMD_CATEGORY_SLOW (1ULL<<32)
#define CMD_CATEGORY_BLOCKING (1ULL<<32) #define CMD_CATEGORY_BLOCKING (1ULL<<33)
#define CMD_CATEGORY_DANGEROUS (1ULL<<33) #define CMD_CATEGORY_DANGEROUS (1ULL<<34)
#define CMD_CATEGORY_CONNECTION (1ULL<<34) #define CMD_CATEGORY_CONNECTION (1ULL<<35)
#define CMD_CATEGORY_TRANSACTION (1ULL<<35) #define CMD_CATEGORY_TRANSACTION (1ULL<<36)
#define CMD_CATEGORY_SCRIPTING (1ULL<<36) #define CMD_CATEGORY_SCRIPTING (1ULL<<37)
/* AOF states */ /* AOF states */
#define AOF_OFF 0 /* AOF is off */ #define AOF_OFF 0 /* AOF is off */
...@@ -254,8 +265,8 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -254,8 +265,8 @@ typedef long long mstime_t; /* millisecond time type. */
#define AOF_WAIT_REWRITE 2 /* AOF waits rewrite to start appending */ #define AOF_WAIT_REWRITE 2 /* AOF waits rewrite to start appending */
/* Client flags */ /* Client flags */
#define CLIENT_SLAVE (1<<0) /* This client is a slave server */ #define CLIENT_SLAVE (1<<0) /* This client is a repliaca */
#define CLIENT_MASTER (1<<1) /* This client is a master server */ #define CLIENT_MASTER (1<<1) /* This client is a master */
#define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */ #define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */ #define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */
#define CLIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operation */ #define CLIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operation */
...@@ -289,7 +300,13 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -289,7 +300,13 @@ typedef long long mstime_t; /* millisecond time type. */
#define CLIENT_PENDING_READ (1<<29) /* The client has pending reads and was put #define CLIENT_PENDING_READ (1<<29) /* The client has pending reads and was put
in the list of clients we can read in the list of clients we can read
from. */ from. */
#define CLIENT_PENDING_COMMAND (1<<30) /* */ #define CLIENT_PENDING_COMMAND (1<<30) /* Used in threaded I/O to signal after
we return single threaded that the
client has already pending commands
to be executed. */
#define CLIENT_TRACKING (1<<31) /* Client enabled keys tracking in order to
perform client side caching. */
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
/* Client block type (btype field in client structure) /* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */ * if CLIENT_BLOCKED flag is set. */
...@@ -388,6 +405,12 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -388,6 +405,12 @@ typedef long long mstime_t; /* millisecond time type. */
#define AOF_FSYNC_EVERYSEC 2 #define AOF_FSYNC_EVERYSEC 2
#define CONFIG_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC #define CONFIG_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC
/* Replication diskless load defines */
#define REPL_DISKLESS_LOAD_DISABLED 0
#define REPL_DISKLESS_LOAD_WHEN_DB_EMPTY 1
#define REPL_DISKLESS_LOAD_SWAPDB 2
#define CONFIG_DEFAULT_REPL_DISKLESS_LOAD REPL_DISKLESS_LOAD_DISABLED
/* Zipped structures related defaults */ /* Zipped structures related defaults */
#define OBJ_HASH_MAX_ZIPLIST_ENTRIES 512 #define OBJ_HASH_MAX_ZIPLIST_ENTRIES 512
#define OBJ_HASH_MAX_ZIPLIST_VALUE 64 #define OBJ_HASH_MAX_ZIPLIST_VALUE 64
...@@ -523,6 +546,10 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -523,6 +546,10 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK) #define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS) #define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
/* Bit flags for moduleTypeAuxSaveFunc */
#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
#define REDISMODULE_AUX_AFTER_RDB (1<<1)
struct RedisModule; struct RedisModule;
struct RedisModuleIO; struct RedisModuleIO;
struct RedisModuleDigest; struct RedisModuleDigest;
...@@ -535,6 +562,8 @@ struct redisObject; ...@@ -535,6 +562,8 @@ struct redisObject;
* is deleted. */ * is deleted. */
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver); typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value); typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value); typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value); typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
typedef size_t (*moduleTypeMemUsageFunc)(const void *value); typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
...@@ -551,6 +580,9 @@ typedef struct RedisModuleType { ...@@ -551,6 +580,9 @@ typedef struct RedisModuleType {
moduleTypeMemUsageFunc mem_usage; moduleTypeMemUsageFunc mem_usage;
moduleTypeDigestFunc digest; moduleTypeDigestFunc digest;
moduleTypeFreeFunc free; moduleTypeFreeFunc free;
moduleTypeAuxLoadFunc aux_load;
moduleTypeAuxSaveFunc aux_save;
int aux_save_triggers;
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */ char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
} moduleType; } moduleType;
...@@ -646,6 +678,11 @@ typedef struct redisObject { ...@@ -646,6 +678,11 @@ typedef struct redisObject {
void *ptr; void *ptr;
} robj; } robj;
/* The a string name for an object's type as listed above
* Native types are checked against the OBJ_STRING, OBJ_LIST, OBJ_* defines,
* and Module types have their registered name returned. */
char *getObjectTypeName(robj*);
/* Macro used to initialize a Redis object allocated on the stack. /* Macro used to initialize a Redis object allocated on the stack.
* Note that this macro is taken near the structure definition to make sure * Note that this macro is taken near the structure definition to make sure
* we'll update it when the structure is changed, to avoid bugs like * we'll update it when the structure is changed, to avoid bugs like
...@@ -816,10 +853,10 @@ typedef struct client { ...@@ -816,10 +853,10 @@ typedef struct client {
time_t ctime; /* Client creation time. */ time_t ctime; /* Client creation time. */
time_t lastinteraction; /* Time of the last interaction, used for timeout */ time_t lastinteraction; /* Time of the last interaction, used for timeout */
time_t obuf_soft_limit_reached_time; time_t obuf_soft_limit_reached_time;
int flags; /* Client flags: CLIENT_* macros. */ uint64_t flags; /* Client flags: CLIENT_* macros. */
int authenticated; /* Needed when the default user requires auth. */ int authenticated; /* Needed when the default user requires auth. */
int replstate; /* Replication state if this is a slave. */ int replstate; /* Replication state if this is a slave. */
int repl_put_online_on_ack; /* Install slave write handler on ACK. */ int repl_put_online_on_ack; /* Install slave write handler on first ACK. */
int repldbfd; /* Replication DB file descriptor. */ int repldbfd; /* Replication DB file descriptor. */
off_t repldboff; /* Replication DB file offset. */ off_t repldboff; /* Replication DB file offset. */
off_t repldbsize; /* Replication DB file size. */ off_t repldbsize; /* Replication DB file size. */
...@@ -845,6 +882,11 @@ typedef struct client { ...@@ -845,6 +882,11 @@ typedef struct client {
sds peerid; /* Cached peer ID. */ sds peerid; /* Cached peer ID. */
listNode *client_list_node; /* list node in client list */ listNode *client_list_node; /* list node in client list */
/* If this client is in tracking mode and this field is non zero,
* invalidation messages for keys fetched by this client will be send to
* the specified client ID. */
uint64_t client_tracking_redirection;
/* Response buffer */ /* Response buffer */
int bufpos; int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES]; char buf[PROTO_REPLY_CHUNK_BYTES];
...@@ -863,7 +905,7 @@ struct moduleLoadQueueEntry { ...@@ -863,7 +905,7 @@ struct moduleLoadQueueEntry {
struct sharedObjectsStruct { struct sharedObjectsStruct {
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
*colon, *queued, *null[4], *nullarray[4], *colon, *queued, *null[4], *nullarray[4], *emptymap[4], *emptyset[4],
*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr, *masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
...@@ -1007,6 +1049,7 @@ struct clusterState; ...@@ -1007,6 +1049,7 @@ struct clusterState;
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL #define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
#define CHILD_INFO_TYPE_RDB 0 #define CHILD_INFO_TYPE_RDB 0
#define CHILD_INFO_TYPE_AOF 1 #define CHILD_INFO_TYPE_AOF 1
#define CHILD_INFO_TYPE_MODULE 3
struct redisServer { struct redisServer {
/* General */ /* General */
...@@ -1042,6 +1085,7 @@ struct redisServer { ...@@ -1042,6 +1085,7 @@ struct redisServer {
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
client blocked on a module command needs client blocked on a module command needs
to be processed. */ to be processed. */
pid_t module_child_pid; /* PID of module child */
/* Networking */ /* Networking */
int port; /* TCP listening port */ int port; /* TCP listening port */
int tcp_backlog; /* TCP listen() backlog */ int tcp_backlog; /* TCP listen() backlog */
...@@ -1115,6 +1159,7 @@ struct redisServer { ...@@ -1115,6 +1159,7 @@ struct redisServer {
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */ _Atomic long long stat_net_output_bytes; /* Bytes written to network. */
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */ size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */ size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
/* The following two are used to track instantaneous metrics, like /* The following two are used to track instantaneous metrics, like
* number of operations per second, network traffic. */ * number of operations per second, network traffic. */
struct { struct {
...@@ -1143,6 +1188,7 @@ struct redisServer { ...@@ -1143,6 +1188,7 @@ struct redisServer {
int daemonize; /* True if running as a daemon */ int daemonize; /* True if running as a daemon */
clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT]; clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];
/* AOF persistence */ /* AOF persistence */
int aof_enabled; /* AOF configuration */
int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */ int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */
int aof_fsync; /* Kind of fsync() policy */ int aof_fsync; /* Kind of fsync() policy */
char *aof_filename; /* Name of the AOF file */ char *aof_filename; /* Name of the AOF file */
...@@ -1199,6 +1245,8 @@ struct redisServer { ...@@ -1199,6 +1245,8 @@ struct redisServer {
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */ int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */ int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */ int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
int rdb_key_save_delay; /* Delay in microseconds between keys while
* writing the RDB. (for testings) */
/* Pipe and data structures for child -> parent info sharing. */ /* Pipe and data structures for child -> parent info sharing. */
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */ int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
struct { struct {
...@@ -1234,7 +1282,9 @@ struct redisServer { ...@@ -1234,7 +1282,9 @@ struct redisServer {
int repl_min_slaves_to_write; /* Min number of slaves to write. */ int repl_min_slaves_to_write; /* Min number of slaves to write. */
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */ int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */ int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
int repl_diskless_sync; /* Send RDB to slaves sockets directly. */ int repl_diskless_sync; /* Master send RDB to slaves sockets directly. */
int repl_diskless_load; /* Slave parse RDB directly from the socket.
* see REPL_DISKLESS_LOAD_* enum */
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */ int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
/* Replication (slave) */ /* Replication (slave) */
char *masteruser; /* AUTH with this user and masterauth with master */ char *masteruser; /* AUTH with this user and masterauth with master */
...@@ -1287,6 +1337,9 @@ struct redisServer { ...@@ -1287,6 +1337,9 @@ struct redisServer {
unsigned int blocked_clients_by_type[BLOCKED_NUM]; unsigned int blocked_clients_by_type[BLOCKED_NUM];
list *unblocked_clients; /* list of clients to unblock before next loop */ list *unblocked_clients; /* list of clients to unblock before next loop */
list *ready_keys; /* List of readyList structures for BLPOP & co */ list *ready_keys; /* List of readyList structures for BLPOP & co */
/* Client side caching. */
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
int tracking_table_max_fill; /* Max fill percentage. */
/* Sort parameters - qsort_r() is only available under BSD so we /* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */ * have to take this state global, in order to pass it to sortCompare() */
int sort_desc; int sort_desc;
...@@ -1337,6 +1390,7 @@ struct redisServer { ...@@ -1337,6 +1390,7 @@ struct redisServer {
lua_State *lua; /* The Lua interpreter. We use just one for all clients */ lua_State *lua; /* The Lua interpreter. We use just one for all clients */
client *lua_client; /* The "fake client" to query Redis from Lua */ client *lua_client; /* The "fake client" to query Redis from Lua */
client *lua_caller; /* The client running EVAL right now, or NULL */ client *lua_caller; /* The client running EVAL right now, or NULL */
char* lua_cur_script; /* SHA1 of the script currently running, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */ unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
mstime_t lua_time_limit; /* Script timeout in milliseconds */ mstime_t lua_time_limit; /* Script timeout in milliseconds */
...@@ -1499,6 +1553,11 @@ void moduleAcquireGIL(void); ...@@ -1499,6 +1553,11 @@ void moduleAcquireGIL(void);
void moduleReleaseGIL(void); void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid); void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c); void moduleCallCommandFilters(client *c);
void ModuleForkDoneHandler(int exitcode, int bysignal);
int TerminateModuleForkChild(int child_pid, int wait);
ssize_t rdbSaveModulesAux(rio *rdb, int when);
int moduleAllDatatypesHandleErrors();
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
/* Utils */ /* Utils */
long long ustime(void); long long ustime(void);
...@@ -1592,6 +1651,7 @@ void linkClient(client *c); ...@@ -1592,6 +1651,7 @@ void linkClient(client *c);
void protectClient(client *c); void protectClient(client *c);
void unprotectClient(client *c); void unprotectClient(client *c);
void initThreadedIO(void); void initThreadedIO(void);
client *lookupClientByID(uint64_t id);
#ifdef __GNUC__ #ifdef __GNUC__
void addReplyErrorFormat(client *c, const char *fmt, ...) void addReplyErrorFormat(client *c, const char *fmt, ...)
...@@ -1603,6 +1663,15 @@ void addReplyErrorFormat(client *c, const char *fmt, ...); ...@@ -1603,6 +1663,15 @@ void addReplyErrorFormat(client *c, const char *fmt, ...);
void addReplyStatusFormat(client *c, const char *fmt, ...); void addReplyStatusFormat(client *c, const char *fmt, ...);
#endif #endif
/* Client side caching (tracking mode) */
void enableTracking(client *c, uint64_t redirect_to);
void disableTracking(client *c);
void trackingRememberKeys(client *c);
void trackingInvalidateKey(robj *keyobj);
void trackingInvalidateKeysOnFlush(int dbid);
void trackingLimitUsedSlots(void);
unsigned long long trackingGetUsedSlots(void);
/* List data type */ /* List data type */
void listTypeTryConversion(robj *subject, robj *value); void listTypeTryConversion(robj *subject, robj *value);
void listTypePush(robj *subject, robj *value, int where); void listTypePush(robj *subject, robj *value, int where);
...@@ -1715,7 +1784,8 @@ void replicationCacheMasterUsingMyself(void); ...@@ -1715,7 +1784,8 @@ void replicationCacheMasterUsingMyself(void);
void feedReplicationBacklog(void *ptr, size_t len); void feedReplicationBacklog(void *ptr, size_t len);
/* Generic persistence functions */ /* Generic persistence functions */
void startLoading(FILE *fp); void startLoadingFile(FILE* fp, char* filename);
void startLoading(size_t size);
void loadingProgress(off_t pos); void loadingProgress(off_t pos);
void stopLoading(void); void stopLoading(void);
...@@ -1749,6 +1819,11 @@ void closeChildInfoPipe(void); ...@@ -1749,6 +1819,11 @@ void closeChildInfoPipe(void);
void sendChildInfo(int process_type); void sendChildInfo(int process_type);
void receiveChildInfo(void); void receiveChildInfo(void);
/* Fork helpers */
int redisFork();
int hasActiveChildProcess();
void sendChildCOWInfo(int ptype, char *pname);
/* acl.c -- Authentication related prototypes. */ /* acl.c -- Authentication related prototypes. */
extern rax *Users; extern rax *Users;
extern user *DefaultUser; extern user *DefaultUser;
...@@ -1849,6 +1924,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name); ...@@ -1849,6 +1924,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
void call(client *c, int flags); void call(client *c, int flags);
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags); void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target); void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
void redisOpArrayInit(redisOpArray *oa);
void redisOpArrayFree(redisOpArray *oa);
void forceCommandPropagation(client *c, int flags); void forceCommandPropagation(client *c, int flags);
void preventCommandPropagation(client *c); void preventCommandPropagation(client *c);
void preventCommandAOF(client *c); void preventCommandAOF(client *c);
...@@ -1927,6 +2004,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify); ...@@ -1927,6 +2004,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify);
void freePubsubPattern(void *p); void freePubsubPattern(void *p);
int listMatchPubsubPattern(void *a, void *b); int listMatchPubsubPattern(void *a, void *b);
int pubsubPublishMessage(robj *channel, robj *message); int pubsubPublishMessage(robj *channel, robj *message);
void addReplyPubsubMessage(client *c, robj *channel, robj *msg);
/* Keyspace events notification */ /* Keyspace events notification */
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid); void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
...@@ -1971,6 +2049,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o); ...@@ -1971,6 +2049,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
#define EMPTYDB_NO_FLAGS 0 /* No flags. */ #define EMPTYDB_NO_FLAGS 0 /* No flags. */
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */ #define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
long long emptyDb(int dbnum, int flags, void(callback)(void*)); long long emptyDb(int dbnum, int flags, void(callback)(void*));
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
long long dbTotalServerKeyCount();
int selectDb(client *c, int id); int selectDb(client *c, int id);
void signalModifiedKey(redisDb *db, robj *key); void signalModifiedKey(redisDb *db, robj *key);
...@@ -2064,6 +2144,7 @@ void dictSdsDestructor(void *privdata, void *val); ...@@ -2064,6 +2144,7 @@ void dictSdsDestructor(void *privdata, void *val);
char *redisGitSHA1(void); char *redisGitSHA1(void);
char *redisGitDirty(void); char *redisGitDirty(void);
uint64_t redisBuildId(void); uint64_t redisBuildId(void);
char *redisBuildIdString(void);
/* Commands prototypes */ /* Commands prototypes */
void authCommand(client *c); void authCommand(client *c);
...@@ -2278,6 +2359,7 @@ void bugReportStart(void); ...@@ -2278,6 +2359,7 @@ void bugReportStart(void);
void serverLogObjectDebugInfo(const robj *o); void serverLogObjectDebugInfo(const robj *o);
void sigsegvHandler(int sig, siginfo_t *info, void *secret); void sigsegvHandler(int sig, siginfo_t *info, void *secret);
sds genRedisInfoString(char *section); sds genRedisInfoString(char *section);
sds genModulesInfoString(sds info);
void enableWatchdog(int period); void enableWatchdog(int period);
void disableWatchdog(void); void disableWatchdog(void);
void watchdogScheduleSignal(int period); void watchdogScheduleSignal(int period);
......
/*********************************************************************
* Filename: sha256.c
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Implementation of the SHA-256 hashing algorithm.
SHA-256 is one of the three algorithms in the SHA2
specification. The others, SHA-384 and SHA-512, are not
offered in this implementation.
Algorithm specification can be found here:
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
This implementation uses little endian byte order.
*********************************************************************/
/*************************** HEADER FILES ***************************/
#include <stdlib.h>
#include <string.h>
#include "sha256.h"
/****************************** MACROS ******************************/
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
/**************************** VARIABLES *****************************/
static const WORD k[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
/*********************** FUNCTION DEFINITIONS ***********************/
void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
{
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
for (i = 0, j = 0; i < 16; ++i, j += 4)
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
for ( ; i < 64; ++i)
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
a = ctx->state[0];
b = ctx->state[1];
c = ctx->state[2];
d = ctx->state[3];
e = ctx->state[4];
f = ctx->state[5];
g = ctx->state[6];
h = ctx->state[7];
for (i = 0; i < 64; ++i) {
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
t2 = EP0(a) + MAJ(a,b,c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
ctx->state[0] += a;
ctx->state[1] += b;
ctx->state[2] += c;
ctx->state[3] += d;
ctx->state[4] += e;
ctx->state[5] += f;
ctx->state[6] += g;
ctx->state[7] += h;
}
void sha256_init(SHA256_CTX *ctx)
{
ctx->datalen = 0;
ctx->bitlen = 0;
ctx->state[0] = 0x6a09e667;
ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372;
ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f;
ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab;
ctx->state[7] = 0x5be0cd19;
}
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
{
WORD i;
for (i = 0; i < len; ++i) {
ctx->data[ctx->datalen] = data[i];
ctx->datalen++;
if (ctx->datalen == 64) {
sha256_transform(ctx, ctx->data);
ctx->bitlen += 512;
ctx->datalen = 0;
}
}
}
void sha256_final(SHA256_CTX *ctx, BYTE hash[])
{
WORD i;
i = ctx->datalen;
// Pad whatever data is left in the buffer.
if (ctx->datalen < 56) {
ctx->data[i++] = 0x80;
while (i < 56)
ctx->data[i++] = 0x00;
}
else {
ctx->data[i++] = 0x80;
while (i < 64)
ctx->data[i++] = 0x00;
sha256_transform(ctx, ctx->data);
memset(ctx->data, 0, 56);
}
// Append to the padding the total message's length in bits and transform.
ctx->bitlen += ctx->datalen * 8;
ctx->data[63] = ctx->bitlen;
ctx->data[62] = ctx->bitlen >> 8;
ctx->data[61] = ctx->bitlen >> 16;
ctx->data[60] = ctx->bitlen >> 24;
ctx->data[59] = ctx->bitlen >> 32;
ctx->data[58] = ctx->bitlen >> 40;
ctx->data[57] = ctx->bitlen >> 48;
ctx->data[56] = ctx->bitlen >> 56;
sha256_transform(ctx, ctx->data);
// Since this implementation uses little endian byte ordering and SHA uses big endian,
// reverse all the bytes when copying the final state to the output hash.
for (i = 0; i < 4; ++i) {
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
}
}
/*********************************************************************
* Filename: sha256.h
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Defines the API for the corresponding SHA1 implementation.
*********************************************************************/
#ifndef SHA256_H
#define SHA256_H
/*************************** HEADER FILES ***************************/
#include <stddef.h>
#include <stdint.h>
/****************************** MACROS ******************************/
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
/**************************** DATA TYPES ****************************/
typedef uint8_t BYTE; // 8-bit byte
typedef uint32_t WORD; // 32-bit word
typedef struct {
BYTE data[64];
WORD datalen;
unsigned long long bitlen;
WORD state[8];
} SHA256_CTX;
/*********************** FUNCTION DECLARATIONS **********************/
void sha256_init(SHA256_CTX *ctx);
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
#endif // SHA256_H
...@@ -58,7 +58,8 @@ int siptlw(int c) { ...@@ -58,7 +58,8 @@ int siptlw(int c) {
/* Test of the CPU is Little Endian and supports not aligned accesses. /* Test of the CPU is Little Endian and supports not aligned accesses.
* Two interesting conditions to speedup the function that happen to be * Two interesting conditions to speedup the function that happen to be
* in most of x86 servers. */ * in most of x86 servers. */
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) #if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) \
|| defined (__aarch64__) || defined (__arm64__)
#define UNALIGNED_LE_CPU #define UNALIGNED_LE_CPU
#endif #endif
......
...@@ -109,5 +109,6 @@ streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id); ...@@ -109,5 +109,6 @@ streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
streamNACK *streamCreateNACK(streamConsumer *consumer); streamNACK *streamCreateNACK(streamConsumer *consumer);
void streamDecodeID(void *buf, streamID *id); void streamDecodeID(void *buf, streamID *id);
int streamCompareID(streamID *a, streamID *b); int streamCompareID(streamID *a, streamID *b);
void streamFreeNACK(streamNACK *na);
#endif #endif
...@@ -772,8 +772,8 @@ void genericHgetallCommand(client *c, int flags) { ...@@ -772,8 +772,8 @@ void genericHgetallCommand(client *c, int flags) {
hashTypeIterator *hi; hashTypeIterator *hi;
int length, count = 0; int length, count = 0;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymap[c->resp]))
|| checkType(c,o,OBJ_HASH)) return; == NULL || checkType(c,o,OBJ_HASH)) return;
/* We return a map if the user requested keys and values, like in the /* We return a map if the user requested keys and values, like in the
* HGETALL case. Otherwise to use a flat array makes more sense. */ * HGETALL case. Otherwise to use a flat array makes more sense. */
......
...@@ -402,7 +402,7 @@ void lrangeCommand(client *c) { ...@@ -402,7 +402,7 @@ void lrangeCommand(client *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) || if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return; (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL
|| checkType(c,o,OBJ_LIST)) return; || checkType(c,o,OBJ_LIST)) return;
llen = listTypeLength(o); llen = listTypeLength(o);
...@@ -414,7 +414,7 @@ void lrangeCommand(client *c) { ...@@ -414,7 +414,7 @@ void lrangeCommand(client *c) {
/* Invariant: start >= 0, so this test will be true when end < 0. /* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) { if (start > end || start >= llen) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
...@@ -606,7 +606,7 @@ void rpoplpushCommand(client *c) { ...@@ -606,7 +606,7 @@ void rpoplpushCommand(client *c) {
* Blocking POP operations * Blocking POP operations
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* This is a helper function for handleClientsBlockedOnLists(). It's work /* This is a helper function for handleClientsBlockedOnKeys(). It's work
* is to serve a specific client (receiver) that is blocked on 'key' * is to serve a specific client (receiver) that is blocked on 'key'
* in the context of the specified 'db', doing the following: * in the context of the specified 'db', doing the following:
* *
......
...@@ -418,10 +418,10 @@ void spopWithCountCommand(client *c) { ...@@ -418,10 +418,10 @@ void spopWithCountCommand(client *c) {
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp])) if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return; == NULL || checkType(c,set,OBJ_SET)) return;
/* If count is zero, serve an empty multibulk ASAP to avoid special /* If count is zero, serve an empty set ASAP to avoid special
* cases later. */ * cases later. */
if (count == 0) { if (count == 0) {
addReplyNull(c); addReply(c,shared.emptyset[c->resp]);
return; return;
} }
...@@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) { ...@@ -632,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
uniq = 0; uniq = 0;
} }
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyset[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return; == NULL || checkType(c,set,OBJ_SET)) return;
size = setTypeSize(set); size = setTypeSize(set);
/* If count is zero, serve it ASAP to avoid special cases later. */ /* If count is zero, serve it ASAP to avoid special cases later. */
if (count == 0) { if (count == 0) {
addReplyNull(c); addReply(c,shared.emptyset[c->resp]);
return; return;
} }
...@@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys, ...@@ -813,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
} }
addReply(c,shared.czero); addReply(c,shared.czero);
} else { } else {
addReplyNull(c); addReply(c,shared.emptyset[c->resp]);
} }
return; return;
} }
......
...@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) { ...@@ -1357,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Optimize: check if the element is too large or the list /* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */ * becomes too long *before* executing zzlInsert. */
zobj->ptr = zzlInsert(zobj->ptr,ele,score); zobj->ptr = zzlInsert(zobj->ptr,ele,score);
if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries) if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); sdslen(ele) > server.zset_max_ziplist_value)
if (sdslen(ele) > server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST); zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
if (newscore) *newscore = score; if (newscore) *newscore = score;
*flags |= ZADD_ADDED; *flags |= ZADD_ADDED;
...@@ -2427,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) { ...@@ -2427,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) {
return; return;
} }
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL
|| checkType(c,zobj,OBJ_ZSET)) return; || checkType(c,zobj,OBJ_ZSET)) return;
/* Sanitize indexes. */ /* Sanitize indexes. */
...@@ -2439,7 +2438,7 @@ void zrangeGenericCommand(client *c, int reverse) { ...@@ -2439,7 +2438,7 @@ void zrangeGenericCommand(client *c, int reverse) {
/* Invariant: start >= 0, so this test will be true when end < 0. /* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) { if (start > end || start >= llen) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
if (end >= llen) end = llen-1; if (end >= llen) end = llen-1;
...@@ -2575,7 +2574,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { ...@@ -2575,7 +2574,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
} }
/* Ok, lookup the key and get the range */ /* Ok, lookup the key and get the range */
if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL || if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
checkType(c,zobj,OBJ_ZSET)) return; checkType(c,zobj,OBJ_ZSET)) return;
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) { if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
...@@ -2595,7 +2594,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { ...@@ -2595,7 +2594,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (eptr == NULL) { if (eptr == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
...@@ -2662,7 +2661,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) { ...@@ -2662,7 +2661,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (ln == NULL) { if (ln == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
...@@ -2920,7 +2919,7 @@ void genericZrangebylexCommand(client *c, int reverse) { ...@@ -2920,7 +2919,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
} }
/* Ok, lookup the key and get the range */ /* Ok, lookup the key and get the range */
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))
{ {
zslFreeLexRange(&range); zslFreeLexRange(&range);
...@@ -2943,7 +2942,7 @@ void genericZrangebylexCommand(client *c, int reverse) { ...@@ -2943,7 +2942,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (eptr == NULL) { if (eptr == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
zslFreeLexRange(&range); zslFreeLexRange(&range);
return; return;
} }
...@@ -3007,7 +3006,7 @@ void genericZrangebylexCommand(client *c, int reverse) { ...@@ -3007,7 +3006,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */ /* No "first" element in the specified interval. */
if (ln == NULL) { if (ln == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
zslFreeLexRange(&range); zslFreeLexRange(&range);
return; return;
} }
...@@ -3161,7 +3160,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey ...@@ -3161,7 +3160,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
/* No candidate for zpopping, return empty. */ /* No candidate for zpopping, return empty. */
if (!zobj) { if (!zobj) {
addReplyNull(c); addReply(c,shared.emptyarray);
return; return;
} }
......
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