Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ad1eedbf9 | ||
|
|
565e139a56 | ||
|
|
b998e2035c | ||
|
|
923502a70b | ||
|
|
26826329f5 | ||
|
|
3508b9c440 | ||
|
|
4e55df4b5c | ||
|
|
ad171f5ce0 | ||
|
|
851e9fc48b | ||
|
|
aeee039a87 | ||
|
|
c44732ac58 | ||
|
|
c3806f5b72 | ||
|
|
a13106e001 | ||
|
|
bf71b120f1 | ||
|
|
75fa7879e6 | ||
|
|
b412c544fd | ||
|
|
29252391c4 | ||
|
|
1c08220022 | ||
|
|
02d38f6b51 | ||
|
|
b28fb3d753 | ||
|
|
dc2df135b3 | ||
|
|
c508cb6793 | ||
|
|
7229fa8d6d | ||
|
|
4d063bb6ba | ||
|
|
9d86ae4597 | ||
|
|
f739c27229 | ||
|
|
58ac57f665 | ||
|
|
adf2701cc9 | ||
|
|
de914ede93 | ||
|
|
ada206559d | ||
|
|
2c70d28295 | ||
|
|
ea2e51c630 | ||
|
|
93037f7642 | ||
|
|
7a808fd8a7 | ||
|
|
62689ef0cf | ||
|
|
b8579c225c | ||
|
|
885c4f856e | ||
|
|
6ddf0ea293 | ||
|
|
6eb996540c | ||
|
|
6dffc1b7a3 | ||
|
|
cb9dde3280 | ||
|
|
6b9f02ac12 | ||
|
|
447b373fc9 | ||
|
|
0c6ea46f2b | ||
|
|
f06f10e66b | ||
|
|
2e6f285009 | ||
|
|
00954f4d48 | ||
|
|
005d9fa861 | ||
|
|
22bcee5178 |
@@ -1,4 +1,4 @@
|
||||
This README is just a fast *quick start* document. You can find more detailed documentation at http://redis.io.
|
||||
This README is just a fast *quick start* document. You can find more detailed documentation at [redis.io](https://redis.io).
|
||||
|
||||
What is Redis?
|
||||
--------------
|
||||
|
||||
+4
-4
@@ -59,7 +59,7 @@
|
||||
# internet, binding to all the interfaces is dangerous and will expose the
|
||||
# instance to everybody on the internet. So by default we uncomment the
|
||||
# following bind directive, that will force Redis to listen only into
|
||||
# the IPv4 lookback interface address (this means Redis will be able to
|
||||
# the IPv4 loopback interface address (this means Redis will be able to
|
||||
# accept connections only from clients running into the same computer it
|
||||
# is running).
|
||||
#
|
||||
@@ -606,7 +606,7 @@ slave-priority 100
|
||||
# deletion of the object. It means that the server stops processing new commands
|
||||
# in order to reclaim all the memory associated with an object in a synchronous
|
||||
# way. If the key deleted is associated with a small object, the time needed
|
||||
# in order to execute th DEL command is very small and comparable to most other
|
||||
# in order to execute the DEL command is very small and comparable to most other
|
||||
# O(1) or O(log_N) commands in Redis. However if the key is associated with an
|
||||
# aggregated value containing millions of elements, the server can block for
|
||||
# a long time (even seconds) in order to complete the operation.
|
||||
@@ -621,7 +621,7 @@ slave-priority 100
|
||||
# It's up to the design of the application to understand when it is a good
|
||||
# idea to use one or the other. However the Redis server sometimes has to
|
||||
# delete keys or flush the whole database as a side effect of other operations.
|
||||
# Specifically Redis deletes objects independently of an user call in the
|
||||
# Specifically Redis deletes objects independently of a user call in the
|
||||
# following scenarios:
|
||||
#
|
||||
# 1) On eviction, because of the maxmemory and maxmemory policy configurations,
|
||||
@@ -914,7 +914,7 @@ lua-time-limit 5000
|
||||
# Docker and other containers).
|
||||
#
|
||||
# In order to make Redis Cluster working in such environments, a static
|
||||
# configuration where each node known its public address is needed. The
|
||||
# configuration where each node knows its public address is needed. The
|
||||
# following two options are used for this scope, and are:
|
||||
#
|
||||
# * cluster-announce-ip
|
||||
|
||||
+1
-1
@@ -353,7 +353,7 @@ void listJoin(list *l, list *o) {
|
||||
else
|
||||
l->head = o->head;
|
||||
|
||||
l->tail = o->tail;
|
||||
if (o->tail) l->tail = o->tail;
|
||||
l->len += o->len;
|
||||
|
||||
/* Setup other as an empty list. */
|
||||
|
||||
+1
-1
@@ -237,7 +237,7 @@ int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
||||
|
||||
static int anetSetReuseAddr(char *err, int fd) {
|
||||
int yes = 1;
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
/* Make sure connection-intensive things like the redis benchmark
|
||||
* will be able to close/open sockets a zillion of times */
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
|
||||
|
||||
+1
-1
@@ -653,7 +653,7 @@ unsigned int keyHashSlot(char *key, int keylen) {
|
||||
for (e = s+1; e < keylen; e++)
|
||||
if (key[e] == '}') break;
|
||||
|
||||
/* No '}' or nothing betweeen {} ? Hash the whole key. */
|
||||
/* No '}' or nothing between {} ? Hash the whole key. */
|
||||
if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
|
||||
|
||||
/* If we are here there is both a { and a } on its right. Hash
|
||||
|
||||
+11
-8
@@ -103,7 +103,7 @@ void activeExpireCycle(int type) {
|
||||
|
||||
int j, iteration = 0;
|
||||
int dbs_per_call = CRON_DBS_PER_CALL;
|
||||
long long start = ustime(), timelimit;
|
||||
long long start = ustime(), timelimit, elapsed;
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
@@ -140,7 +140,7 @@ void activeExpireCycle(int type) {
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
|
||||
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
|
||||
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
int expired;
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
|
||||
@@ -155,6 +155,7 @@ void activeExpireCycle(int type) {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
int ttl_samples;
|
||||
iteration++;
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if ((num = dictSize(db->expires)) == 0) {
|
||||
@@ -207,18 +208,20 @@ void activeExpireCycle(int type) {
|
||||
/* We can't block forever here even if there are many keys to
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
iteration++;
|
||||
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
|
||||
long long elapsed = ustime()-start;
|
||||
|
||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||
if (elapsed > timelimit) timelimit_exit = 1;
|
||||
elapsed = ustime()-start;
|
||||
if (elapsed > timelimit) {
|
||||
timelimit_exit = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (timelimit_exit) return;
|
||||
/* We don't repeat the cycle if there are less than 25% of keys
|
||||
* found expired in the current DB. */
|
||||
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
|
||||
+5
-1
@@ -79,7 +79,11 @@
|
||||
* Unconditionally aligning does not cost very much, so do it if unsure
|
||||
*/
|
||||
#ifndef STRICT_ALIGN
|
||||
# define STRICT_ALIGN !(defined(__i386) || defined (__amd64))
|
||||
# if !(defined(__i386) || defined (__amd64))
|
||||
# define STRICT_ALIGN 1
|
||||
# else
|
||||
# define STRICT_ALIGN 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
+42
-23
@@ -613,7 +613,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
||||
sds cmdname = sdsnew(name);
|
||||
|
||||
/* Check if the command name is busy. */
|
||||
if (lookupCommand((char*)name) != NULL) {
|
||||
if (lookupCommand(cmdname) != NULL) {
|
||||
sdsfree(cmdname);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
@@ -648,7 +648,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
||||
*
|
||||
* This is an internal function, Redis modules developers don't need
|
||||
* to use it. */
|
||||
void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver){
|
||||
void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
||||
RedisModule *module;
|
||||
|
||||
if (ctx->module != NULL) return;
|
||||
@@ -660,6 +660,15 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
|
||||
ctx->module = module;
|
||||
}
|
||||
|
||||
/* Return non-zero if the module name is busy.
|
||||
* Otherwise zero is returned. */
|
||||
int RM_IsModuleNameBusy(const char *name) {
|
||||
sds modulename = sdsnew(name);
|
||||
dictEntry *de = dictFind(modules,modulename);
|
||||
sdsfree(modulename);
|
||||
return de != NULL;
|
||||
}
|
||||
|
||||
/* Return the current UNIX time in milliseconds. */
|
||||
long long RM_Milliseconds(void) {
|
||||
return mstime();
|
||||
@@ -1162,7 +1171,9 @@ int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) {
|
||||
* in the context of a command execution. EXEC will be handled by the
|
||||
* RedisModuleCommandDispatcher() function. */
|
||||
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
if (ctx->client->flags & CLIENT_LUA) return;
|
||||
/* Skip this if client explicitly wrap the command with MULTI, or if
|
||||
* the module command was called by a script. */
|
||||
if (ctx->client->flags & (CLIENT_MULTI|CLIENT_LUA)) return;
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* If this is a thread safe context, we do not want to wrap commands
|
||||
@@ -3734,6 +3745,28 @@ void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
zfree(module);
|
||||
}
|
||||
|
||||
void moduleUnregisterCommands(struct RedisModule *module) {
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->proc == RedisModuleCommandDispatcher) {
|
||||
RedisModuleCommandProxy *cp =
|
||||
(void*)(unsigned long)cmd->getkeys_proc;
|
||||
sds cmdname = cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Load a module and initialize it. On success C_OK is returned, otherwise
|
||||
* C_ERR is returned. */
|
||||
int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
@@ -3754,7 +3787,10 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
return C_ERR;
|
||||
}
|
||||
if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) {
|
||||
if (ctx.module) moduleFreeModuleStructure(ctx.module);
|
||||
if (ctx.module) {
|
||||
moduleUnregisterCommands(ctx.module);
|
||||
moduleFreeModuleStructure(ctx.module);
|
||||
}
|
||||
dlclose(handle);
|
||||
serverLog(LL_WARNING,
|
||||
"Module %s initialization failed. Module not loaded",path);
|
||||
@@ -3788,25 +3824,7 @@ int moduleUnload(sds name) {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Unregister all the commands registered by this module. */
|
||||
dictIterator *di = dictGetSafeIterator(server.commands);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
struct redisCommand *cmd = dictGetVal(de);
|
||||
if (cmd->proc == RedisModuleCommandDispatcher) {
|
||||
RedisModuleCommandProxy *cp =
|
||||
(void*)(unsigned long)cmd->getkeys_proc;
|
||||
sds cmdname = cp->rediscmd->name;
|
||||
if (cp->module == module) {
|
||||
dictDelete(server.commands,cmdname);
|
||||
dictDelete(server.orig_commands,cmdname);
|
||||
sdsfree(cmdname);
|
||||
zfree(cp->rediscmd);
|
||||
zfree(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
moduleUnregisterCommands(module);
|
||||
|
||||
/* Unregister all the hooks. TODO: Yet no hooks support here. */
|
||||
|
||||
@@ -3901,6 +3919,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(Strdup);
|
||||
REGISTER_API(CreateCommand);
|
||||
REGISTER_API(SetModuleAttribs);
|
||||
REGISTER_API(IsModuleNameBusy);
|
||||
REGISTER_API(WrongArity);
|
||||
REGISTER_API(ReplyWithLongLong);
|
||||
REGISTER_API(ReplyWithError);
|
||||
|
||||
+22
-7
@@ -560,7 +560,7 @@ int getDoubleFromObject(const robj *o, double *target) {
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
eptr[0] != '\0' ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
@@ -602,7 +602,7 @@ int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
value = strtold(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
eptr[0] != '\0' ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
@@ -1012,11 +1012,25 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
}
|
||||
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <refcount|encoding|idletime> <key> */
|
||||
* Usage: OBJECT <refcount|encoding|idletime|freq> <key> */
|
||||
void objectCommand(client *c) {
|
||||
robj *o;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
void *blenp = addDeferredMultiBulkLength(c);
|
||||
int blen = 0;
|
||||
blen++; addReplyStatus(c,
|
||||
"OBJECT <subcommand> key. Subcommands:");
|
||||
blen++; addReplyStatus(c,
|
||||
"refcount -- Return the number of references of the value associated with the specified key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"encoding -- Return the kind of internal representation used in order to store the value associated with a key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"idletime -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.");
|
||||
blen++; addReplyStatus(c,
|
||||
"freq -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.");
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
@@ -1035,13 +1049,14 @@ void objectCommand(client *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
|
||||
addReplyError(c,"An LRU maxmemory policy is selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"A non-LFU maxmemory policy is selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
}
|
||||
addReplyLongLong(c,o->lru&255);
|
||||
} else {
|
||||
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime|freq)");
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help",
|
||||
(char *)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -943,6 +943,23 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
|
||||
/* If we are storing the replication information on disk, persist
|
||||
* the script cache as well: on successful PSYNC after a restart, we need
|
||||
* to be able to process any EVALSHA inside the replication backlog the
|
||||
* master will send us. */
|
||||
if (rsi && dictSize(server.lua_scripts)) {
|
||||
di = dictGetIterator(server.lua_scripts);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sha = dictGetKey(de);
|
||||
robj *body = dictGetVal(de);
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_SCRIPT) == -1) goto werr;
|
||||
if (rdbSaveRawString(rdb,(unsigned char*)sha,sdslen(sha)) == -1) goto werr;
|
||||
if (rdbSaveRawString(rdb,body->ptr,sdslen(body->ptr)) == -1)
|
||||
goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
|
||||
@@ -1563,6 +1580,33 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
dictExpand(db->dict,db_size);
|
||||
dictExpand(db->expires,expires_size);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_SCRIPT) {
|
||||
sds sha;
|
||||
robj *body;
|
||||
char funcname[43];
|
||||
|
||||
if ((sha = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) goto eoferr;
|
||||
if (sdslen(sha) != 40) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Lua script stored into the RDB file has invalid "
|
||||
"name length: '%s'", sha);
|
||||
}
|
||||
if ((body = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
|
||||
/* Compose the Lua function name, and load it. */
|
||||
funcname[0] = 'f';
|
||||
funcname[1] = '_';
|
||||
memcpy(funcname+2,sha,41);
|
||||
if (luaCreateFunction(NULL,server.lua,funcname,body) == C_ERR) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Can't load Lua script from RDB file! "
|
||||
"Script SHA1: %s BODY: %d",
|
||||
sha, body->ptr);
|
||||
}
|
||||
sdsfree(sha);
|
||||
decrRefCount(body);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_AUX) {
|
||||
/* AUX: generic string-string fields. Use to add state to RDB
|
||||
* which is backward compatible. Implementations of RDB loading
|
||||
@@ -2000,6 +2044,9 @@ void bgsaveCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
|
||||
if (server.rdb_child_pid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
} else if (server.aof_child_pid != -1) {
|
||||
@@ -2012,7 +2059,7 @@ void bgsaveCommand(client *c) {
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||
"possible.");
|
||||
}
|
||||
} else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {
|
||||
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
@@ -2033,22 +2080,37 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
|
||||
*rsi = rsi_init;
|
||||
|
||||
/* If the instance is a master, we can populate the replication info
|
||||
* in all the cases, even if sometimes in incomplete (but safe) form. */
|
||||
if (!server.masterhost) {
|
||||
if (server.repl_backlog) rsi->repl_stream_db = server.slaveseldb;
|
||||
/* Note that if repl_backlog is NULL, it means that histories
|
||||
* following from this point will trigger a full synchronization
|
||||
* generating a SELECT statement, so we can leave the currently
|
||||
* selected DB set to -1. This allows a restarted master to reload
|
||||
* its replication ID/offset when there are no connected slaves. */
|
||||
* only when repl_backlog is not NULL. If the repl_backlog is NULL,
|
||||
* it means that the instance isn't in any replication chains. In this
|
||||
* scenario the replication info is useless, because when a slave
|
||||
* connects to us, the NULL repl_backlog will trigger a full
|
||||
* synchronization, at the same time we will use a new replid and clear
|
||||
* replid2. */
|
||||
if (!server.masterhost && server.repl_backlog) {
|
||||
/* Note that when server.slaveseldb is -1, it means that this master
|
||||
* didn't apply any write commands after a full synchronization.
|
||||
* So we can let repl_stream_db be 0, this allows a restarted slave
|
||||
* to reload replication ID/offset, it's safe because the next write
|
||||
* command must generate a SELECT statement. */
|
||||
rsi->repl_stream_db = server.slaveseldb == -1 ? 0 : server.slaveseldb;
|
||||
return rsi;
|
||||
}
|
||||
|
||||
/* If the instance is a slave we need a connected master in order to
|
||||
* fetch the currently selected DB. */
|
||||
/* If the instance is a slave we need a connected master
|
||||
* in order to fetch the currently selected DB. */
|
||||
if (server.master) {
|
||||
rsi->repl_stream_db = server.master->db->id;
|
||||
return rsi;
|
||||
}
|
||||
|
||||
/* If we have a cached master we can use it in order to populate the
|
||||
* replication selected DB info inside the RDB file: the slave can
|
||||
* increment the master_repl_offset only from data arriving from the
|
||||
* master, so if we are disconnected the offset in the cached master
|
||||
* is valid. */
|
||||
if (server.cached_master) {
|
||||
rsi->repl_stream_db = server.cached_master->db->id;
|
||||
return rsi;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 14))
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define RDB_OPCODE_SCRIPT 249
|
||||
#define RDB_OPCODE_AUX 250
|
||||
#define RDB_OPCODE_RESIZEDB 251
|
||||
#define RDB_OPCODE_EXPIRETIME_MS 252
|
||||
|
||||
@@ -193,12 +193,12 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
buf[9] = '\0';
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
rdbCheckError("Wrong signature trying to load DB from file");
|
||||
return 1;
|
||||
goto err;
|
||||
}
|
||||
rdbver = atoi(buf+5);
|
||||
if (rdbver < 1 || rdbver > RDB_VERSION) {
|
||||
rdbCheckError("Can't handle RDB format version %d",rdbver);
|
||||
return 1;
|
||||
goto err;
|
||||
}
|
||||
|
||||
startLoading(fp);
|
||||
@@ -270,7 +270,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
} else {
|
||||
if (!rdbIsObjectType(type)) {
|
||||
rdbCheckError("Invalid object type: %d", type);
|
||||
return 1;
|
||||
goto err;
|
||||
}
|
||||
rdbstate.key_type = type;
|
||||
}
|
||||
@@ -307,6 +307,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbCheckInfo("RDB file was saved with checksum disabled: no check performed.");
|
||||
} else if (cksum != expected) {
|
||||
rdbCheckError("RDB CRC error");
|
||||
goto err;
|
||||
} else {
|
||||
rdbCheckInfo("Checksum OK");
|
||||
}
|
||||
@@ -321,6 +322,8 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
} else {
|
||||
rdbCheckError("Unexpected EOF reading RDB file");
|
||||
}
|
||||
err:
|
||||
if (closefile) fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
+91
-2
@@ -198,6 +198,92 @@ static sds getDotfilePath(char *envoverride, char *dotfilename) {
|
||||
return dotPath;
|
||||
}
|
||||
|
||||
/* URL-style percent decoding. */
|
||||
#define isHexChar(c) (isdigit(c) || (c >= 'a' && c <= 'f'))
|
||||
#define decodeHexChar(c) (isdigit(c) ? c - '0' : c - 'a' + 10)
|
||||
#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l))
|
||||
|
||||
static sds percentDecode(const char *pe, size_t len) {
|
||||
const char *end = pe + len;
|
||||
sds ret = sdsempty();
|
||||
const char *curr = pe;
|
||||
|
||||
while (curr < end) {
|
||||
if (*curr == '%') {
|
||||
if ((end - curr) < 2) {
|
||||
fprintf(stderr, "Incomplete URI encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char h = tolower(*(++curr));
|
||||
char l = tolower(*(++curr));
|
||||
if (!isHexChar(h) || !isHexChar(l)) {
|
||||
fprintf(stderr, "Illegal character in URI encoding\n");
|
||||
exit(1);
|
||||
}
|
||||
char c = decodeHex(h, l);
|
||||
ret = sdscatlen(ret, &c, 1);
|
||||
curr++;
|
||||
} else {
|
||||
ret = sdscatlen(ret, curr++, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse a URI and extract the server connection information.
|
||||
* URI scheme is based on the the provisional specification[1] excluding support
|
||||
* for query parameters. Valid URIs are:
|
||||
* scheme: "redis://"
|
||||
* authority: [<username> ":"] <password> "@"] [<hostname> [":" <port>]]
|
||||
* path: ["/" [<db>]]
|
||||
*
|
||||
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
|
||||
static void parseRedisUri(const char *uri) {
|
||||
|
||||
const char *scheme = "redis://";
|
||||
const char *curr = uri;
|
||||
const char *end = uri + strlen(uri);
|
||||
const char *userinfo, *username, *port, *host, *path;
|
||||
|
||||
/* URI must start with a valid scheme. */
|
||||
if (strncasecmp(scheme, curr, strlen(scheme))) {
|
||||
fprintf(stderr,"Invalid URI scheme\n");
|
||||
exit(1);
|
||||
}
|
||||
curr += strlen(scheme);
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract user info. */
|
||||
if ((userinfo = strchr(curr,'@'))) {
|
||||
if ((username = strchr(curr, ':')) && username < userinfo) {
|
||||
/* If provided, username is ignored. */
|
||||
curr = username + 1;
|
||||
}
|
||||
|
||||
config.auth = percentDecode(curr, userinfo - curr);
|
||||
curr = userinfo + 1;
|
||||
}
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract host and port. */
|
||||
path = strchr(curr, '/');
|
||||
if (*curr != '/') {
|
||||
host = path ? path - 1 : end;
|
||||
if ((port = strchr(curr, ':'))) {
|
||||
config.hostport = atoi(port + 1);
|
||||
host = port - 1;
|
||||
}
|
||||
config.hostip = sdsnewlen(curr, host - curr + 1);
|
||||
}
|
||||
curr = path ? path + 1 : end;
|
||||
if (curr == end) return;
|
||||
|
||||
/* Extract database number. */
|
||||
config.dbnum = atoi(curr);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Help functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -624,7 +710,7 @@ int isColorTerm(void) {
|
||||
return t != NULL && strstr(t,"xterm") != NULL;
|
||||
}
|
||||
|
||||
/* Helpe function for sdsCatColorizedLdbReply() appending colorize strings
|
||||
/* Helper function for sdsCatColorizedLdbReply() appending colorize strings
|
||||
* to an SDS string. */
|
||||
sds sdscatcolor(sds o, char *s, size_t len, char *color) {
|
||||
if (!isColorTerm()) return sdscatlen(o,s,len);
|
||||
@@ -1002,6 +1088,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
||||
config.auth = argv[++i];
|
||||
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
||||
parseRedisUri(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--raw")) {
|
||||
config.output = OUTPUT_RAW;
|
||||
} else if (!strcmp(argv[i],"--no-raw")) {
|
||||
@@ -1109,6 +1197,7 @@ static void usage(void) {
|
||||
" -p <port> Server port (default: 6379).\n"
|
||||
" -s <socket> Server socket (overrides hostname and port).\n"
|
||||
" -a <password> Password to use when connecting to the server.\n"
|
||||
" -u <uri> Server URI.\n"
|
||||
" -r <repeat> Execute specified command N times.\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
" It is possible to specify sub-second times like -i 0.1.\n"
|
||||
@@ -2364,7 +2453,7 @@ static void statMode(void) {
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf("%-8s",buf);
|
||||
|
||||
/* Requets */
|
||||
/* Requests */
|
||||
aux = getLongInfoField(reply->str,"total_commands_processed");
|
||||
sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
|
||||
printf("%-19s",buf);
|
||||
|
||||
+4
-1
@@ -143,7 +143,8 @@ void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
|
||||
@@ -263,6 +264,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(Strdup);
|
||||
REDISMODULE_GET_API(CreateCommand);
|
||||
REDISMODULE_GET_API(SetModuleAttribs);
|
||||
REDISMODULE_GET_API(IsModuleNameBusy);
|
||||
REDISMODULE_GET_API(WrongArity);
|
||||
REDISMODULE_GET_API(ReplyWithLongLong);
|
||||
REDISMODULE_GET_API(ReplyWithError);
|
||||
@@ -370,6 +372,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(AbortBlock);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -2613,6 +2613,23 @@ void replicationCron(void) {
|
||||
time_t idle = server.unixtime - server.repl_no_slaves_since;
|
||||
|
||||
if (idle > server.repl_backlog_time_limit) {
|
||||
/* When we free the backlog, we always use a new
|
||||
* replication ID and clear the ID2. This is needed
|
||||
* because when there is no backlog, the master_repl_offset
|
||||
* is not updated, but we would still retain our replication
|
||||
* ID, leading to the following problem:
|
||||
*
|
||||
* 1. We are a master instance.
|
||||
* 2. Our slave is promoted to master. It's repl-id-2 will
|
||||
* be the same as our repl-id.
|
||||
* 3. We, yet as master, receive some updates, that will not
|
||||
* increment the master_repl_offset.
|
||||
* 4. Later we are turned into a slave, connecto to the new
|
||||
* master that will accept our PSYNC request by second
|
||||
* replication ID, but there will be data inconsistency
|
||||
* because we received writes. */
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
freeReplicationBacklog();
|
||||
serverLog(LL_NOTICE,
|
||||
"Replication backlog freed after %d seconds "
|
||||
|
||||
+18
-5
@@ -358,6 +358,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int inuse = 0; /* Recursive calls detection. */
|
||||
|
||||
/* Reflect MULTI state */
|
||||
if (server.lua_multi_emitted || (server.lua_caller->flags & CLIENT_MULTI)) {
|
||||
c->flags |= CLIENT_MULTI;
|
||||
} else {
|
||||
c->flags &= ~CLIENT_MULTI;
|
||||
}
|
||||
|
||||
/* By using Lua debug hooks it is possible to trigger a recursive call
|
||||
* to luaRedisGenericCommand(), which normally should never happen.
|
||||
* To make this function reentrant is futile and makes it slower, but
|
||||
@@ -535,6 +542,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
* a Lua script in the context of AOF and slaves. */
|
||||
if (server.lua_replicate_commands &&
|
||||
!server.lua_multi_emitted &&
|
||||
!(server.lua_caller->flags & CLIENT_MULTI) &&
|
||||
server.lua_write_dirty &&
|
||||
server.lua_repl != PROPAGATE_NONE)
|
||||
{
|
||||
@@ -1152,16 +1160,21 @@ int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,
|
||||
"Error compiling script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
sdsfree(funcdef);
|
||||
return C_ERR;
|
||||
}
|
||||
sdsfree(funcdef);
|
||||
if (lua_pcall(lua,0,0,0)) {
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
if (c != NULL) {
|
||||
addReplyErrorFormat(c,"Error running script (new function): %s\n",
|
||||
lua_tostring(lua,-1));
|
||||
}
|
||||
lua_pop(lua,1);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -1172,7 +1185,7 @@ int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
|
||||
{
|
||||
int retval = dictAdd(server.lua_scripts,
|
||||
sdsnewlen(funcname+2,40),body);
|
||||
serverAssertWithInfo(c,NULL,retval == DICT_OK);
|
||||
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
|
||||
incrRefCount(body);
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
+3
-2
@@ -276,7 +276,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,3,"r",0,NULL,2,2,2,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
|
||||
{"memory",memoryCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
@@ -3540,7 +3540,8 @@ void loadDataFromDisk(void) {
|
||||
rsi.repl_id_is_set &&
|
||||
rsi.repl_offset != -1 &&
|
||||
/* Note that older implementations may save a repl_stream_db
|
||||
* of -1 inside the RDB file. */
|
||||
* of -1 inside the RDB file in a wrong way, see more information
|
||||
* in function rdbPopulateSaveInfo. */
|
||||
rsi.repl_stream_db != -1)
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
|
||||
@@ -1781,6 +1781,7 @@ void scriptingInit(int setup);
|
||||
int ldbRemoveChild(pid_t pid);
|
||||
void ldbKillForkedSessions(void);
|
||||
int ldbPendingChildren(void);
|
||||
int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body);
|
||||
|
||||
/* Blocked clients */
|
||||
void processUnblockedClients(void);
|
||||
|
||||
+5
-1
@@ -39,7 +39,11 @@
|
||||
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
|
||||
|
||||
#if !defined(HAVE_SETPROCTITLE)
|
||||
#define HAVE_SETPROCTITLE (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#define HAVE_SETPROCTITLE 1
|
||||
#else
|
||||
#define HAVE_SETPROCTITLE 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -287,8 +287,8 @@ int hashTypeDelete(robj *o, sds field) {
|
||||
if (fptr != NULL) {
|
||||
fptr = ziplistFind(fptr, (unsigned char*)field, sdslen(field), 1);
|
||||
if (fptr != NULL) {
|
||||
zl = ziplistDelete(zl,&fptr);
|
||||
zl = ziplistDelete(zl,&fptr);
|
||||
zl = ziplistDelete(zl,&fptr); /* Delete the key. */
|
||||
zl = ziplistDelete(zl,&fptr); /* Delete the value. */
|
||||
o->ptr = zl;
|
||||
deleted = 1;
|
||||
}
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ proc end_tests {} {
|
||||
puts "GOOD! No errors."
|
||||
exit 0
|
||||
} else {
|
||||
puts "WARNING $::failed tests faield."
|
||||
puts "WARNING $::failed test(s) failed."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,18 @@ start_server {tags {"latency-monitor"}} {
|
||||
assert {[r latency reset] > 0}
|
||||
assert {[r latency latest] eq {}}
|
||||
}
|
||||
|
||||
test {LATENCY of expire events are correctly collected} {
|
||||
r config set latency-monitor-threshold 20
|
||||
r eval {
|
||||
local i = 0
|
||||
while (i < 1000000) do
|
||||
redis.call('sadd','mybigkey',i)
|
||||
i = i+1
|
||||
end
|
||||
} 0
|
||||
r pexpire mybigkey 1
|
||||
after 500
|
||||
assert_match {*expire-cycle*} [r latency latest]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,4 +144,11 @@ start_server {tags {"incr"}} {
|
||||
r set foo 1
|
||||
roundFloat [r incrbyfloat foo -1.1]
|
||||
} {-0.1}
|
||||
|
||||
test {string to double with null terminator} {
|
||||
r set foo 1
|
||||
r setrange foo 2 2
|
||||
catch {r incrbyfloat foo 1} err
|
||||
format $err
|
||||
} {ERR*valid*}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user