Unverified Commit fb4e0d40 authored by Oran Agra's avatar Oran Agra Committed by GitHub
Browse files

Merge pull request #10532 from oranagra/7.0-rc3

Release 7.0 rc3
parents d2b5a579 8b242ef9
...@@ -905,9 +905,9 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { ...@@ -905,9 +905,9 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
return raxGenericInsert(rax,s,len,data,old,1); return raxGenericInsert(rax,s,len,data,old,1);
} }
/* Non overwriting insert function: this if an element with the same key /* Non overwriting insert function: if an element with the same key
* exists, the value is not updated and the function returns 0. * exists, the value is not updated and the function returns 0.
* This is a just a wrapper for raxGenericInsert(). */ * This is just a wrapper for raxGenericInsert(). */
int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
return raxGenericInsert(rax,s,len,data,old,0); return raxGenericInsert(rax,s,len,data,old,0);
} }
......
...@@ -1242,24 +1242,9 @@ ssize_t rdbSaveFunctions(rio *rdb) { ...@@ -1242,24 +1242,9 @@ ssize_t rdbSaveFunctions(rio *rdb) {
ssize_t written = 0; ssize_t written = 0;
ssize_t ret; ssize_t ret;
while ((entry = dictNext(iter))) { while ((entry = dictNext(iter))) {
if ((ret = rdbSaveType(rdb, RDB_OPCODE_FUNCTION)) < 0) goto werr; if ((ret = rdbSaveType(rdb, RDB_OPCODE_FUNCTION2)) < 0) goto werr;
written += ret; written += ret;
functionLibInfo *li = dictGetVal(entry); functionLibInfo *li = dictGetVal(entry);
if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->name, sdslen(li->name))) < 0) goto werr;
written += ret;
if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->ei->name, sdslen(li->ei->name))) < 0) goto werr;
written += ret;
if (li->desc) {
/* desc exists */
if ((ret = rdbSaveLen(rdb, 1)) < 0) goto werr;
written += ret;
if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->desc, sdslen(li->desc))) < 0) goto werr;
written += ret;
} else {
/* desc not exists */
if ((ret = rdbSaveLen(rdb, 0)) < 0) goto werr;
written += ret;
}
if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->code, sdslen(li->code))) < 0) goto werr; if ((ret = rdbSaveRawString(rdb, (unsigned char *) li->code, sdslen(li->code))) < 0) goto werr;
written += ret; written += ret;
} }
...@@ -2811,56 +2796,79 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) { ...@@ -2811,56 +2796,79 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
* *
* The lib_ctx argument is also optional. If NULL is given, only verify rdb * The lib_ctx argument is also optional. If NULL is given, only verify rdb
* structure with out performing the actual functions loading. */ * structure with out performing the actual functions loading. */
int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int rdbflags, sds *err) { int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int type, int rdbflags, sds *err) {
UNUSED(ver); UNUSED(ver);
sds error = NULL;
sds final_payload = NULL;
int res = C_ERR;
if (type == RDB_OPCODE_FUNCTION) {
/* RDB that was generated on versions 7.0 rc1 and 7.0 rc2 has another
* an old format that contains the library name, engine and description.
* To support this format we must read those values. */
sds name = NULL; sds name = NULL;
sds engine_name = NULL; sds engine_name = NULL;
sds desc = NULL; sds desc = NULL;
sds blob = NULL; sds blob = NULL;
uint64_t has_desc; uint64_t has_desc;
sds error = NULL;
int res = C_ERR;
if (!(name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { if (!(name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) {
error = sdsnew("Failed loading library name"); error = sdsnew("Failed loading library name");
goto error; goto cleanup;
} }
if (!(engine_name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { if (!(engine_name = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) {
error = sdsnew("Failed loading engine name"); error = sdsnew("Failed loading engine name");
goto error; goto cleanup;
} }
if ((has_desc = rdbLoadLen(rdb, NULL)) == RDB_LENERR) { if ((has_desc = rdbLoadLen(rdb, NULL)) == RDB_LENERR) {
error = sdsnew("Failed loading library description indicator"); error = sdsnew("Failed loading library description indicator");
goto error; goto cleanup;
} }
if (has_desc && !(desc = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { if (has_desc && !(desc = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) {
error = sdsnew("Failed loading library description"); error = sdsnew("Failed loading library description");
goto error; goto cleanup;
} }
if (!(blob = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) { if (!(blob = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) {
error = sdsnew("Failed loading library blob"); error = sdsnew("Failed loading library blob");
goto error; goto cleanup;
}
/* Translate old format (versions 7.0 rc1 and 7.0 rc2) to new format.
* The new format has the library name and engine inside the script payload.
* Add those parameters to the original script payload (ignore the description if exists). */
final_payload = sdscatfmt(sdsempty(), "#!%s name=%s\n%s", engine_name, name, blob);
cleanup:
if (name) sdsfree(name);
if (engine_name) sdsfree(engine_name);
if (desc) sdsfree(desc);
if (blob) sdsfree(blob);
if (error) goto done;
} else if (type == RDB_OPCODE_FUNCTION2) {
if (!(final_payload = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) {
error = sdsnew("Failed loading library payload");
goto done;
}
} else {
serverPanic("Bad function type was given to rdbFunctionLoad");
} }
if (lib_ctx) { if (lib_ctx) {
if (functionsCreateWithLibraryCtx(name, engine_name, desc, blob, rdbflags & RDBFLAGS_ALLOW_DUP, &error, lib_ctx) != C_OK) { sds library_name = NULL;
if (!(library_name = functionsCreateWithLibraryCtx(final_payload, rdbflags & RDBFLAGS_ALLOW_DUP, &error, lib_ctx))) {
if (!error) { if (!error) {
error = sdsnew("Failed creating the library"); error = sdsnew("Failed creating the library");
} }
goto error; goto done;
} }
sdsfree(library_name);
} }
res = C_OK; res = C_OK;
error: done:
if (name) sdsfree(name); if (final_payload) sdsfree(final_payload);
if (engine_name) sdsfree(engine_name);
if (desc) sdsfree(desc);
if (blob) sdsfree(blob);
if (error) { if (error) {
if (err) { if (err) {
*err = error; *err = error;
...@@ -3091,9 +3099,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin ...@@ -3091,9 +3099,9 @@ int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadin
decrRefCount(aux); decrRefCount(aux);
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} }
} else if (type == RDB_OPCODE_FUNCTION) { } else if (type == RDB_OPCODE_FUNCTION || type == RDB_OPCODE_FUNCTION2) {
sds err = NULL; sds err = NULL;
if (rdbFunctionLoad(rdb, rdbver, rdb_loading_ctx->functions_lib_ctx, rdbflags, &err) != C_OK) { if (rdbFunctionLoad(rdb, rdbver, rdb_loading_ctx->functions_lib_ctx, type, rdbflags, &err) != C_OK) {
serverLog(LL_WARNING,"Failed loading library, %s", err); serverLog(LL_WARNING,"Failed loading library, %s", err);
sdsfree(err); sdsfree(err);
goto eoferr; goto eoferr;
......
...@@ -101,7 +101,8 @@ ...@@ -101,7 +101,8 @@
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 19)) #define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 19))
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */ /* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_FUNCTION 246 /* engine data */ #define RDB_OPCODE_FUNCTION2 245 /* function library data */
#define RDB_OPCODE_FUNCTION 246 /* old function library data for 7.0 rc1 and rc2 */
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */ #define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
#define RDB_OPCODE_IDLE 248 /* LRU idle time. */ #define RDB_OPCODE_IDLE 248 /* LRU idle time. */
#define RDB_OPCODE_FREQ 249 /* LFU frequency. */ #define RDB_OPCODE_FREQ 249 /* LFU frequency. */
...@@ -170,7 +171,7 @@ int rdbSaveBinaryFloatValue(rio *rdb, float val); ...@@ -170,7 +171,7 @@ int rdbSaveBinaryFloatValue(rio *rdb, float val);
int rdbLoadBinaryFloatValue(rio *rdb, float *val); int rdbLoadBinaryFloatValue(rio *rdb, float *val);
int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi); int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi);
int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadingCtx *rdb_loading_ctx); int rdbLoadRioWithLoadingCtx(rio *rdb, int rdbflags, rdbSaveInfo *rsi, rdbLoadingCtx *rdb_loading_ctx);
int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int rdbflags, sds *err); int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int type, int rdbflags, sds *err);
int rdbSaveRio(int req, rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi); int rdbSaveRio(int req, rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi);
ssize_t rdbSaveFunctions(rio *rdb); ssize_t rdbSaveFunctions(rio *rdb);
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi); rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
......
...@@ -1118,6 +1118,9 @@ static clusterNode **addClusterNode(clusterNode *node) { ...@@ -1118,6 +1118,9 @@ static clusterNode **addClusterNode(clusterNode *node) {
return config.cluster_nodes; return config.cluster_nodes;
} }
/* TODO: This should be refactored to use CLUSTER SLOTS, the migrating/importing
* information is anyway not used.
*/
static int fetchClusterConfiguration() { static int fetchClusterConfiguration() {
int success = 1; int success = 1;
redisContext *ctx = NULL; redisContext *ctx = NULL;
...@@ -1179,7 +1182,7 @@ static int fetchClusterConfiguration() { ...@@ -1179,7 +1182,7 @@ static int fetchClusterConfiguration() {
clusterNode *node = NULL; clusterNode *node = NULL;
char *ip = NULL; char *ip = NULL;
int port = 0; int port = 0;
char *paddr = strchr(addr, ':'); char *paddr = strrchr(addr, ':');
if (paddr != NULL) { if (paddr != NULL) {
*paddr = '\0'; *paddr = '\0';
ip = addr; ip = addr;
......
...@@ -63,6 +63,7 @@ struct { ...@@ -63,6 +63,7 @@ struct {
#define RDB_CHECK_DOING_READ_LEN 6 #define RDB_CHECK_DOING_READ_LEN 6
#define RDB_CHECK_DOING_READ_AUX 7 #define RDB_CHECK_DOING_READ_AUX 7
#define RDB_CHECK_DOING_READ_MODULE_AUX 8 #define RDB_CHECK_DOING_READ_MODULE_AUX 8
#define RDB_CHECK_DOING_READ_FUNCTIONS 9
char *rdb_check_doing_string[] = { char *rdb_check_doing_string[] = {
"start", "start",
...@@ -73,7 +74,8 @@ char *rdb_check_doing_string[] = { ...@@ -73,7 +74,8 @@ char *rdb_check_doing_string[] = {
"check-sum", "check-sum",
"read-len", "read-len",
"read-aux", "read-aux",
"read-module-aux" "read-module-aux",
"read-functions"
}; };
char *rdb_type_string[] = { char *rdb_type_string[] = {
...@@ -303,9 +305,10 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -303,9 +305,10 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
robj *o = rdbLoadCheckModuleValue(&rdb,name); robj *o = rdbLoadCheckModuleValue(&rdb,name);
decrRefCount(o); decrRefCount(o);
continue; /* Read type again. */ continue; /* Read type again. */
} else if (type == RDB_OPCODE_FUNCTION) { } else if (type == RDB_OPCODE_FUNCTION || type == RDB_OPCODE_FUNCTION2) {
sds err = NULL; sds err = NULL;
if (rdbFunctionLoad(&rdb, rdbver, NULL, 0, &err) != C_OK) { rdbstate.doing = RDB_CHECK_DOING_READ_FUNCTIONS;
if (rdbFunctionLoad(&rdb, rdbver, NULL, type, 0, &err) != C_OK) {
rdbCheckError("Failed loading library, %s", err); rdbCheckError("Failed loading library, %s", err);
sdsfree(err); sdsfree(err);
goto err; goto err;
......
...@@ -70,6 +70,7 @@ ...@@ -70,6 +70,7 @@
#define OUTPUT_RAW 1 #define OUTPUT_RAW 1
#define OUTPUT_CSV 2 #define OUTPUT_CSV 2
#define OUTPUT_JSON 3 #define OUTPUT_JSON 3
#define OUTPUT_QUOTED_JSON 4
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */ #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */ #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE" #define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
...@@ -155,6 +156,9 @@ ...@@ -155,6 +156,9 @@
#define CC_FORCE (1<<0) /* Re-connect if already connected. */ #define CC_FORCE (1<<0) /* Re-connect if already connected. */
#define CC_QUIET (1<<1) /* Don't log connecting errors. */ #define CC_QUIET (1<<1) /* Don't log connecting errors. */
/* DNS lookup */
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46 */
/* --latency-dist palettes. */ /* --latency-dist palettes. */
int spectrum_palette_color_size = 19; int spectrum_palette_color_size = 19;
int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196}; int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
...@@ -281,7 +285,7 @@ static void usage(int err); ...@@ -281,7 +285,7 @@ static void usage(int err);
static void slaveMode(void); static void slaveMode(void);
char *redisGitSHA1(void); char *redisGitSHA1(void);
char *redisGitDirty(void); char *redisGitDirty(void);
static int cliConnect(int force); static int cliConnect(int flags);
static char *getInfoField(char *info, char *field); static char *getInfoField(char *info, char *field);
static long getLongInfoField(char *info, char *field); static long getLongInfoField(char *info, char *field);
...@@ -799,7 +803,12 @@ static void cliInitHelp(void) { ...@@ -799,7 +803,12 @@ static void cliInitHelp(void) {
redisReply *commandTable; redisReply *commandTable;
dict *groups; dict *groups;
if (cliConnect(CC_QUIET) == REDIS_ERR) return; if (cliConnect(CC_QUIET) == REDIS_ERR) {
/* Can not connect to the server, but we still want to provide
* help, generate it only from the old help.h data instead. */
cliOldInitHelp();
return;
}
commandTable = redisCommand(context, "COMMAND DOCS"); commandTable = redisCommand(context, "COMMAND DOCS");
if (commandTable == NULL || commandTable->type == REDIS_REPLY_ERROR) { if (commandTable == NULL || commandTable->type == REDIS_REPLY_ERROR) {
/* New COMMAND DOCS subcommand not supported - generate help from old help.h data instead. */ /* New COMMAND DOCS subcommand not supported - generate help from old help.h data instead. */
...@@ -869,6 +878,12 @@ static void cliOutputHelp(int argc, char **argv) { ...@@ -869,6 +878,12 @@ static void cliOutputHelp(int argc, char **argv) {
group = argv[0]+1; group = argv[0]+1;
} }
if (helpEntries == NULL) {
/* Initialize the help using the results of the COMMAND command.
* In case we are using redis-cli help XXX, we need to init it. */
cliInitHelp();
}
assert(argc > 0); assert(argc > 0);
for (i = 0; i < helpEntriesLen; i++) { for (i = 0; i < helpEntriesLen; i++) {
entry = &helpEntries[i]; entry = &helpEntries[i];
...@@ -1486,16 +1501,39 @@ static sds cliFormatReplyCSV(redisReply *r) { ...@@ -1486,16 +1501,39 @@ static sds cliFormatReplyCSV(redisReply *r) {
return out; return out;
} }
static sds cliFormatReplyJson(sds out, redisReply *r) { /* Append specified buffer to out and return it, using required JSON output
* mode. */
static sds jsonStringOutput(sds out, const char *p, int len, int mode) {
if (mode == OUTPUT_JSON) {
return escapeJsonString(out, p, len);
} else if (mode == OUTPUT_QUOTED_JSON) {
/* Need to double-quote backslashes */
sds tmp = sdscatrepr(sdsempty(), p, len);
int tmplen = sdslen(tmp);
char *n = tmp;
while (tmplen--) {
if (*n == '\\') out = sdscatlen(out, "\\\\", 2);
else out = sdscatlen(out, n, 1);
n++;
}
sdsfree(tmp);
return out;
} else {
assert(0);
}
}
static sds cliFormatReplyJson(sds out, redisReply *r, int mode) {
unsigned int i; unsigned int i;
switch (r->type) { switch (r->type) {
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
out = sdscat(out,"error:"); out = sdscat(out,"error:");
out = sdscatrepr(out,r->str,strlen(r->str)); out = jsonStringOutput(out,r->str,strlen(r->str),mode);
break; break;
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
out = sdscatrepr(out,r->str,r->len); out = jsonStringOutput(out,r->str,r->len,mode);
break; break;
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer); out = sdscatprintf(out,"%lld",r->integer);
...@@ -1505,7 +1543,7 @@ static sds cliFormatReplyJson(sds out, redisReply *r) { ...@@ -1505,7 +1543,7 @@ static sds cliFormatReplyJson(sds out, redisReply *r) {
break; break;
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB: case REDIS_REPLY_VERB:
out = sdscatrepr(out,r->str,r->len); out = jsonStringOutput(out,r->str,r->len,mode);
break; break;
case REDIS_REPLY_NIL: case REDIS_REPLY_NIL:
out = sdscat(out,"null"); out = sdscat(out,"null");
...@@ -1518,7 +1556,7 @@ static sds cliFormatReplyJson(sds out, redisReply *r) { ...@@ -1518,7 +1556,7 @@ static sds cliFormatReplyJson(sds out, redisReply *r) {
case REDIS_REPLY_PUSH: case REDIS_REPLY_PUSH:
out = sdscat(out,"["); out = sdscat(out,"[");
for (i = 0; i < r->elements; i++ ) { for (i = 0; i < r->elements; i++ ) {
out = cliFormatReplyJson(out, r->element[i]); out = cliFormatReplyJson(out,r->element[i],mode);
if (i != r->elements-1) out = sdscat(out,","); if (i != r->elements-1) out = sdscat(out,",");
} }
out = sdscat(out,"]"); out = sdscat(out,"]");
...@@ -1527,20 +1565,25 @@ static sds cliFormatReplyJson(sds out, redisReply *r) { ...@@ -1527,20 +1565,25 @@ static sds cliFormatReplyJson(sds out, redisReply *r) {
out = sdscat(out,"{"); out = sdscat(out,"{");
for (i = 0; i < r->elements; i += 2) { for (i = 0; i < r->elements; i += 2) {
redisReply *key = r->element[i]; redisReply *key = r->element[i];
if (key->type == REDIS_REPLY_STATUS || if (key->type == REDIS_REPLY_ERROR ||
key->type == REDIS_REPLY_STATUS ||
key->type == REDIS_REPLY_STRING || key->type == REDIS_REPLY_STRING ||
key->type == REDIS_REPLY_VERB) { key->type == REDIS_REPLY_VERB)
out = cliFormatReplyJson(out, key); {
out = cliFormatReplyJson(out,key,mode);
} else { } else {
/* According to JSON spec, JSON map keys must be strings, */ /* According to JSON spec, JSON map keys must be strings,
/* and in RESP3, they can be other types. */ * and in RESP3, they can be other types.
sds tmp = cliFormatReplyJson(sdsempty(), key); * The first one(cliFormatReplyJson) is to convert non string type to string
out = sdscatrepr(out,tmp,sdslen(tmp)); * The Second one(escapeJsonString) is to escape the converted string */
sdsfree(tmp); sds keystr = cliFormatReplyJson(sdsempty(),key,mode);
if (keystr[0] == '"') out = sdscatsds(out,keystr);
else out = sdscatfmt(out,"\"%S\"",keystr);
sdsfree(keystr);
} }
out = sdscat(out,":"); out = sdscat(out,":");
out = cliFormatReplyJson(out, r->element[i+1]); out = cliFormatReplyJson(out,r->element[i+1],mode);
if (i != r->elements-2) out = sdscat(out,","); if (i != r->elements-2) out = sdscat(out,",");
} }
out = sdscat(out,"}"); out = sdscat(out,"}");
...@@ -1566,8 +1609,8 @@ static sds cliFormatReply(redisReply *reply, int mode, int verbatim) { ...@@ -1566,8 +1609,8 @@ static sds cliFormatReply(redisReply *reply, int mode, int verbatim) {
} else if (mode == OUTPUT_CSV) { } else if (mode == OUTPUT_CSV) {
out = cliFormatReplyCSV(reply); out = cliFormatReplyCSV(reply);
out = sdscatlen(out, "\n", 1); out = sdscatlen(out, "\n", 1);
} else if (mode == OUTPUT_JSON) { } else if (mode == OUTPUT_JSON || mode == OUTPUT_QUOTED_JSON) {
out = cliFormatReplyJson(sdsempty(), reply); out = cliFormatReplyJson(sdsempty(), reply, mode);
out = sdscatlen(out, "\n", 1); out = sdscatlen(out, "\n", 1);
} else { } else {
fprintf(stderr, "Error: Unknown output encoding %d\n", mode); fprintf(stderr, "Error: Unknown output encoding %d\n", mode);
...@@ -1684,12 +1727,6 @@ static int cliSendCommand(int argc, char **argv, long repeat) { ...@@ -1684,12 +1727,6 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
size_t *argvlen; size_t *argvlen;
int j, output_raw; int j, output_raw;
if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */
(!strcasecmp(command,"help") || !strcasecmp(command,"?"))) {
cliOutputHelp(--argc, ++argv);
return REDIS_OK;
}
if (context == NULL) return REDIS_ERR; if (context == NULL) return REDIS_ERR;
output_raw = 0; output_raw = 0;
...@@ -1953,11 +1990,17 @@ static int parseOptions(int argc, char **argv) { ...@@ -1953,11 +1990,17 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--csv")) { } else if (!strcmp(argv[i],"--csv")) {
config.output = OUTPUT_CSV; config.output = OUTPUT_CSV;
} else if (!strcmp(argv[i],"--json")) { } else if (!strcmp(argv[i],"--json")) {
/* Not overwrite explicit value by -3*/ /* Not overwrite explicit value by -3 */
if (config.resp3 == 0) { if (config.resp3 == 0) {
config.resp3 = 2; config.resp3 = 2;
} }
config.output = OUTPUT_JSON; config.output = OUTPUT_JSON;
} else if (!strcmp(argv[i],"--quoted-json")) {
/* Not overwrite explicit value by -3*/
if (config.resp3 == 0) {
config.resp3 = 2;
}
config.output = OUTPUT_QUOTED_JSON;
} else if (!strcmp(argv[i],"--latency")) { } else if (!strcmp(argv[i],"--latency")) {
config.latency_mode = 1; config.latency_mode = 1;
} else if (!strcmp(argv[i],"--latency-dist")) { } else if (!strcmp(argv[i],"--latency-dist")) {
...@@ -2289,6 +2332,7 @@ static void usage(int err) { ...@@ -2289,6 +2332,7 @@ static void usage(int err) {
" --quoted-input Force input to be handled as quoted strings.\n" " --quoted-input Force input to be handled as quoted strings.\n"
" --csv Output in CSV format.\n" " --csv Output in CSV format.\n"
" --json Output in JSON format (default RESP3, use -2 if you want to use with RESP2).\n" " --json Output in JSON format (default RESP3, use -2 if you want to use with RESP2).\n"
" --quoted-json Same as --json, but produce ASCII-safe quoted strings, not Unicode.\n"
" --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n" " --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n"
" STDOUT is a tty but can be overridden with --show-pushes no.\n" " STDOUT is a tty but can be overridden with --show-pushes no.\n"
" --stat Print rolling stats about server: mem, clients, ...\n",version); " --stat Print rolling stats about server: mem, clients, ...\n",version);
...@@ -2384,6 +2428,17 @@ static int confirmWithYes(char *msg, int ignore_force) { ...@@ -2384,6 +2428,17 @@ static int confirmWithYes(char *msg, int ignore_force) {
} }
static int issueCommandRepeat(int argc, char **argv, long repeat) { static int issueCommandRepeat(int argc, char **argv, long repeat) {
/* In Lua debugging mode, we want to pass the "help" to Redis to get
* it's own HELP message, rather than handle it by the CLI, see ldbRepl.
*
* For the normal Redis HELP, we can process it without a connection. */
if (!config.eval_ldb &&
(!strcasecmp(argv[0],"help") || !strcasecmp(argv[0],"?")))
{
cliOutputHelp(--argc, ++argv);
return REDIS_OK;
}
while (1) { while (1) {
if (config.cluster_reissue_command || context == NULL || if (config.cluster_reissue_command || context == NULL ||
context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF) context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF)
...@@ -2403,6 +2458,8 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) { ...@@ -2403,6 +2458,8 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
} }
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) { if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
cliPrintContextError(); cliPrintContextError();
redisFree(context);
context = NULL;
return REDIS_ERR; return REDIS_ERR;
} }
...@@ -2540,8 +2597,13 @@ static void repl(void) { ...@@ -2540,8 +2597,13 @@ static void repl(void) {
int argc; int argc;
sds *argv; sds *argv;
/* There is no need to initialize redis HELP when we are in lua debugger mode.
* It has its own HELP and commands (COMMAND or COMMAND DOCS will fail and got nothing).
* We will initialize the redis HELP after the Lua debugging session ended.*/
if (!config.eval_ldb) {
/* Initialize the help using the results of the COMMAND command. */ /* Initialize the help using the results of the COMMAND command. */
cliInitHelp(); cliInitHelp();
}
config.interactive = 1; config.interactive = 1;
linenoiseSetMultiLine(1); linenoiseSetMultiLine(1);
...@@ -2643,6 +2705,7 @@ static void repl(void) { ...@@ -2643,6 +2705,7 @@ static void repl(void) {
printf("\n(Lua debugging session ended%s)\n\n", printf("\n(Lua debugging session ended%s)\n\n",
config.eval_ldb_sync ? "" : config.eval_ldb_sync ? "" :
" -- dataset changes rolled back"); " -- dataset changes rolled back");
cliInitHelp();
} }
elapsed = mstime()-start_time; elapsed = mstime()-start_time;
...@@ -2695,7 +2758,7 @@ static int noninteractive(int argc, char **argv) { ...@@ -2695,7 +2758,7 @@ static int noninteractive(int argc, char **argv) {
retval = issueCommand(argc, sds_args); retval = issueCommand(argc, sds_args);
sdsfreesplitres(sds_args, argc); sdsfreesplitres(sds_args, argc);
return retval; return retval == REDIS_OK ? 0 : 1;
} }
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
...@@ -2782,7 +2845,7 @@ static int evalMode(int argc, char **argv) { ...@@ -2782,7 +2845,7 @@ static int evalMode(int argc, char **argv) {
break; /* Return to the caller. */ break; /* Return to the caller. */
} }
} }
return retval; return retval == REDIS_OK ? 0 : 1;
} }
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
...@@ -3915,7 +3978,10 @@ static int clusterManagerSetSlot(clusterManagerNode *node1, ...@@ -3915,7 +3978,10 @@ static int clusterManagerSetSlot(clusterManagerNode *node1,
slot, status, slot, status,
(char *) node2->name); (char *) node2->name);
if (err != NULL) *err = NULL; if (err != NULL) *err = NULL;
if (!reply) return 0; if (!reply) {
if (err) *err = zstrdup("CLUSTER SETSLOT failed to run");
return 0;
}
int success = 1; int success = 1;
if (reply->type == REDIS_REPLY_ERROR) { if (reply->type == REDIS_REPLY_ERROR) {
success = 0; success = 0;
...@@ -4365,33 +4431,41 @@ static int clusterManagerMoveSlot(clusterManagerNode *source, ...@@ -4365,33 +4431,41 @@ static int clusterManagerMoveSlot(clusterManagerNode *source,
pipeline, print_dots, err); pipeline, print_dots, err);
if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) printf("\n"); if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) printf("\n");
if (!success) return 0; if (!success) return 0;
/* Set the new node as the owner of the slot in all the known nodes. */
if (!option_cold) { if (!option_cold) {
/* Set the new node as the owner of the slot in all the known nodes.
*
* We inform the target node first. It will propagate the information to
* the rest of the cluster.
*
* If we inform any other node first, it can happen that the target node
* crashes before it is set as the new owner and then the slot is left
* without an owner which results in redirect loops. See issue #7116. */
success = clusterManagerSetSlot(target, target, slot, "node", err);
if (!success) return 0;
/* Inform the source node. If the source node has just lost its last
* slot and the target node has already informed the source node, the
* source node has turned itself into a replica. This is not an error in
* this scenario so we ignore it. See issue #9223. */
success = clusterManagerSetSlot(source, target, slot, "node", err);
const char *acceptable = "ERR Please use SETSLOT only with masters.";
if (!success && err && !strncmp(*err, acceptable, strlen(acceptable))) {
zfree(*err);
*err = NULL;
} else if (!success && err) {
return 0;
}
/* We also inform the other nodes to avoid redirects in case the target
* node is slow to propagate the change to the entire cluster. */
listIter li; listIter li;
listNode *ln; listNode *ln;
listRewind(cluster_manager.nodes, &li); listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value; clusterManagerNode *n = ln->value;
if (n == target || n == source) continue; /* already done */
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue; if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER " success = clusterManagerSetSlot(n, target, slot, "node", err);
"SETSLOT %d %s %s",
slot, "node",
target->name);
success = (r != NULL);
if (!success) {
if (err) *err = zstrdup("CLUSTER SETSLOT failed to run");
return 0;
}
if (r->type == REDIS_REPLY_ERROR) {
success = 0;
if (err != NULL) {
*err = zmalloc((r->len + 1) * sizeof(char));
strcpy(*err, r->str);
} else {
CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, r->str);
}
}
freeReplyObject(r);
if (!success) return 0; if (!success) return 0;
} }
} }
...@@ -6235,16 +6309,26 @@ assign_replicas: ...@@ -6235,16 +6309,26 @@ assign_replicas:
clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join " clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join "
"the cluster\n"); "the cluster\n");
clusterManagerNode *first = NULL; clusterManagerNode *first = NULL;
char first_ip[NET_IP_STR_LEN]; /* first->ip may be a hostname */
listRewind(cluster_manager.nodes, &li); listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *node = ln->value; clusterManagerNode *node = ln->value;
if (first == NULL) { if (first == NULL) {
first = node; first = node;
/* Although hiredis supports connecting to a hostname, CLUSTER
* MEET requires an IP address, so we do a DNS lookup here. */
if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE)
== ANET_ERR)
{
fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
success = 0;
goto cleanup;
}
continue; continue;
} }
redisReply *reply = NULL; redisReply *reply = NULL;
reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d", reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
first->ip, first->port); first_ip, first->port);
int is_err = 0; int is_err = 0;
if (reply != NULL) { if (reply != NULL) {
if ((is_err = reply->type == REDIS_REPLY_ERROR)) if ((is_err = reply->type == REDIS_REPLY_ERROR))
...@@ -6416,8 +6500,15 @@ static int clusterManagerCommandAddNode(int argc, char **argv) { ...@@ -6416,8 +6500,15 @@ static int clusterManagerCommandAddNode(int argc, char **argv) {
// Send CLUSTER MEET command to the new node // Send CLUSTER MEET command to the new node
clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it " clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it "
"join the cluster.\n", ip, port); "join the cluster.\n", ip, port);
/* CLUSTER MEET requires an IP address, so we do a DNS lookup here. */
char first_ip[NET_IP_STR_LEN];
if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE) == ANET_ERR) {
fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip);
success = 0;
goto cleanup;
}
reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d", reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
first->ip, first->port); first_ip, first->port);
if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL))) if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
goto cleanup; goto cleanup;
...@@ -7252,14 +7343,14 @@ static int clusterManagerCommandHelp(int argc, char **argv) { ...@@ -7252,14 +7343,14 @@ static int clusterManagerCommandHelp(int argc, char **argv) {
int commands_count = sizeof(clusterManagerCommands) / int commands_count = sizeof(clusterManagerCommands) /
sizeof(clusterManagerCommandDef); sizeof(clusterManagerCommandDef);
int i = 0, j; int i = 0, j;
fprintf(stderr, "Cluster Manager Commands:\n"); fprintf(stdout, "Cluster Manager Commands:\n");
int padding = 15; int padding = 15;
for (; i < commands_count; i++) { for (; i < commands_count; i++) {
clusterManagerCommandDef *def = &(clusterManagerCommands[i]); clusterManagerCommandDef *def = &(clusterManagerCommands[i]);
int namelen = strlen(def->name), padlen = padding - namelen; int namelen = strlen(def->name), padlen = padding - namelen;
fprintf(stderr, " %s", def->name); fprintf(stdout, " %s", def->name);
for (j = 0; j < padlen; j++) fprintf(stderr, " "); for (j = 0; j < padlen; j++) fprintf(stdout, " ");
fprintf(stderr, "%s\n", (def->args ? def->args : "")); fprintf(stdout, "%s\n", (def->args ? def->args : ""));
if (def->options != NULL) { if (def->options != NULL) {
int optslen = strlen(def->options); int optslen = strlen(def->options);
char *p = def->options, *eos = p + optslen; char *p = def->options, *eos = p + optslen;
...@@ -7269,18 +7360,18 @@ static int clusterManagerCommandHelp(int argc, char **argv) { ...@@ -7269,18 +7360,18 @@ static int clusterManagerCommandHelp(int argc, char **argv) {
char buf[255]; char buf[255];
memcpy(buf, p, deflen); memcpy(buf, p, deflen);
buf[deflen] = '\0'; buf[deflen] = '\0';
for (j = 0; j < padding; j++) fprintf(stderr, " "); for (j = 0; j < padding; j++) fprintf(stdout, " ");
fprintf(stderr, " --cluster-%s\n", buf); fprintf(stdout, " --cluster-%s\n", buf);
p = comma + 1; p = comma + 1;
if (p >= eos) break; if (p >= eos) break;
} }
if (p < eos) { if (p < eos) {
for (j = 0; j < padding; j++) fprintf(stderr, " "); for (j = 0; j < padding; j++) fprintf(stdout, " ");
fprintf(stderr, " --cluster-%s\n", p); fprintf(stdout, " --cluster-%s\n", p);
} }
} }
} }
fprintf(stderr, "\nFor check, fix, reshard, del-node, set-timeout, " fprintf(stdout, "\nFor check, fix, reshard, del-node, set-timeout, "
"info, rebalance, call, import, backup you " "info, rebalance, call, import, backup you "
"can specify the host and port of any working node in " "can specify the host and port of any working node in "
"the cluster.\n"); "the cluster.\n");
...@@ -7288,16 +7379,16 @@ static int clusterManagerCommandHelp(int argc, char **argv) { ...@@ -7288,16 +7379,16 @@ static int clusterManagerCommandHelp(int argc, char **argv) {
int options_count = sizeof(clusterManagerOptions) / int options_count = sizeof(clusterManagerOptions) /
sizeof(clusterManagerOptionDef); sizeof(clusterManagerOptionDef);
i = 0; i = 0;
fprintf(stderr, "\nCluster Manager Options:\n"); fprintf(stdout, "\nCluster Manager Options:\n");
for (; i < options_count; i++) { for (; i < options_count; i++) {
clusterManagerOptionDef *def = &(clusterManagerOptions[i]); clusterManagerOptionDef *def = &(clusterManagerOptions[i]);
int namelen = strlen(def->name), padlen = padding - namelen; int namelen = strlen(def->name), padlen = padding - namelen;
fprintf(stderr, " %s", def->name); fprintf(stdout, " %s", def->name);
for (j = 0; j < padlen; j++) fprintf(stderr, " "); for (j = 0; j < padlen; j++) fprintf(stdout, " ");
fprintf(stderr, "%s\n", def->desc); fprintf(stdout, "%s\n", def->desc);
} }
fprintf(stderr, "\n"); fprintf(stdout, "\n");
return 0; return 0;
} }
...@@ -8969,10 +9060,11 @@ int main(int argc, char **argv) { ...@@ -8969,10 +9060,11 @@ int main(int argc, char **argv) {
} }
/* Otherwise, we have some arguments to execute */ /* Otherwise, we have some arguments to execute */
if (cliConnect(0) != REDIS_OK) exit(1);
if (config.eval) { if (config.eval) {
if (cliConnect(0) != REDIS_OK) exit(1);
return evalMode(argc,argv); return evalMode(argc,argv);
} else { } else {
cliConnect(CC_QUIET);
return noninteractive(argc,argv); return noninteractive(argc,argv);
} }
} }
...@@ -80,6 +80,15 @@ ...@@ -80,6 +80,15 @@
#define REDISMODULE_HASH_EXISTS (1<<3) #define REDISMODULE_HASH_EXISTS (1<<3)
#define REDISMODULE_HASH_COUNT_ALL (1<<4) #define REDISMODULE_HASH_COUNT_ALL (1<<4)
#define REDISMODULE_CONFIG_DEFAULT 0 /* This is the default for a module config. */
#define REDISMODULE_CONFIG_IMMUTABLE (1ULL<<0) /* Can this value only be set at startup? */
#define REDISMODULE_CONFIG_SENSITIVE (1ULL<<1) /* Does this value contain sensitive information */
#define REDISMODULE_CONFIG_HIDDEN (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */
#define REDISMODULE_CONFIG_PROTECTED (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#define REDISMODULE_CONFIG_DENY_LOADING (1ULL<<6) /* This config is forbidden during loading. */
#define REDISMODULE_CONFIG_MEMORY (1ULL<<7) /* Indicates if this value can be set as a memory value */
/* StreamID type. */ /* StreamID type. */
typedef struct RedisModuleStreamID { typedef struct RedisModuleStreamID {
uint64_t ms; uint64_t ms;
...@@ -429,7 +438,8 @@ typedef void (*RedisModuleEventLoopOneShotFunc)(void *user_data); ...@@ -429,7 +438,8 @@ typedef void (*RedisModuleEventLoopOneShotFunc)(void *user_data);
#define REDISMODULE_EVENT_FORK_CHILD 13 #define REDISMODULE_EVENT_FORK_CHILD 13
#define REDISMODULE_EVENT_REPL_ASYNC_LOAD 14 #define REDISMODULE_EVENT_REPL_ASYNC_LOAD 14
#define REDISMODULE_EVENT_EVENTLOOP 15 #define REDISMODULE_EVENT_EVENTLOOP 15
#define _REDISMODULE_EVENT_NEXT 16 /* Next event flag, should be updated if a new event added. */ #define REDISMODULE_EVENT_CONFIG 16
#define _REDISMODULE_EVENT_NEXT 17 /* Next event flag, should be updated if a new event added. */
typedef struct RedisModuleEvent { typedef struct RedisModuleEvent {
uint64_t id; /* REDISMODULE_EVENT_... defines. */ uint64_t id; /* REDISMODULE_EVENT_... defines. */
...@@ -532,7 +542,11 @@ static const RedisModuleEvent ...@@ -532,7 +542,11 @@ static const RedisModuleEvent
RedisModuleEvent_EventLoop = { RedisModuleEvent_EventLoop = {
REDISMODULE_EVENT_EVENTLOOP, REDISMODULE_EVENT_EVENTLOOP,
1 1
}; },
RedisModuleEvent_Config = {
REDISMODULE_EVENT_CONFIG,
1
};
/* Those are values that are used for the 'subevent' callback argument. */ /* Those are values that are used for the 'subevent' callback argument. */
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0 #define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0
...@@ -574,6 +588,9 @@ static const RedisModuleEvent ...@@ -574,6 +588,9 @@ static const RedisModuleEvent
#define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1 #define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1
#define _REDISMODULE_SUBEVENT_MODULE_NEXT 2 #define _REDISMODULE_SUBEVENT_MODULE_NEXT 2
#define REDISMODULE_SUBEVENT_CONFIG_CHANGE 0
#define _REDISMODULE_SUBEVENT_CONFIG_NEXT 1
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0 #define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1 #define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1
#define _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT 2 #define _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT 2
...@@ -674,6 +691,17 @@ typedef struct RedisModuleModuleChange { ...@@ -674,6 +691,17 @@ typedef struct RedisModuleModuleChange {
#define RedisModuleModuleChange RedisModuleModuleChangeV1 #define RedisModuleModuleChange RedisModuleModuleChangeV1
#define REDISMODULE_CONFIGCHANGE_VERSION 1
typedef struct RedisModuleConfigChange {
uint64_t version; /* Not used since this structure is never passed
from the module to the core right now. Here
for future compatibility. */
uint32_t num_changes; /* how many redis config options were changed */
const char **config_names; /* the config names that were changed */
} RedisModuleConfigChangeV1;
#define RedisModuleConfigChange RedisModuleConfigChangeV1
#define REDISMODULE_CRON_LOOP_VERSION 1 #define REDISMODULE_CRON_LOOP_VERSION 1
typedef struct RedisModuleCronLoopInfo { typedef struct RedisModuleCronLoopInfo {
uint64_t version; /* Not used since this structure is never passed uint64_t version; /* Not used since this structure is never passed
...@@ -788,6 +816,15 @@ typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keynam ...@@ -788,6 +816,15 @@ typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keynam
typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata); typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata);
typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata); typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata);
typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx); typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx);
typedef RedisModuleString * (*RedisModuleConfigGetStringFunc)(const char *name, void *privdata);
typedef long long (*RedisModuleConfigGetNumericFunc)(const char *name, void *privdata);
typedef int (*RedisModuleConfigGetBoolFunc)(const char *name, void *privdata);
typedef int (*RedisModuleConfigGetEnumFunc)(const char *name, void *privdata);
typedef int (*RedisModuleConfigSetStringFunc)(const char *name, RedisModuleString *val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, RedisModuleString **err);
typedef int (*RedisModuleConfigApplyFunc)(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err);
typedef struct RedisModuleTypeMethods { typedef struct RedisModuleTypeMethods {
uint64_t version; uint64_t version;
...@@ -1124,6 +1161,7 @@ REDISMODULE_API void (*RedisModule_ACLAddLogEntry)(RedisModuleCtx *ctx, RedisMod ...@@ -1124,6 +1161,7 @@ REDISMODULE_API void (*RedisModule_ACLAddLogEntry)(RedisModuleCtx *ctx, RedisMod
REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RedactClientCommandArgument)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR; REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) REDISMODULE_ATTR; REDISMODULE_API int *(*RedisModule_GetCommandKeysWithFlags)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) REDISMODULE_ATTR;
...@@ -1139,6 +1177,11 @@ REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDefragCtx) ...@@ -1139,6 +1177,11 @@ REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromDefragCtx)
REDISMODULE_API int (*RedisModule_EventLoopAdd)(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopAdd)(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_EventLoopDel)(int fd, int mask) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopDel)(int fd, int mask) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_EventLoopAddOneShot)(RedisModuleEventLoopOneShotFunc func, void *user_data) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_EventLoopAddOneShot)(RedisModuleEventLoopOneShotFunc func, void *user_data) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterBoolConfig)(RedisModuleCtx *ctx, char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterNumericConfig)(RedisModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, RedisModuleConfigGetNumericFunc getfn, RedisModuleConfigSetNumericFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterStringConfig)(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_RegisterEnumConfig)(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_LoadConfigs)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX) #define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
...@@ -1447,6 +1490,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -1447,6 +1490,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(DeauthenticateAndCloseClient); REDISMODULE_GET_API(DeauthenticateAndCloseClient);
REDISMODULE_GET_API(AuthenticateClientWithACLUser); REDISMODULE_GET_API(AuthenticateClientWithACLUser);
REDISMODULE_GET_API(AuthenticateClientWithUser); REDISMODULE_GET_API(AuthenticateClientWithUser);
REDISMODULE_GET_API(RedactClientCommandArgument);
REDISMODULE_GET_API(GetClientCertificate); REDISMODULE_GET_API(GetClientCertificate);
REDISMODULE_GET_API(GetCommandKeys); REDISMODULE_GET_API(GetCommandKeys);
REDISMODULE_GET_API(GetCommandKeysWithFlags); REDISMODULE_GET_API(GetCommandKeysWithFlags);
...@@ -1462,6 +1506,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -1462,6 +1506,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(EventLoopAdd); REDISMODULE_GET_API(EventLoopAdd);
REDISMODULE_GET_API(EventLoopDel); REDISMODULE_GET_API(EventLoopDel);
REDISMODULE_GET_API(EventLoopAddOneShot); REDISMODULE_GET_API(EventLoopAddOneShot);
REDISMODULE_GET_API(RegisterBoolConfig);
REDISMODULE_GET_API(RegisterNumericConfig);
REDISMODULE_GET_API(RegisterStringConfig);
REDISMODULE_GET_API(RegisterEnumConfig);
REDISMODULE_GET_API(LoadConfigs);
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
RedisModule_SetModuleAttribs(ctx,name,ver,apiver); RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
......
...@@ -543,7 +543,8 @@ void replicationFeedStreamFromMasterStream(char *buf, size_t buflen) { ...@@ -543,7 +543,8 @@ void replicationFeedStreamFromMasterStream(char *buf, size_t buflen) {
} }
void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv, int argc) { void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv, int argc) {
if (!(listLength(server.monitors) && !server.loading)) return; /* Fast path to return if the monitors list is empty or the server is in loading. */
if (monitors == NULL || listLength(monitors) == 0 || server.loading) return;
listNode *ln; listNode *ln;
listIter li; listIter li;
int j; int j;
...@@ -1528,15 +1529,7 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, ...@@ -1528,15 +1529,7 @@ void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData,
} }
} }
/* This function is called at the end of every background saving, /* This function is called at the end of every background saving.
* or when the replication RDB transfer strategy is modified from
* disk to socket or the other way around.
*
* The goal of this function is to handle slaves waiting for a successful
* background saving in order to perform non-blocking synchronization, and
* to schedule a new BGSAVE if there are slaves that attached while a
* BGSAVE was in progress, but it was not a good one for replication (no
* other slave was accumulating differences).
* *
* The argument bgsaveerr is C_OK if the background saving succeeded * The argument bgsaveerr is C_OK if the background saving succeeded
* otherwise C_ERR is passed to the function. * otherwise C_ERR is passed to the function.
...@@ -3204,7 +3197,8 @@ void replicationCacheMaster(client *c) { ...@@ -3204,7 +3197,8 @@ void replicationCacheMaster(client *c) {
* offsets, including pending transactions, already populated arguments, * offsets, including pending transactions, already populated arguments,
* pending outputs to the master. */ * pending outputs to the master. */
sdsclear(server.master->querybuf); sdsclear(server.master->querybuf);
sdsclear(server.master->pending_querybuf); server.master->qb_pos = 0;
server.master->repl_applied = 0;
server.master->read_reploff = server.master->reploff; server.master->read_reploff = server.master->reploff;
if (c->flags & CLIENT_MULTI) discardTransaction(c); if (c->flags & CLIENT_MULTI) discardTransaction(c);
listEmpty(c->reply); listEmpty(c->reply);
...@@ -3342,6 +3336,14 @@ void refreshGoodSlavesCount(void) { ...@@ -3342,6 +3336,14 @@ void refreshGoodSlavesCount(void) {
server.repl_good_slaves_count = good; server.repl_good_slaves_count = good;
} }
/* return true if status of good replicas is OK. otherwise false */
int checkGoodReplicasStatus(void) {
return server.masterhost || /* not a primary status should be OK */
!server.repl_min_slaves_max_lag || /* Min slave max lag not configured */
!server.repl_min_slaves_to_write || /* Min slave to write not configured */
server.repl_good_slaves_count >= server.repl_min_slaves_to_write; /* check if we have enough slaves */
}
/* ----------------------- SYNCHRONOUS REPLICATION -------------------------- /* ----------------------- SYNCHRONOUS REPLICATION --------------------------
* Redis synchronous replication design can be summarized in points: * Redis synchronous replication design can be summarized in points:
* *
......
...@@ -312,25 +312,7 @@ static int scriptVerifyACL(client *c, sds *err) { ...@@ -312,25 +312,7 @@ static int scriptVerifyACL(client *c, sds *err) {
int acl_retval = ACLCheckAllPerm(c, &acl_errpos); int acl_retval = ACLCheckAllPerm(c, &acl_errpos);
if (acl_retval != ACL_OK) { if (acl_retval != ACL_OK) {
addACLLogEntry(c,acl_retval,ACL_LOG_CTX_LUA,acl_errpos,NULL,NULL); addACLLogEntry(c,acl_retval,ACL_LOG_CTX_LUA,acl_errpos,NULL,NULL);
switch (acl_retval) { *err = sdscatfmt(sdsempty(), "The user executing the script %s", getAclErrorMessage(acl_retval));
case ACL_DENIED_CMD:
*err = sdsnew("The user executing the script can't run this "
"command or subcommand");
break;
case ACL_DENIED_KEY:
*err = sdsnew("The user executing the script can't access "
"at least one of the keys mentioned in the "
"command arguments");
break;
case ACL_DENIED_CHANNEL:
*err = sdsnew("The user executing the script can't publish "
"to the channel mentioned in the command");
break;
default:
*err = sdsnew("The user executing the script is lacking the "
"permissions for the command");
break;
}
return C_ERR; return C_ERR;
} }
return C_OK; return C_OK;
...@@ -360,14 +342,7 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) { ...@@ -360,14 +342,7 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) {
} }
if (deny_write_type != DISK_ERROR_TYPE_NONE) { if (deny_write_type != DISK_ERROR_TYPE_NONE) {
if (deny_write_type == DISK_ERROR_TYPE_RDB) { *err = writeCommandsGetDiskErrorMessage(deny_write_type);
*err = sdsdup(shared.bgsaveerr->ptr);
} else {
*err = sdsempty();
*err = sdscatfmt(*err,
"-MISCONF Errors writing to the AOF file: %s\r\n",
strerror(server.aof_last_write_errno));
}
return C_ERR; return C_ERR;
} }
...@@ -375,11 +350,7 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) { ...@@ -375,11 +350,7 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) {
* user configured the min-slaves-to-write option. Note this only reachable * user configured the min-slaves-to-write option. Note this only reachable
* for Eval scripts that didn't declare flags, see the other check in * for Eval scripts that didn't declare flags, see the other check in
* scriptPrepareForRun */ * scriptPrepareForRun */
if (server.masterhost == NULL && if (!checkGoodReplicasStatus()) {
server.repl_min_slaves_max_lag &&
server.repl_min_slaves_to_write &&
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
{
*err = sdsdup(shared.noreplicaserr->ptr); *err = sdsdup(shared.noreplicaserr->ptr);
return C_ERR; return C_ERR;
} }
...@@ -387,6 +358,16 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) { ...@@ -387,6 +358,16 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) {
return C_OK; return C_OK;
} }
static int scriptVerifyMayReplicate(scriptRunCtx *run_ctx, char **err) {
if (run_ctx->c->cmd->flags & CMD_MAY_REPLICATE &&
server.client_pause_type == CLIENT_PAUSE_WRITE) {
*err = sdsnew("May-replicate commands are not allowed when client pause write.");
return C_ERR;
}
return C_OK;
}
static int scriptVerifyOOM(scriptRunCtx *run_ctx, char **err) { static int scriptVerifyOOM(scriptRunCtx *run_ctx, char **err) {
if (run_ctx->flags & SCRIPT_ALLOW_OOM) { if (run_ctx->flags & SCRIPT_ALLOW_OOM) {
/* Allow running any command even if OOM reached */ /* Allow running any command even if OOM reached */
...@@ -528,6 +509,10 @@ void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) { ...@@ -528,6 +509,10 @@ void scriptCall(scriptRunCtx *run_ctx, robj* *argv, int argc, sds *err) {
goto error; goto error;
} }
if (scriptVerifyMayReplicate(run_ctx, err) != C_OK) {
goto error;
}
if (scriptVerifyOOM(run_ctx, err) != C_OK) { if (scriptVerifyOOM(run_ctx, err) != C_OK) {
goto error; goto error;
} }
......
...@@ -391,7 +391,6 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd ...@@ -391,7 +391,6 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
sentinelRedisInstance *sentinelGetMasterByName(char *name); sentinelRedisInstance *sentinelGetMasterByName(char *name);
char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master); char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master);
char *sentinelGetObjectiveLeader(sentinelRedisInstance *master); char *sentinelGetObjectiveLeader(sentinelRedisInstance *master);
int yesnotoi(char *s);
void instanceLinkConnectionError(const redisAsyncContext *c); void instanceLinkConnectionError(const redisAsyncContext *c);
const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri); const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri);
void sentinelAbortFailover(sentinelRedisInstance *ri); void sentinelAbortFailover(sentinelRedisInstance *ri);
...@@ -1134,6 +1133,27 @@ int sentinelTryConnectionSharing(sentinelRedisInstance *ri) { ...@@ -1134,6 +1133,27 @@ int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {
return C_ERR; return C_ERR;
} }
/* Disconnect the relevant master and its replicas. */
void dropInstanceConnections(sentinelRedisInstance *ri) {
serverAssert(ri->flags & SRI_MASTER);
/* Disconnect with the master. */
instanceLinkCloseConnection(ri->link, ri->link->cc);
instanceLinkCloseConnection(ri->link, ri->link->pc);
/* Disconnect with all replicas. */
dictIterator *di;
dictEntry *de;
sentinelRedisInstance *repl_ri;
di = dictGetIterator(ri->slaves);
while ((de = dictNext(di)) != NULL) {
repl_ri = dictGetVal(de);
instanceLinkCloseConnection(repl_ri->link, repl_ri->link->cc);
instanceLinkCloseConnection(repl_ri->link, repl_ri->link->pc);
}
dictReleaseIterator(di);
}
/* Drop all connections to other sentinels. Returns the number of connections /* Drop all connections to other sentinels. Returns the number of connections
* dropped.*/ * dropped.*/
int sentinelDropConnections(void) { int sentinelDropConnections(void) {
...@@ -4063,7 +4083,7 @@ NULL ...@@ -4063,7 +4083,7 @@ NULL
dictReleaseIterator(di); dictReleaseIterator(di);
if (masters_local != sentinel.masters) dictRelease(masters_local); if (masters_local != sentinel.masters) dictRelease(masters_local);
} else if (!strcasecmp(c->argv[1]->ptr,"simulate-failure")) { } else if (!strcasecmp(c->argv[1]->ptr,"simulate-failure")) {
/* SENTINEL SIMULATE-FAILURE <flag> <flag> ... <flag> */ /* SENTINEL SIMULATE-FAILURE [CRASH-AFTER-ELECTION] [CRASH-AFTER-PROMOTION] [HELP] */
int j; int j;
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE; sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
...@@ -4297,6 +4317,7 @@ void sentinelSetCommand(client *c) { ...@@ -4297,6 +4317,7 @@ void sentinelSetCommand(client *c) {
char *value = c->argv[++j]->ptr; char *value = c->argv[++j]->ptr;
sdsfree(ri->auth_pass); sdsfree(ri->auth_pass);
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL; ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
dropInstanceConnections(ri);
changes++; changes++;
redacted = 1; redacted = 1;
} else if (!strcasecmp(option,"auth-user") && moreargs > 0) { } else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
...@@ -4304,6 +4325,7 @@ void sentinelSetCommand(client *c) { ...@@ -4304,6 +4325,7 @@ void sentinelSetCommand(client *c) {
char *value = c->argv[++j]->ptr; char *value = c->argv[++j]->ptr;
sdsfree(ri->auth_user); sdsfree(ri->auth_user);
ri->auth_user = strlen(value) ? sdsnew(value) : NULL; ri->auth_user = strlen(value) ? sdsnew(value) : NULL;
dropInstanceConnections(ri);
changes++; changes++;
} else if (!strcasecmp(option,"quorum") && moreargs > 0) { } else if (!strcasecmp(option,"quorum") && moreargs > 0) {
/* quorum <count> */ /* quorum <count> */
......
...@@ -526,6 +526,30 @@ dictType stringSetDictType = { ...@@ -526,6 +526,30 @@ dictType stringSetDictType = {
NULL /* allow to expand */ NULL /* allow to expand */
}; };
/* Dict for for case-insensitive search using null terminated C strings.
* The key and value do not have a destructor. */
dictType externalStringType = {
distCStrCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
distCStrKeyCaseCompare, /* key compare */
NULL, /* key destructor */
NULL, /* val destructor */
NULL /* allow to expand */
};
/* Dict for case-insensitive search using sds objects with a zmalloc
* allocated object as the value. */
dictType sdsHashDictType = {
dictSdsCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */
dictVanillaFree, /* val destructor */
NULL /* allow to expand */
};
int htNeedsResize(dict *dict) { int htNeedsResize(dict *dict) {
long long size, used; long long size, used;
...@@ -686,23 +710,6 @@ int clientsCronResizeQueryBuffer(client *c) { ...@@ -686,23 +710,6 @@ int clientsCronResizeQueryBuffer(client *c) {
* which ever is bigger. */ * which ever is bigger. */
if (c->bulklen != -1 && (size_t)c->bulklen > c->querybuf_peak) if (c->bulklen != -1 && (size_t)c->bulklen > c->querybuf_peak)
c->querybuf_peak = c->bulklen; c->querybuf_peak = c->bulklen;
/* Clients representing masters also use a "pending query buffer" that
* is the yet not applied part of the stream we are reading. Such buffer
* also needs resizing from time to time, otherwise after a very large
* transfer (a huge value or a big MIGRATE operation) it will keep using
* a lot of memory. */
if (c->flags & CLIENT_MASTER) {
/* There are two conditions to resize the pending query buffer:
* 1) Pending Query buffer is > LIMIT_PENDING_QUERYBUF.
* 2) Used length is smaller than pending_querybuf_size/2 */
size_t pending_querybuf_size = sdsAllocSize(c->pending_querybuf);
if(pending_querybuf_size > LIMIT_PENDING_QUERYBUF &&
sdslen(c->pending_querybuf) < (pending_querybuf_size/2))
{
c->pending_querybuf = sdsRemoveFreeSpace(c->pending_querybuf);
}
}
return 0; return 0;
} }
...@@ -720,6 +727,10 @@ int clientsCronResizeOutputBuffer(client *c, mstime_t now_ms) { ...@@ -720,6 +727,10 @@ int clientsCronResizeOutputBuffer(client *c, mstime_t now_ms) {
const size_t buffer_target_shrink_size = c->buf_usable_size/2; const size_t buffer_target_shrink_size = c->buf_usable_size/2;
const size_t buffer_target_expand_size = c->buf_usable_size*2; const size_t buffer_target_expand_size = c->buf_usable_size*2;
/* in case the resizing is disabled return immediately */
if(!server.reply_buffer_resizing_enabled)
return 0;
if (buffer_target_shrink_size >= PROTO_REPLY_MIN_BYTES && if (buffer_target_shrink_size >= PROTO_REPLY_MIN_BYTES &&
c->buf_peak < buffer_target_shrink_size ) c->buf_peak < buffer_target_shrink_size )
{ {
...@@ -786,7 +797,7 @@ int clientsCronTrackExpansiveClients(client *c, int time_idx) { ...@@ -786,7 +797,7 @@ int clientsCronTrackExpansiveClients(client *c, int time_idx) {
* client's memory usage doubles it's moved up to the next bucket, if it's * client's memory usage doubles it's moved up to the next bucket, if it's
* halved we move it down a bucket. * halved we move it down a bucket.
* For more details see CLIENT_MEM_USAGE_BUCKETS documentation in server.h. */ * For more details see CLIENT_MEM_USAGE_BUCKETS documentation in server.h. */
clientMemUsageBucket *getMemUsageBucket(size_t mem) { static inline clientMemUsageBucket *getMemUsageBucket(size_t mem) {
int size_in_bits = 8*(int)sizeof(mem); int size_in_bits = 8*(int)sizeof(mem);
int clz = mem > 0 ? __builtin_clzl(mem) : size_in_bits; int clz = mem > 0 ? __builtin_clzl(mem) : size_in_bits;
int bucket_idx = size_in_bits - clz; int bucket_idx = size_in_bits - clz;
...@@ -803,46 +814,34 @@ clientMemUsageBucket *getMemUsageBucket(size_t mem) { ...@@ -803,46 +814,34 @@ clientMemUsageBucket *getMemUsageBucket(size_t mem) {
* and also from the clientsCron. We call it from the cron so we have updated * and also from the clientsCron. We call it from the cron so we have updated
* stats for non CLIENT_TYPE_NORMAL/PUBSUB clients and in case a configuration * stats for non CLIENT_TYPE_NORMAL/PUBSUB clients and in case a configuration
* change requires us to evict a non-active client. * change requires us to evict a non-active client.
*
* This also adds the client to the correct memory usage bucket. Each bucket contains
* all clients with roughly the same amount of memory. This way we group
* together clients consuming about the same amount of memory and can quickly
* free them in case we reach maxmemory-clients (client eviction).
*/ */
int updateClientMemUsage(client *c) { int updateClientMemUsage(client *c) {
serverAssert(io_threads_op == IO_THREADS_OP_IDLE);
size_t mem = getClientMemoryUsage(c, NULL); size_t mem = getClientMemoryUsage(c, NULL);
int type = getClientType(c); int type = getClientType(c);
/* Remove the old value of the memory used by the client from the old /* Remove the old value of the memory used by the client from the old
* category, and add it back. */ * category, and add it back. */
atomicDecr(server.stat_clients_type_memory[c->last_memory_type], c->last_memory_usage); if (type != c->last_memory_type) {
atomicIncr(server.stat_clients_type_memory[type], mem); server.stat_clients_type_memory[c->last_memory_type] -= c->last_memory_usage;
server.stat_clients_type_memory[type] += mem;
/* Remember what we added and where, to remove it next time. */
c->last_memory_usage = mem;
c->last_memory_type = type; c->last_memory_type = type;
} else {
server.stat_clients_type_memory[type] += mem - c->last_memory_usage;
}
/* Update client mem usage bucket only when we're not in the context of an
* IO thread. See updateClientMemUsageBucket() for details. */
if (io_threads_op == IO_THREADS_OP_IDLE)
updateClientMemUsageBucket(c);
return 0;
}
/* Adds the client to the correct memory usage bucket. Each bucket contains
* all clients with roughly the same amount of memory. This way we group
* together clients consuming about the same amount of memory and can quickly
* free them in case we reach maxmemory-clients (client eviction).
* Note that in case of io-threads enabled we have to call this function only
* after the fan-in phase (when no io-threads are working) because the bucket
* lists are global. The io-threads themselves track per-client memory usage in
* updateClientMemUsage(). Here we update the clients to each bucket when all
* io-threads are done (both for read and write io-threading). */
void updateClientMemUsageBucket(client *c) {
serverAssert(io_threads_op == IO_THREADS_OP_IDLE);
int allow_eviction = int allow_eviction =
(c->last_memory_type == CLIENT_TYPE_NORMAL || c->last_memory_type == CLIENT_TYPE_PUBSUB) && (type == CLIENT_TYPE_NORMAL || type == CLIENT_TYPE_PUBSUB) &&
!(c->flags & CLIENT_NO_EVICT); !(c->flags & CLIENT_NO_EVICT);
/* Update the client in the mem usage buckets */ /* Update the client in the mem usage buckets */
if (c->mem_usage_bucket) { if (c->mem_usage_bucket) {
c->mem_usage_bucket->mem_usage_sum -= c->last_memory_usage_on_bucket_update; c->mem_usage_bucket->mem_usage_sum -= c->last_memory_usage;
/* If this client can't be evicted then remove it from the mem usage /* If this client can't be evicted then remove it from the mem usage
* buckets */ * buckets */
if (!allow_eviction) { if (!allow_eviction) {
...@@ -852,8 +851,8 @@ void updateClientMemUsageBucket(client *c) { ...@@ -852,8 +851,8 @@ void updateClientMemUsageBucket(client *c) {
} }
} }
if (allow_eviction) { if (allow_eviction) {
clientMemUsageBucket *bucket = getMemUsageBucket(c->last_memory_usage); clientMemUsageBucket *bucket = getMemUsageBucket(mem);
bucket->mem_usage_sum += c->last_memory_usage; bucket->mem_usage_sum += mem;
if (bucket != c->mem_usage_bucket) { if (bucket != c->mem_usage_bucket) {
if (c->mem_usage_bucket) if (c->mem_usage_bucket)
listDelNode(c->mem_usage_bucket->clients, listDelNode(c->mem_usage_bucket->clients,
...@@ -864,7 +863,10 @@ void updateClientMemUsageBucket(client *c) { ...@@ -864,7 +863,10 @@ void updateClientMemUsageBucket(client *c) {
} }
} }
c->last_memory_usage_on_bucket_update = c->last_memory_usage; /* Remember what we added, to remove it next time. */
c->last_memory_usage = mem;
return 0;
} }
/* Return the max samples in the memory usage of clients tracked by /* Return the max samples in the memory usage of clients tracked by
...@@ -1410,7 +1412,7 @@ void blockingOperationEnds() { ...@@ -1410,7 +1412,7 @@ void blockingOperationEnds() {
} }
} }
/* This function fill in the role of serverCron during RDB or AOF loading, and /* This function fills in the role of serverCron during RDB or AOF loading, and
* also during blocked scripts. * also during blocked scripts.
* It attempts to do its duties at a similar rate as the configured server.hz, * It attempts to do its duties at a similar rate as the configured server.hz,
* and updates cronloops variable so that similarly to serverCron, the * and updates cronloops variable so that similarly to serverCron, the
...@@ -2405,6 +2407,7 @@ void initServer(void) { ...@@ -2405,6 +2407,7 @@ void initServer(void) {
server.thp_enabled = 0; server.thp_enabled = 0;
server.cluster_drop_packet_filter = -1; server.cluster_drop_packet_filter = -1;
server.reply_buffer_peak_reset_time = REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME; server.reply_buffer_peak_reset_time = REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME;
server.reply_buffer_resizing_enabled = 1;
resetReplicationBuffer(); resetReplicationBuffer();
if ((server.tls_port || server.tls_replication || server.tls_cluster) if ((server.tls_port || server.tls_replication || server.tls_cluster)
...@@ -3406,6 +3409,16 @@ void rejectCommand(client *c, robj *reply) { ...@@ -3406,6 +3409,16 @@ void rejectCommand(client *c, robj *reply) {
} }
} }
void rejectCommandSds(client *c, sds s) {
if (c->cmd && c->cmd->proc == execCommand) {
execCommandAbort(c, s);
sdsfree(s);
} else {
/* The following frees 's'. */
addReplyErrorSds(c, s);
}
}
void rejectCommandFormat(client *c, const char *fmt, ...) { void rejectCommandFormat(client *c, const char *fmt, ...) {
if (c->cmd) c->cmd->rejected_calls++; if (c->cmd) c->cmd->rejected_calls++;
flagTransaction(c); flagTransaction(c);
...@@ -3416,13 +3429,7 @@ void rejectCommandFormat(client *c, const char *fmt, ...) { ...@@ -3416,13 +3429,7 @@ void rejectCommandFormat(client *c, const char *fmt, ...) {
/* Make sure there are no newlines in the string, otherwise invalid protocol /* Make sure there are no newlines in the string, otherwise invalid protocol
* is emitted (The args come from the user, they may contain any character). */ * is emitted (The args come from the user, they may contain any character). */
sdsmapchars(s, "\r\n", " ", 2); sdsmapchars(s, "\r\n", " ", 2);
if (c->cmd && c->cmd->proc == execCommand) { rejectCommandSds(c, s);
execCommandAbort(c, s);
sdsfree(s);
} else {
/* The following frees 's'. */
addReplyErrorSds(c, s);
}
} }
/* This is called after a command in call, we can do some maintenance job in it. */ /* This is called after a command in call, we can do some maintenance job in it. */
...@@ -3705,23 +3712,14 @@ int processCommand(client *c) { ...@@ -3705,23 +3712,14 @@ int processCommand(client *c) {
server.masterhost == NULL && server.masterhost == NULL &&
(is_write_command ||c->cmd->proc == pingCommand)) (is_write_command ||c->cmd->proc == pingCommand))
{ {
if (deny_write_type == DISK_ERROR_TYPE_RDB) sds err = writeCommandsGetDiskErrorMessage(deny_write_type);
rejectCommand(c, shared.bgsaveerr); rejectCommandSds(c, err);
else
rejectCommandFormat(c,
"-MISCONF Errors writing to the AOF file: %s",
strerror(server.aof_last_write_errno));
return C_OK; return C_OK;
} }
/* Don't accept write commands if there are not enough good slaves and /* Don't accept write commands if there are not enough good slaves and
* user configured the min-slaves-to-write option. */ * user configured the min-slaves-to-write option. */
if (server.masterhost == NULL && if (is_write_command && !checkGoodReplicasStatus()) {
server.repl_min_slaves_to_write &&
server.repl_min_slaves_max_lag &&
is_write_command &&
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
{
rejectCommand(c, shared.noreplicaserr); rejectCommand(c, shared.noreplicaserr);
return C_OK; return C_OK;
} }
...@@ -4146,6 +4144,18 @@ int writeCommandsDeniedByDiskError(void) { ...@@ -4146,6 +4144,18 @@ int writeCommandsDeniedByDiskError(void) {
return DISK_ERROR_TYPE_NONE; return DISK_ERROR_TYPE_NONE;
} }
sds writeCommandsGetDiskErrorMessage(int error_code) {
sds ret = NULL;
if (error_code == DISK_ERROR_TYPE_RDB) {
ret = sdsdup(shared.bgsaveerr->ptr);
} else {
ret = sdscatfmt(sdsempty(),
"-MISCONF Errors writing to the AOF file: %s",
strerror(server.aof_last_write_errno));
}
return ret;
}
/* The PING command. It works in a different way if the client is in /* The PING command. It works in a different way if the client is in
* in Pub/Sub mode. */ * in Pub/Sub mode. */
void pingCommand(client *c) { void pingCommand(client *c) {
...@@ -6388,9 +6398,9 @@ void dismissMemory(void* ptr, size_t size_hint) { ...@@ -6388,9 +6398,9 @@ void dismissMemory(void* ptr, size_t size_hint) {
/* Dismiss big chunks of memory inside a client structure, see dismissMemory() */ /* Dismiss big chunks of memory inside a client structure, see dismissMemory() */
void dismissClientMemory(client *c) { void dismissClientMemory(client *c) {
/* Dismiss client query buffer. */ /* Dismiss client query buffer and static reply buffer. */
dismissMemory(c->buf, c->buf_usable_size);
dismissSds(c->querybuf); dismissSds(c->querybuf);
dismissSds(c->pending_querybuf);
/* Dismiss argv array only if we estimate it contains a big buffer. */ /* Dismiss argv array only if we estimate it contains a big buffer. */
if (c->argc && c->argv_len_sum/c->argc >= server.page_size) { if (c->argc && c->argv_len_sum/c->argc >= server.page_size) {
for (int i = 0; i < c->argc; i++) { for (int i = 0; i < c->argc; i++) {
...@@ -6414,9 +6424,6 @@ void dismissClientMemory(client *c) { ...@@ -6414,9 +6424,6 @@ void dismissClientMemory(client *c) {
if (bulk) dismissMemory(bulk, bulk->size); if (bulk) dismissMemory(bulk, bulk->size);
} }
} }
/* The client struct has a big static reply buffer in it. */
dismissMemory(c, 0);
} }
/* In the child process, we don't need some buffers anymore, and these are /* In the child process, we don't need some buffers anymore, and these are
......
...@@ -103,7 +103,6 @@ typedef long long ustime_t; /* microsecond time type. */ ...@@ -103,7 +103,6 @@ typedef long long ustime_t; /* microsecond time type. */
#define CONFIG_MIN_HZ 1 #define CONFIG_MIN_HZ 1
#define CONFIG_MAX_HZ 500 #define CONFIG_MAX_HZ 500
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */ #define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
#define CONFIG_MAX_LINE 1024
#define CRON_DBS_PER_CALL 16 #define CRON_DBS_PER_CALL 16
#define NET_MAX_WRITES_PER_EVENT (1024*64) #define NET_MAX_WRITES_PER_EVENT (1024*64)
#define PROTO_SHARED_SELECT_CMDS 10 #define PROTO_SHARED_SELECT_CMDS 10
...@@ -165,11 +164,8 @@ typedef long long ustime_t; /* microsecond time type. */ ...@@ -165,11 +164,8 @@ typedef long long ustime_t; /* microsecond time type. */
#define PROTO_MBULK_BIG_ARG (1024*32) #define PROTO_MBULK_BIG_ARG (1024*32)
#define PROTO_RESIZE_THRESHOLD (1024*32) /* Threshold for determining whether to resize query buffer */ #define PROTO_RESIZE_THRESHOLD (1024*32) /* Threshold for determining whether to resize query buffer */
#define PROTO_REPLY_MIN_BYTES (1024) /* the lower limit on reply buffer size */ #define PROTO_REPLY_MIN_BYTES (1024) /* the lower limit on reply buffer size */
#define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */
#define REDIS_AUTOSYNC_BYTES (1024*1024*4) /* Sync file every 4MB. */ #define REDIS_AUTOSYNC_BYTES (1024*1024*4) /* Sync file every 4MB. */
#define LIMIT_PENDING_QUERYBUF (4*1024*1024) /* 4mb */
#define REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME 5000 /* 5 seconds */ #define REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME 5000 /* 5 seconds */
/* When configuring the server eventloop, we setup it so that the total number /* When configuring the server eventloop, we setup it so that the total number
...@@ -764,6 +760,8 @@ struct RedisModule { ...@@ -764,6 +760,8 @@ struct RedisModule {
list *usedby; /* List of modules using APIs from this one. */ list *usedby; /* List of modules using APIs from this one. */
list *using; /* List of modules we use some APIs of. */ list *using; /* List of modules we use some APIs of. */
list *filters; /* List of filters the module has registered. */ list *filters; /* List of filters the module has registered. */
list *module_configs; /* List of configurations the module has registered */
int configs_initialized; /* Have the module configurations been initialized? */
int in_call; /* RM_Call() nesting level */ int in_call; /* RM_Call() nesting level */
int in_hook; /* Hooks callback nesting level for this module (0 or 1). */ int in_hook; /* Hooks callback nesting level for this module (0 or 1). */
int options; /* Module options and capabilities. */ int options; /* Module options and capabilities. */
...@@ -1084,10 +1082,6 @@ typedef struct client { ...@@ -1084,10 +1082,6 @@ typedef struct client {
robj *name; /* As set by CLIENT SETNAME. */ robj *name; /* As set by CLIENT SETNAME. */
sds querybuf; /* Buffer we use to accumulate client queries. */ sds querybuf; /* Buffer we use to accumulate client queries. */
size_t qb_pos; /* The position we have read in querybuf. */ size_t qb_pos; /* The position we have read in querybuf. */
sds pending_querybuf; /* If this client is flagged as master, this buffer
represents the yet not applied portion of the
replication stream that we are receiving from
the master. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */ size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
int argc; /* Num of arguments of current command. */ int argc; /* Num of arguments of current command. */
robj **argv; /* Arguments of current command. */ robj **argv; /* Arguments of current command. */
...@@ -1124,6 +1118,7 @@ typedef struct client { ...@@ -1124,6 +1118,7 @@ typedef struct client {
sds replpreamble; /* Replication DB preamble. */ sds replpreamble; /* Replication DB preamble. */
long long read_reploff; /* Read replication offset if this is a master. */ long long read_reploff; /* Read replication offset if this is a master. */
long long reploff; /* Applied replication offset if this is a master. */ long long reploff; /* Applied replication offset if this is a master. */
long long repl_applied; /* Applied replication data count in querybuf, if this is a replica. */
long long repl_ack_off; /* Replication ack offset, if this is a slave. */ long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */ long long repl_ack_time;/* Replication ack time, if this is a slave. */
long long repl_last_partial_write; /* The last time the server did a partial write from the RDB child pipe to this replica */ long long repl_last_partial_write; /* The last time the server did a partial write from the RDB child pipe to this replica */
...@@ -1173,7 +1168,6 @@ typedef struct client { ...@@ -1173,7 +1168,6 @@ typedef struct client {
size_t last_memory_usage; size_t last_memory_usage;
int last_memory_type; int last_memory_type;
size_t last_memory_usage_on_bucket_update;
listNode *mem_usage_bucket_node; listNode *mem_usage_bucket_node;
clientMemUsageBucket *mem_usage_bucket; clientMemUsageBucket *mem_usage_bucket;
...@@ -1482,6 +1476,7 @@ struct redisServer { ...@@ -1482,6 +1476,7 @@ struct redisServer {
dict *moduleapi; /* Exported core APIs dictionary for modules. */ dict *moduleapi; /* Exported core APIs dictionary for modules. */
dict *sharedapi; /* Like moduleapi but containing the APIs that dict *sharedapi; /* Like moduleapi but containing the APIs that
modules share with each other. */ modules share with each other. */
dict *module_configs_queue; /* Dict that stores module configurations from .conf file until after modules are loaded during startup or arguments to loadex. */
list *loadmodule_queue; /* List of modules to load at startup. */ list *loadmodule_queue; /* List of modules to load at startup. */
int module_pipe[2]; /* Pipe used to awake the event loop by module threads. */ int module_pipe[2]; /* Pipe used to awake the event loop by module threads. */
pid_t child_pid; /* PID of current child */ pid_t child_pid; /* PID of current child */
...@@ -1584,8 +1579,8 @@ struct redisServer { ...@@ -1584,8 +1579,8 @@ struct redisServer {
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. */ size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
double stat_module_progress; /* Module save progress. */ double stat_module_progress; /* Module save progress. */
redisAtomic size_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */ size_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
size_t stat_cluster_links_memory;/* Mem usage by cluster links */ size_t stat_cluster_links_memory; /* Mem usage by cluster links */
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */ long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
long long stat_total_error_replies; /* Total number of issued error replies ( command + rejected errors ) */ long long stat_total_error_replies; /* Total number of issued error replies ( command + rejected errors ) */
long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */ long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */
...@@ -1697,7 +1692,7 @@ struct redisServer { ...@@ -1697,7 +1692,7 @@ struct redisServer {
int rdb_pipe_numconns; /* target of diskless rdb fork child. */ int rdb_pipe_numconns; /* target of diskless rdb fork child. */
int rdb_pipe_numconns_writing; /* Number of rdb conns with pending writes. */ int rdb_pipe_numconns_writing; /* Number of rdb conns with pending writes. */
char *rdb_pipe_buff; /* In diskless replication, this buffer holds data */ char *rdb_pipe_buff; /* In diskless replication, this buffer holds data */
int rdb_pipe_bufflen; /* that was read from the the rdb pipe. */ int rdb_pipe_bufflen; /* that was read from the rdb pipe. */
int rdb_key_save_delay; /* Delay in microseconds between keys while int rdb_key_save_delay; /* Delay in microseconds between keys while
* writing the RDB. (for testings). negative * writing the RDB. (for testings). negative
* value means fractions of microseconds (on average). */ * value means fractions of microseconds (on average). */
...@@ -1851,7 +1846,7 @@ struct redisServer { ...@@ -1851,7 +1846,7 @@ struct redisServer {
int cluster_slave_no_failover; /* Prevent slave from starting a failover int cluster_slave_no_failover; /* Prevent slave from starting a failover
if the master is in failure state. */ if the master is in failure state. */
char *cluster_announce_ip; /* IP address to announce on cluster bus. */ char *cluster_announce_ip; /* IP address to announce on cluster bus. */
char *cluster_announce_hostname; /* IP address to announce on cluster bus. */ char *cluster_announce_hostname; /* hostname to announce on cluster bus. */
int cluster_preferred_endpoint_type; /* Use the announced hostname when available. */ int cluster_preferred_endpoint_type; /* Use the announced hostname when available. */
int cluster_announce_port; /* base port to announce on cluster bus. */ int cluster_announce_port; /* base port to announce on cluster bus. */
int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */ int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */
...@@ -1914,6 +1909,7 @@ struct redisServer { ...@@ -1914,6 +1909,7 @@ struct redisServer {
int cluster_allow_pubsubshard_when_down; /* Is pubsubshard allowed when the cluster int cluster_allow_pubsubshard_when_down; /* Is pubsubshard allowed when the cluster
is down, doesn't affect pubsub global. */ is down, doesn't affect pubsub global. */
long reply_buffer_peak_reset_time; /* The amount of time (in milliseconds) to wait between reply buffer peak resets */ long reply_buffer_peak_reset_time; /* The amount of time (in milliseconds) to wait between reply buffer peak resets */
int reply_buffer_resizing_enabled; /* Is reply buffer resizing enabled (1 by default) */
}; };
#define MAX_KEYS_BUFFER 256 #define MAX_KEYS_BUFFER 256
...@@ -2326,6 +2322,8 @@ extern dictType dbDictType; ...@@ -2326,6 +2322,8 @@ extern dictType dbDictType;
extern double R_Zero, R_PosInf, R_NegInf, R_Nan; extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
extern dictType hashDictType; extern dictType hashDictType;
extern dictType stringSetDictType; extern dictType stringSetDictType;
extern dictType externalStringType;
extern dictType sdsHashDictType;
extern dictType dbExpiresDictType; extern dictType dbExpiresDictType;
extern dictType modulesDictType; extern dictType modulesDictType;
extern dictType sdsReplyDictType; extern dictType sdsReplyDictType;
...@@ -2343,7 +2341,8 @@ int populateArgsStructure(struct redisCommandArg *args); ...@@ -2343,7 +2341,8 @@ int populateArgsStructure(struct redisCommandArg *args);
void moduleInitModulesSystem(void); void moduleInitModulesSystem(void);
void moduleInitModulesSystemLast(void); void moduleInitModulesSystemLast(void);
void modulesCron(void); void modulesCron(void);
int moduleLoad(const char *path, void **argv, int argc); int moduleLoad(const char *path, void **argv, int argc, int is_loadex);
int moduleUnload(sds name);
void moduleLoadFromQueue(void); void moduleLoadFromQueue(void);
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
...@@ -2649,6 +2648,7 @@ void resizeReplicationBacklog(); ...@@ -2649,6 +2648,7 @@ void resizeReplicationBacklog();
void replicationSetMaster(char *ip, int port); void replicationSetMaster(char *ip, int port);
void replicationUnsetMaster(void); void replicationUnsetMaster(void);
void refreshGoodSlavesCount(void); void refreshGoodSlavesCount(void);
int checkGoodReplicasStatus(void);
void processClientsWaitingReplicas(void); void processClientsWaitingReplicas(void);
void unblockClientWaitingReplicas(client *c); void unblockClientWaitingReplicas(client *c);
int replicationCountAcksByOffset(long long offset); int replicationCountAcksByOffset(long long offset);
...@@ -2689,6 +2689,7 @@ int allPersistenceDisabled(void); ...@@ -2689,6 +2689,7 @@ int allPersistenceDisabled(void);
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */ #define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
#define DISK_ERROR_TYPE_NONE 0 /* No problems, we can accept writes. */ #define DISK_ERROR_TYPE_NONE 0 /* No problems, we can accept writes. */
int writeCommandsDeniedByDiskError(void); int writeCommandsDeniedByDiskError(void);
sds writeCommandsGetDiskErrorMessage(int);
/* RDB persistence */ /* RDB persistence */
#include "rdb.h" #include "rdb.h"
...@@ -2757,6 +2758,7 @@ user *ACLGetUserByName(const char *name, size_t namelen); ...@@ -2757,6 +2758,7 @@ user *ACLGetUserByName(const char *name, size_t namelen);
int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags); int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags);
int ACLUserCheckChannelPerm(user *u, sds channel, int literal); int ACLUserCheckChannelPerm(user *u, sds channel, int literal);
int ACLCheckAllUserCommandPerm(user *u, struct redisCommand *cmd, robj **argv, int argc, int *idxptr); int ACLCheckAllUserCommandPerm(user *u, struct redisCommand *cmd, robj **argv, int argc, int *idxptr);
int ACLUserCheckCmdWithUnrestrictedKeyAccess(user *u, struct redisCommand *cmd, robj **argv, int argc, int flags);
int ACLCheckAllPerm(client *c, int *idxptr); int ACLCheckAllPerm(client *c, int *idxptr);
int ACLSetUser(user *u, const char *op, ssize_t oplen); int ACLSetUser(user *u, const char *op, ssize_t oplen);
uint64_t ACLGetCommandCategoryFlagByName(const char *name); uint64_t ACLGetCommandCategoryFlagByName(const char *name);
...@@ -2769,6 +2771,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd); ...@@ -2769,6 +2771,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd);
user *ACLCreateUnlinkedUser(); user *ACLCreateUnlinkedUser();
void ACLFreeUserAndKillClients(user *u); void ACLFreeUserAndKillClients(user *u);
void addACLLogEntry(client *c, int reason, int context, int argpos, sds username, sds object); void addACLLogEntry(client *c, int reason, int context, int argpos, sds username, sds object);
const char* getAclErrorMessage(int acl_res);
void ACLUpdateDefaultUserPassword(sds password); void ACLUpdateDefaultUserPassword(sds password);
/* Sorted sets data type */ /* Sorted sets data type */
...@@ -2844,7 +2847,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev ...@@ -2844,7 +2847,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
size_t freeMemoryGetNotCountedMemory(); size_t freeMemoryGetNotCountedMemory();
int overMaxmemoryAfterAlloc(size_t moremem); int overMaxmemoryAfterAlloc(size_t moremem);
int processCommand(client *c); int processCommand(client *c);
int processPendingCommandsAndResetClient(client *c); int processPendingCommandAndInputBuffer(client *c);
void setupSignalHandlers(void); void setupSignalHandlers(void);
void removeSignalHandlers(void); void removeSignalHandlers(void);
int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler); int createSocketAcceptHandler(socketFds *sfd, aeFileProc *accept_handler);
...@@ -2971,6 +2974,40 @@ int keyspaceEventsStringToFlags(char *classes); ...@@ -2971,6 +2974,40 @@ int keyspaceEventsStringToFlags(char *classes);
sds keyspaceEventsFlagsToString(int flags); sds keyspaceEventsFlagsToString(int flags);
/* Configuration */ /* Configuration */
/* Configuration Flags */
#define MODIFIABLE_CONFIG 0 /* This is the implied default for a standard
* config, which is mutable. */
#define IMMUTABLE_CONFIG (1ULL<<0) /* Can this value only be set at startup? */
#define SENSITIVE_CONFIG (1ULL<<1) /* Does this value contain sensitive information */
#define DEBUG_CONFIG (1ULL<<2) /* Values that are useful for debugging. */
#define MULTI_ARG_CONFIG (1ULL<<3) /* This config receives multiple arguments. */
#define HIDDEN_CONFIG (1ULL<<4) /* This config is hidden in `config get <pattern>` (used for tests/debugging) */
#define PROTECTED_CONFIG (1ULL<<5) /* Becomes immutable if enable-protected-configs is enabled. */
#define DENY_LOADING_CONFIG (1ULL<<6) /* This config is forbidden during loading. */
#define ALIAS_CONFIG (1ULL<<7) /* For configs with multiple names, this flag is set on the alias. */
#define MODULE_CONFIG (1ULL<<8) /* This config is a module config */
#define INTEGER_CONFIG 0 /* No flags means a simple integer configuration */
#define MEMORY_CONFIG (1<<0) /* Indicates if this value can be loaded as a memory value */
#define PERCENT_CONFIG (1<<1) /* Indicates if this value can be loaded as a percent (and stored as a negative int) */
#define OCTAL_CONFIG (1<<2) /* This value uses octal representation */
/* Enum Configs contain an array of configEnum objects that match a string with an integer. */
typedef struct configEnum {
char *name;
int val;
} configEnum;
/* Type of configuration. */
typedef enum {
BOOL_CONFIG,
NUMERIC_CONFIG,
STRING_CONFIG,
SDS_CONFIG,
ENUM_CONFIG,
SPECIAL_CONFIG,
} configType;
void loadServerConfig(char *filename, char config_from_stdin, char *options); void loadServerConfig(char *filename, char config_from_stdin, char *options);
void appendServerSaveParams(time_t seconds, int changes); void appendServerSaveParams(time_t seconds, int changes);
void resetServerSaveParams(void); void resetServerSaveParams(void);
...@@ -2979,9 +3016,29 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti ...@@ -2979,9 +3016,29 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option); void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option);
int rewriteConfig(char *path, int force_write); int rewriteConfig(char *path, int force_write);
void initConfigValues(); void initConfigValues();
void removeConfig(sds name);
sds getConfigDebugInfo(); sds getConfigDebugInfo();
int allowProtectedAction(int config, client *c); int allowProtectedAction(int config, client *c);
/* Module Configuration */
typedef struct ModuleConfig ModuleConfig;
int performModuleConfigSetFromName(sds name, sds value, const char **err);
int performModuleConfigSetDefaultFromName(sds name, const char **err);
void addModuleBoolConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val);
void addModuleStringConfig(const char *module_name, const char *name, int flags, void *privdata, sds default_val);
void addModuleEnumConfig(const char *module_name, const char *name, int flags, void *privdata, int default_val, configEnum *enum_vals);
void addModuleNumericConfig(const char *module_name, const char *name, int flags, void *privdata, long long default_val, int conf_flags, long long lower, long long upper);
void addModuleConfigApply(list *module_configs, ModuleConfig *module_config);
int moduleConfigApplyConfig(list *module_configs, const char **err, const char **err_arg_name);
int getModuleBoolConfig(ModuleConfig *module_config);
int setModuleBoolConfig(ModuleConfig *config, int val, const char **err);
sds getModuleStringConfig(ModuleConfig *module_config);
int setModuleStringConfig(ModuleConfig *config, sds strval, const char **err);
int getModuleEnumConfig(ModuleConfig *module_config);
int setModuleEnumConfig(ModuleConfig *config, int val, const char **err);
long long getModuleNumericConfig(ModuleConfig *module_config);
int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err);
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key); int removeExpire(redisDb *db, robj *key);
void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj); void deleteExpiredKeyAndPropagate(redisDb *db, robj *keyobj);
...@@ -3128,6 +3185,7 @@ void handleClientsBlockedOnKeys(void); ...@@ -3128,6 +3185,7 @@ void handleClientsBlockedOnKeys(void);
void signalKeyAsReady(redisDb *db, robj *key, int type); void signalKeyAsReady(redisDb *db, robj *key, int type);
void blockForKeys(client *c, int btype, robj **keys, int numkeys, long count, mstime_t timeout, robj *target, struct blockPos *blockpos, streamID *ids); void blockForKeys(client *c, int btype, robj **keys, int numkeys, long count, mstime_t timeout, robj *target, struct blockPos *blockpos, streamID *ids);
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us, int had_errors); void updateStatsOnUnblock(client *c, long blocked_us, long reply_us, int had_errors);
void scanDatabaseForDeletedStreams(redisDb *emptied, redisDb *replaced_with);
/* timeout.c -- Blocked clients timeout and connections timeout. */ /* timeout.c -- Blocked clients timeout and connections timeout. */
void addClientToTimeoutTable(client *c); void addClientToTimeoutTable(client *c);
......
...@@ -197,13 +197,15 @@ void sortCommandGeneric(client *c, int readonly) { ...@@ -197,13 +197,15 @@ void sortCommandGeneric(client *c, int readonly) {
int syntax_error = 0; int syntax_error = 0;
robj *sortval, *sortby = NULL, *storekey = NULL; robj *sortval, *sortby = NULL, *storekey = NULL;
redisSortObject *vector; /* Resulting vector to sort */ redisSortObject *vector; /* Resulting vector to sort */
int user_has_full_key_access = 0; /* ACL - used in order to verify 'get' and 'by' options can be used */
/* Create a list of operations to perform for every sorted element. /* Create a list of operations to perform for every sorted element.
* Operations can be GET */ * Operations can be GET */
operations = listCreate(); operations = listCreate();
listSetFreeMethod(operations,zfree); listSetFreeMethod(operations,zfree);
j = 2; /* options start at argv[2] */ j = 2; /* options start at argv[2] */
user_has_full_key_access = ACLUserCheckCmdWithUnrestrictedKeyAccess(c->user, c->cmd, c->argv, c->argc, CMD_KEY_ACCESS);
/* The SORT command has an SQL-alike syntax, parse it */ /* The SORT command has an SQL-alike syntax, parse it */
while(j < c->argc) { while(j < c->argc) {
int leftargs = c->argc-j-1; int leftargs = c->argc-j-1;
...@@ -233,13 +235,20 @@ void sortCommandGeneric(client *c, int readonly) { ...@@ -233,13 +235,20 @@ void sortCommandGeneric(client *c, int readonly) {
if (strchr(c->argv[j+1]->ptr,'*') == NULL) { if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
dontsort = 1; dontsort = 1;
} else { } else {
/* If BY is specified with a real patter, we can't accept /* If BY is specified with a real pattern, we can't accept
* it in cluster mode. */ * it in cluster mode. */
if (server.cluster_enabled) { if (server.cluster_enabled) {
addReplyError(c,"BY option of SORT denied in Cluster mode."); addReplyError(c,"BY option of SORT denied in Cluster mode.");
syntax_error++; syntax_error++;
break; break;
} }
/* If BY is specified with a real pattern, we can't accept
* it if no full ACL key access is applied for this command. */
if (!user_has_full_key_access) {
addReplyError(c,"BY option of SORT denied due to insufficient ACL permissions.");
syntax_error++;
break;
}
} }
j++; j++;
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
...@@ -248,6 +257,11 @@ void sortCommandGeneric(client *c, int readonly) { ...@@ -248,6 +257,11 @@ void sortCommandGeneric(client *c, int readonly) {
syntax_error++; syntax_error++;
break; break;
} }
if (!user_has_full_key_access) {
addReplyError(c,"GET option of SORT denied due to insufficient ACL permissions.");
syntax_error++;
break;
}
listAddNodeTail(operations,createSortOperation( listAddNodeTail(operations,createSortOperation(
SORT_OP_GET,c->argv[j+1])); SORT_OP_GET,c->argv[j+1]));
getop++; getop++;
......
...@@ -120,8 +120,9 @@ void zslFree(zskiplist *zsl) { ...@@ -120,8 +120,9 @@ void zslFree(zskiplist *zsl) {
* (both inclusive), with a powerlaw-alike distribution where higher * (both inclusive), with a powerlaw-alike distribution where higher
* levels are less likely to be returned. */ * levels are less likely to be returned. */
int zslRandomLevel(void) { int zslRandomLevel(void) {
static const int threshold = ZSKIPLIST_P*RAND_MAX;
int level = 1; int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) while (random() < threshold)
level += 1; level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
} }
...@@ -720,8 +721,8 @@ zskiplistNode *zslLastInLexRange(zskiplist *zsl, zlexrangespec *range) { ...@@ -720,8 +721,8 @@ zskiplistNode *zslLastInLexRange(zskiplist *zsl, zlexrangespec *range) {
double zzlStrtod(unsigned char *vstr, unsigned int vlen) { double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
char buf[128]; char buf[128];
if (vlen > sizeof(buf)) if (vlen > sizeof(buf) - 1)
vlen = sizeof(buf); vlen = sizeof(buf) - 1;
memcpy(buf,vstr,vlen); memcpy(buf,vstr,vlen);
buf[vlen] = '\0'; buf[vlen] = '\0';
return strtod(buf,NULL); return strtod(buf,NULL);
...@@ -1026,7 +1027,7 @@ unsigned char *zzlDelete(unsigned char *zl, unsigned char *eptr) { ...@@ -1026,7 +1027,7 @@ unsigned char *zzlDelete(unsigned char *zl, unsigned char *eptr) {
unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, sds ele, double score) { unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, sds ele, double score) {
unsigned char *sptr; unsigned char *sptr;
char scorebuf[128]; char scorebuf[MAX_D2STRING_CHARS];
int scorelen; int scorelen;
scorelen = d2string(scorebuf,sizeof(scorebuf),score); scorelen = d2string(scorebuf,sizeof(scorebuf),score);
......
...@@ -405,8 +405,8 @@ int string2ll(const char *s, size_t slen, long long *value) { ...@@ -405,8 +405,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
int negative = 0; int negative = 0;
unsigned long long v; unsigned long long v;
/* A zero length string is not a valid number. */ /* A string of zero length or excessive length is not a valid number. */
if (plen == slen) if (plen == slen || slen >= LONG_STR_SIZE)
return 0; return 0;
/* Special case: first and only digit is 0. */ /* Special case: first and only digit is 0. */
......
...@@ -34,10 +34,22 @@ ...@@ -34,10 +34,22 @@
#include "sds.h" #include "sds.h"
/* The maximum number of characters needed to represent a long double /* The maximum number of characters needed to represent a long double
* as a string (long double has a huge range). * as a string (long double has a huge range of some 4952 chars, see LDBL_MAX).
* This should be the size of the buffer given to ld2string */ * This should be the size of the buffer given to ld2string */
#define MAX_LONG_DOUBLE_CHARS 5*1024 #define MAX_LONG_DOUBLE_CHARS 5*1024
/* The maximum number of characters needed to represent a double
* as a string (double has a huge range of some 328 chars, see DBL_MAX).
* This should be the size of the buffer for sprintf with %f */
#define MAX_DOUBLE_CHARS 400
/* The maximum number of characters needed to for d2string call.
* Since it uses %g and not %f, some 40 chars should be enough. */
#define MAX_D2STRING_CHARS 128
/* Bytes needed for long -> str + '\0' */
#define LONG_STR_SIZE 21
/* long double to string conversion options */ /* long double to string conversion options */
typedef enum { typedef enum {
LD_STR_AUTO, /* %.17Lg */ LD_STR_AUTO, /* %.17Lg */
...@@ -63,6 +75,7 @@ int string2d(const char *s, size_t slen, double *dp); ...@@ -63,6 +75,7 @@ int string2d(const char *s, size_t slen, double *dp);
int trimDoubleString(char *buf, size_t len); int trimDoubleString(char *buf, size_t len);
int d2string(char *buf, size_t len, double value); int d2string(char *buf, size_t len, double value);
int ld2string(char *buf, size_t len, long double value, ld2string_mode mode); int ld2string(char *buf, size_t len, long double value, ld2string_mode mode);
int yesnotoi(char *s);
sds getAbsolutePath(char *filename); sds getAbsolutePath(char *filename);
long getTimeZone(void); long getTimeZone(void);
int pathIsBaseName(char *path); int pathIsBaseName(char *path);
......
#define REDIS_VERSION "6.9.241" #define REDIS_VERSION "6.9.242"
#define REDIS_VERSION_NUM 0x000609f1 #define REDIS_VERSION_NUM 0x000609f2
...@@ -608,7 +608,7 @@ int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) { ...@@ -608,7 +608,7 @@ int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
} }
/* Fills a struct with all information about an entry. /* Fills a struct with all information about an entry.
* This function is the "unsafe" alternative to the one blow. * This function is the "unsafe" alternative to the one below.
* Generally, all function that return a pointer to an element in the ziplist * Generally, all function that return a pointer to an element in the ziplist
* will assert that this element is valid, so it can be freely used. * will assert that this element is valid, so it can be freely used.
* Generally functions such ziplistGet assume the input pointer is already * Generally functions such ziplistGet assume the input pointer is already
...@@ -803,7 +803,7 @@ unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) { ...@@ -803,7 +803,7 @@ unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
/* Update tail offset after loop. */ /* Update tail offset after loop. */
if (tail == zl + prevoffset) { if (tail == zl + prevoffset) {
/* When the the last entry we need to update is also the tail, update tail offset /* When the last entry we need to update is also the tail, update tail offset
* unless this is the only entry that was updated (so the tail offset didn't change). */ * unless this is the only entry that was updated (so the tail offset didn't change). */
if (extra - delta != 0) { if (extra - delta != 0) {
ZIPLIST_TAIL_OFFSET(zl) = ZIPLIST_TAIL_OFFSET(zl) =
......
...@@ -203,6 +203,21 @@ proc wait_for_cluster_propagation {} { ...@@ -203,6 +203,21 @@ proc wait_for_cluster_propagation {} {
} }
} }
# Check if cluster's view of hostnames is consistent
proc are_hostnames_propagated {match_string} {
for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} {
set cfg [R $j cluster slots]
foreach node $cfg {
for {set i 2} {$i < [llength $node]} {incr i} {
if {! [string match $match_string [lindex [lindex [lindex $node $i] 3] 1]] } {
return 0
}
}
}
}
return 1
}
# Returns a parsed CLUSTER LINKS output of the instance identified # Returns a parsed CLUSTER LINKS output of the instance identified
# by the given `id` as a list of dictionaries, with each dictionary # by the given `id` as a list of dictionaries, with each dictionary
# corresponds to a link. # corresponds to a link.
......
...@@ -64,7 +64,7 @@ test "It is possible to write and read from the cluster" { ...@@ -64,7 +64,7 @@ test "It is possible to write and read from the cluster" {
} }
test "Function no-cluster flag" { test "Function no-cluster flag" {
R 1 function load lua test { R 1 function load {#!lua name=test
redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}} redis.register_function{function_name='f1', callback=function() return 'hello' end, flags={'no-cluster'}}
} }
catch {R 1 fcall f1 0} e catch {R 1 fcall f1 0} e
......
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