summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--redis.conf8
-rw-r--r--src/adlist.c2
-rw-r--r--src/cluster.c5
-rw-r--r--src/db.c4
-rw-r--r--src/debug.c4
-rw-r--r--src/dict.c2
-rw-r--r--src/expire.c19
-rw-r--r--src/lzfP.h6
-rw-r--r--src/module.c153
-rw-r--r--src/modules/testmodule.c82
-rw-r--r--src/object.c41
-rw-r--r--src/rdb.c77
-rw-r--r--src/rdb.h1
-rw-r--r--src/redis-benchmark.c4
-rw-r--r--src/redis-check-rdb.c9
-rw-r--r--src/redis-cli.c94
-rw-r--r--src/redismodule.h31
-rw-r--r--src/replication.c47
-rw-r--r--src/scripting.c8
-rw-r--r--src/sds.c17
-rw-r--r--src/server.c41
-rw-r--r--src/setproctitle.c6
-rw-r--r--src/slowlog.c11
-rw-r--r--src/t_hash.c4
-rw-r--r--tests/instances.tcl2
-rw-r--r--tests/unit/latency-monitor.tcl14
-rw-r--r--tests/unit/type/incr.tcl7
-rw-r--r--tests/unit/type/zset.tcl4
29 files changed, 590 insertions, 115 deletions
diff --git a/README.md b/README.md
index 70a15790f..42ab47853 100644
--- a/README.md
+++ b/README.md
@@ -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?
--------------
diff --git a/redis.conf b/redis.conf
index c54dba392..54ba1298e 100644
--- a/redis.conf
+++ b/redis.conf
@@ -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
diff --git a/src/adlist.c b/src/adlist.c
index e87d25cee..ec5f8bbf4 100644
--- a/src/adlist.c
+++ b/src/adlist.c
@@ -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. */
diff --git a/src/cluster.c b/src/cluster.c
index a9fedce0c..dc0a444ae 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -243,6 +243,7 @@ int clusterLoadConfig(char *filename) {
*p = '\0';
direction = p[1]; /* Either '>' or '<' */
slot = atoi(argv[j]+1);
+ if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
p += 3;
cn = clusterLookupNode(p);
if (!cn) {
@@ -262,6 +263,8 @@ int clusterLoadConfig(char *filename) {
} else {
start = stop = atoi(argv[j]);
}
+ if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
+ if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
while(start <= stop) clusterAddSlot(n, start++);
}
@@ -650,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
diff --git a/src/db.c b/src/db.c
index 7d1504d30..71c642d00 100644
--- a/src/db.c
+++ b/src/db.c
@@ -416,7 +416,9 @@ void flushallCommand(client *c) {
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
int saved_dirty = server.dirty;
- rdbSave(server.rdb_filename,NULL);
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ rdbSave(server.rdb_filename,rsiptr);
server.dirty = saved_dirty;
}
server.dirty++;
diff --git a/src/debug.c b/src/debug.c
index d6e12ec2a..5c3fd3471 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -335,7 +335,9 @@ void debugCommand(client *c) {
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
serverAssertWithInfo(c,c->argv[0],1 == 2);
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
- if (rdbSave(server.rdb_filename,NULL) != C_OK) {
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
addReply(c,shared.err);
return;
}
diff --git a/src/dict.c b/src/dict.c
index 69fb3b8f8..210d50dcd 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -940,7 +940,7 @@ static unsigned long _dictNextPower(unsigned long size)
{
unsigned long i = DICT_HT_INITIAL_SIZE;
- if (size >= LONG_MAX) return LONG_MAX;
+ if (size >= LONG_MAX) return LONG_MAX + 1LU;
while(1) {
if (i >= size)
return i;
diff --git a/src/expire.c b/src/expire.c
index a02fe566a..81c9e23f5 100644
--- a/src/expire.c
+++ b/src/expire.c
@@ -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);
}
/*-----------------------------------------------------------------------------
diff --git a/src/lzfP.h b/src/lzfP.h
index c6d2e096c..93c27b42d 100644
--- a/src/lzfP.h
+++ b/src/lzfP.h
@@ -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
/*
diff --git a/src/module.c b/src/module.c
index fda68b273..8a4c40f12 100644
--- a/src/module.c
+++ b/src/module.c
@@ -442,9 +442,7 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
client *c = ctx->client;
- /* We don't want any automatic propagation here since in modules we handle
- * replication / AOF propagation in explicit ways. */
- preventCommandPropagation(c);
+ if (c->flags & CLIENT_LUA) return;
/* Handle the replication of the final EXEC, since whatever a command
* emits is always wrappered around MULTI/EXEC. */
@@ -615,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;
}
@@ -650,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;
@@ -662,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();
@@ -1164,6 +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) {
+ /* 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
@@ -1216,6 +1226,7 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
/* Release the argv. */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv);
+ server.dirty++;
return REDISMODULE_OK;
}
@@ -1234,6 +1245,7 @@ int RM_ReplicateVerbatim(RedisModuleCtx *ctx) {
alsoPropagate(ctx->client->cmd,ctx->client->db->id,
ctx->client->argv,ctx->client->argc,
PROPAGATE_AOF|PROPAGATE_REPL);
+ server.dirty++;
return REDISMODULE_OK;
}
@@ -1262,6 +1274,74 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
return ctx->client->db->id;
}
+
+/* Return the current context's flags. The flags provide information on the
+ * current request context (whether the client is a Lua script or in a MULTI),
+ * and about the Redis instance in general, i.e replication and persistence.
+ *
+ * The available flags are:
+ *
+ * * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script
+ *
+ * * REDISMODULE_CTX_FLAGS_MULTI: The command is running inside a transaction
+ *
+ * * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
+ *
+ * * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
+ *
+ * * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
+ *
+ * * REDISMODULE_CTX_FLAGS_CLUSTER: The Redis instance is in cluster mode
+ *
+ * * REDISMODULE_CTX_FLAGS_AOF: The Redis instance has AOF enabled
+ *
+ * * REDISMODULE_CTX_FLAGS_RDB: The instance has RDB enabled
+ *
+ * * REDISMODULE_CTX_FLAGS_MAXMEMORY: The instance has Maxmemory set
+ *
+ * * REDISMODULE_CTX_FLAGS_EVICT: Maxmemory is set and has an eviction
+ * policy that may delete keys
+ */
+int RM_GetContextFlags(RedisModuleCtx *ctx) {
+
+ int flags = 0;
+ /* Client specific flags */
+ if (ctx->client) {
+ if (ctx->client->flags & CLIENT_LUA)
+ flags |= REDISMODULE_CTX_FLAGS_LUA;
+ if (ctx->client->flags & CLIENT_MULTI)
+ flags |= REDISMODULE_CTX_FLAGS_MULTI;
+ }
+
+ if (server.cluster_enabled)
+ flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
+
+ /* Maxmemory and eviction policy */
+ if (server.maxmemory > 0) {
+ flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
+
+ if (server.maxmemory_policy != MAXMEMORY_NO_EVICTION)
+ flags |= REDISMODULE_CTX_FLAGS_EVICT;
+ }
+
+ /* Persistence flags */
+ if (server.aof_state != AOF_OFF)
+ flags |= REDISMODULE_CTX_FLAGS_AOF;
+ if (server.saveparamslen > 0)
+ flags |= REDISMODULE_CTX_FLAGS_RDB;
+
+ /* Replication flags */
+ if (server.masterhost == NULL) {
+ flags |= REDISMODULE_CTX_FLAGS_MASTER;
+ } else {
+ flags |= REDISMODULE_CTX_FLAGS_SLAVE;
+ if (server.repl_slave_ro)
+ flags |= REDISMODULE_CTX_FLAGS_READONLY;
+ }
+
+ return flags;
+}
+
/* Change the currently selected DB. Returns an error if the id
* is out of range.
*
@@ -3333,14 +3413,16 @@ void unblockClientFromModule(client *c) {
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms) {
client *c = ctx->client;
int islua = c->flags & CLIENT_LUA;
+ int ismulti = c->flags & CLIENT_MULTI;
c->bpop.module_blocked_handle = zmalloc(sizeof(RedisModuleBlockedClient));
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
/* We need to handle the invalid operation of calling modules blocking
- * commands from Lua. We actually create an already aborted (client set to
- * NULL) blocked client handle, and actually reply to Lua with an error. */
- bc->client = islua ? NULL : c;
+ * commands from Lua or MULTI. We actually create an already aborted
+ * (client set to NULL) blocked client handle, and actually reply with
+ * an error. */
+ bc->client = (islua || ismulti) ? NULL : c;
bc->module = ctx->module;
bc->reply_callback = reply_callback;
bc->timeout_callback = timeout_callback;
@@ -3351,9 +3433,11 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
bc->dbid = c->db->id;
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
- if (islua) {
+ if (islua || ismulti) {
c->bpop.module_blocked_handle = NULL;
- addReplyError(c,"Blocking module command called from Lua script");
+ addReplyError(c, islua ?
+ "Blocking module command called from Lua script" :
+ "Blocking module command called from transaction");
} else {
blockClient(c,BLOCKED_MODULE);
}
@@ -3661,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) {
@@ -3681,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);
@@ -3715,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. */
@@ -3828,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);
@@ -3891,6 +3983,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(IsKeysPositionRequest);
REGISTER_API(KeyAtPos);
REGISTER_API(GetClientId);
+ REGISTER_API(GetContextFlags);
REGISTER_API(PoolAlloc);
REGISTER_API(CreateDataType);
REGISTER_API(ModuleTypeSetValue);
diff --git a/src/modules/testmodule.c b/src/modules/testmodule.c
index 8da45c0ea..a0d706fea 100644
--- a/src/modules/testmodule.c
+++ b/src/modules/testmodule.c
@@ -121,6 +121,81 @@ int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
}
+/* TEST.CTXFLAGS -- Test GetContextFlags. */
+int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argc);
+ REDISMODULE_NOT_USED(argv);
+
+ RedisModule_AutoMemory(ctx);
+
+ int ok = 1;
+ const char *errString = NULL;
+
+ #define FAIL(msg) \
+ { \
+ ok = 0; \
+ errString = msg; \
+ goto end; \
+ }
+
+ int flags = RedisModule_GetContextFlags(ctx);
+ if (flags == 0) {
+ FAIL("Got no flags");
+ }
+
+ if (flags & REDISMODULE_CTX_FLAGS_LUA) FAIL("Lua flag was set");
+ if (flags & REDISMODULE_CTX_FLAGS_MULTI) FAIL("Multi flag was set");
+
+ if (flags & REDISMODULE_CTX_FLAGS_AOF) FAIL("AOF Flag was set")
+ /* Enable AOF to test AOF flags */
+ RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "yes");
+ flags = RedisModule_GetContextFlags(ctx);
+ if (!(flags & REDISMODULE_CTX_FLAGS_AOF))
+ FAIL("AOF Flag not set after config set");
+
+ if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set");
+ /* Enable RDB to test RDB flags */
+ RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1");
+ flags = RedisModule_GetContextFlags(ctx);
+ if (!(flags & REDISMODULE_CTX_FLAGS_RDB))
+ FAIL("RDB Flag was not set after config set");
+
+ if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) FAIL("Master flag was not set");
+ if (flags & REDISMODULE_CTX_FLAGS_SLAVE) FAIL("Slave flag was set");
+ if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set");
+ if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set");
+
+ if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was set");
+ ;
+ RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000");
+ flags = RedisModule_GetContextFlags(ctx);
+ if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY))
+ FAIL("Maxmemory flag was not set after config set");
+
+ if (flags & REDISMODULE_CTX_FLAGS_EVICT) FAIL("Eviction flag was set");
+ RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy",
+ "allkeys-lru");
+ flags = RedisModule_GetContextFlags(ctx);
+ if (!(flags & REDISMODULE_CTX_FLAGS_EVICT))
+ FAIL("Eviction flag was not set after config set");
+
+ end:
+ /* Revert config changes */
+ RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "no");
+ RedisModule_Call(ctx, "config", "ccc", "set", "save", "");
+ RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0");
+ RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "noeviction");
+
+ if (!ok) {
+ RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s",
+ errString);
+ return RedisModule_ReplyWithSimpleString(ctx, "ERR");
+ }
+
+ return RedisModule_ReplyWithSimpleString(ctx, "OK");
+ }
+
+
/* ----------------------------- Test framework ----------------------------- */
/* Return 1 if the reply matches the specified string, otherwise log errors
@@ -188,6 +263,9 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
T("test.call","");
if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
+ T("test.ctxflags","");
+ if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail;
+
T("test.string.append","");
if (!TestAssertStringReply(ctx,reply,"foobar",6)) goto fail;
@@ -229,6 +307,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
TestStringPrintf,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
+ if (RedisModule_CreateCommand(ctx,"test.ctxflags",
+ TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
if (RedisModule_CreateCommand(ctx,"test.it",
TestIt,"readonly",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
diff --git a/src/object.c b/src/object.c
index 2565ed59f..8c33d7ef6 100644
--- a/src/object.c
+++ b/src/object.c
@@ -558,11 +558,11 @@ int getDoubleFromObject(const robj *o, double *target) {
if (sdsEncodedObject(o)) {
errno = 0;
value = strtod(o->ptr, &eptr);
- if (isspace(((const char*)o->ptr)[0]) ||
- eptr[0] != '\0' ||
+ if (sdslen(o->ptr) == 0 ||
+ isspace(((const char*)o->ptr)[0]) ||
+ (size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
(errno == ERANGE &&
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
- errno == EINVAL ||
isnan(value))
return C_ERR;
} else if (o->encoding == OBJ_ENCODING_INT) {
@@ -600,8 +600,12 @@ int getLongDoubleFromObject(robj *o, long double *target) {
if (sdsEncodedObject(o)) {
errno = 0;
value = strtold(o->ptr, &eptr);
- if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
- errno == ERANGE || isnan(value))
+ if (sdslen(o->ptr) == 0 ||
+ isspace(((const char*)o->ptr)[0]) ||
+ (size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
+ (errno == ERANGE &&
+ (value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
+ isnan(value))
return C_ERR;
} else if (o->encoding == OBJ_ENCODING_INT) {
value = (long)o->ptr;
@@ -1008,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);
@@ -1031,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);
}
}
@@ -1070,7 +1089,7 @@ void memoryCommand(client *c) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
size_t usage = objectComputeSize(o,samples);
- usage += sdsAllocSize(c->argv[1]->ptr);
+ usage += sdsAllocSize(c->argv[2]->ptr);
usage += sizeof(dictEntry);
addReplyLongLong(c,usage);
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
diff --git a/src/rdb.c b/src/rdb.c
index 792c8ff94..00106cac4 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -656,7 +656,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;
nwritten += n;
- do {
+ while(node) {
if (quicklistNodeIsCompressed(node)) {
void *data;
size_t compress_len = quicklistGetLzf(node, &data);
@@ -666,7 +666,8 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
nwritten += n;
}
- } while ((node = node->next));
+ node = node->next;
+ }
} else {
serverPanic("Unknown list encoding");
}
@@ -858,16 +859,14 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
/* Handle saving options that generate aux fields. */
if (rsi) {
- if (rsi->repl_stream_db &&
- rdbSaveAuxFieldStrInt(rdb,"repl-stream-db",rsi->repl_stream_db)
- == -1)
- {
- return -1;
- }
+ if (rdbSaveAuxFieldStrInt(rdb,"repl-stream-db",rsi->repl_stream_db)
+ == -1) return -1;
+ if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid)
+ == -1) return -1;
+ if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset)
+ == -1) return -1;
}
if (rdbSaveAuxFieldStrInt(rdb,"aof-preamble",aof_preamble) == -1) return -1;
- if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid) == -1) return -1;
- if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset) == -1) return -1;
return 1;
}
@@ -1977,7 +1976,9 @@ void saveCommand(client *c) {
addReplyError(c,"Background save already in progress");
return;
}
- if (rdbSave(server.rdb_filename,NULL) == C_OK) {
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
addReply(c,shared.ok);
} else {
addReply(c,shared.err);
@@ -1999,6 +2000,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) {
@@ -2011,9 +2015,58 @@ 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);
}
}
+
+/* Populate the rdbSaveInfo structure used to persist the replication
+ * information inside the RDB file. Currently the structure explicitly
+ * contains just the currently selected DB from the master stream, however
+ * if the rdbSave*() family functions receive a NULL rsi structure also
+ * the Replication ID/offset is not saved. The function popultes 'rsi'
+ * that is normally stack-allocated in the caller, returns the populated
+ * pointer if the instance has a valid master client, otherwise NULL
+ * is returned, and the RDB saving will not persist any replication related
+ * information. */
+rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
+ rdbSaveInfo rsi_init = RDB_SAVE_INFO_INIT;
+ *rsi = rsi_init;
+
+ /* If the instance is a master, we can populate the replication info
+ * 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 (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;
+}
diff --git a/src/rdb.h b/src/rdb.h
index a22cb33ce..62a13f444 100644
--- a/src/rdb.h
+++ b/src/rdb.h
@@ -147,5 +147,6 @@ int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
int rdbSaveBinaryFloatValue(rio *rdb, float val);
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi);
+rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
#endif
diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c
index dec8ecb52..928ec31e0 100644
--- a/src/redis-benchmark.c
+++ b/src/redis-benchmark.c
@@ -572,8 +572,8 @@ usage:
" -a <password> Password for Redis Auth\n"
" -c <clients> Number of parallel connections (default 50)\n"
" -n <requests> Total number of requests (default 100000)\n"
-" -d <size> Data size of SET/GET value in bytes (default 2)\n"
-" --dbnum <db> SELECT the specified db number (default 0)\n"
+" -d <size> Data size of SET/GET value in bytes (default 3)\n"
+" --dbnum <db> SELECT the specified db number (default 0)\n"
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n"
" Using this option the benchmark will expand the string __rand_int__\n"
diff --git a/src/redis-check-rdb.c b/src/redis-check-rdb.c
index 4027536e5..71ac50d03 100644
--- a/src/redis-check-rdb.c
+++ b/src/redis-check-rdb.c
@@ -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;
}
diff --git a/src/redis-cli.c b/src/redis-cli.c
index 705d917e9..e2c257a6c 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -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);
@@ -632,7 +718,6 @@ sds sdscatcolor(sds o, char *s, size_t len, char *color) {
int bold = strstr(color,"bold") != NULL;
int ccode = 37; /* Defaults to white. */
if (strstr(color,"red")) ccode = 31;
- else if (strstr(color,"red")) ccode = 31;
else if (strstr(color,"green")) ccode = 32;
else if (strstr(color,"yellow")) ccode = 33;
else if (strstr(color,"blue")) ccode = 34;
@@ -1003,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")) {
@@ -1110,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"
@@ -2365,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);
diff --git a/src/redismodule.h b/src/redismodule.h
index 7fc0fec40..672951f78 100644
--- a/src/redismodule.h
+++ b/src/redismodule.h
@@ -58,6 +58,30 @@
#define REDISMODULE_HASH_CFIELDS (1<<2)
#define REDISMODULE_HASH_EXISTS (1<<3)
+/* Context Flags: Info about the current context returned by RM_GetContextFlags */
+
+/* The command is running in the context of a Lua script */
+#define REDISMODULE_CTX_FLAGS_LUA 0x0001
+/* The command is running inside a Redis transaction */
+#define REDISMODULE_CTX_FLAGS_MULTI 0x0002
+/* The instance is a master */
+#define REDISMODULE_CTX_FLAGS_MASTER 0x0004
+/* The instance is a slave */
+#define REDISMODULE_CTX_FLAGS_SLAVE 0x0008
+/* The instance is read-only (usually meaning it's a slave as well) */
+#define REDISMODULE_CTX_FLAGS_READONLY 0x0010
+/* The instance is running in cluster mode */
+#define REDISMODULE_CTX_FLAGS_CLUSTER 0x0020
+/* The instance has AOF enabled */
+#define REDISMODULE_CTX_FLAGS_AOF 0x0040 //
+/* The instance has RDB enabled */
+#define REDISMODULE_CTX_FLAGS_RDB 0x0080 //
+/* The instance has Maxmemory set */
+#define REDISMODULE_CTX_FLAGS_MAXMEMORY 0x0100
+/* Maxmemory is set and has an eviction policy that may delete keys */
+#define REDISMODULE_CTX_FLAGS_EVICT 0x0200
+
+
/* A special pointer that we can use between the core and the module to signal
* field deletion, and that is impossible to be a valid pointer. */
#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1)
@@ -119,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);
@@ -183,6 +208,7 @@ int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ..
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
+int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
@@ -238,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);
@@ -302,6 +329,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(IsKeysPositionRequest);
REDISMODULE_GET_API(KeyAtPos);
REDISMODULE_GET_API(GetClientId);
+ REDISMODULE_GET_API(GetContextFlags);
REDISMODULE_GET_API(PoolAlloc);
REDISMODULE_GET_API(CreateDataType);
REDISMODULE_GET_API(ModuleTypeSetValue);
@@ -344,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;
}
diff --git a/src/replication.c b/src/replication.c
index 6be5d2631..cf4db3e3a 100644
--- a/src/replication.c
+++ b/src/replication.c
@@ -569,18 +569,19 @@ int startBgsaveForReplication(int mincapa) {
serverLog(LL_NOTICE,"Starting BGSAVE for SYNC with target: %s",
socket_target ? "slaves sockets" : "disk");
- rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
- /* If we are saving for a chained slave (that is, if we are,
- * in turn, a slave of another instance), make sure after
- * loadig the RDB, our slaves select the right DB: we'll just
- * send the replication stream we receive from our master, so
- * no way to send SELECT commands. */
- if (server.master) rsi.repl_stream_db = server.master->db->id;
-
- if (socket_target)
- retval = rdbSaveToSlavesSockets(&rsi);
- else
- retval = rdbSaveBackground(server.rdb_filename,&rsi);
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ /* Only do rdbSave* when rsiptr is not NULL,
+ * otherwise slave will miss repl-stream-db. */
+ if (rsiptr) {
+ if (socket_target)
+ retval = rdbSaveToSlavesSockets(rsiptr);
+ else
+ retval = rdbSaveBackground(server.rdb_filename,rsiptr);
+ } else {
+ serverLog(LL_WARNING,"BGSAVE for replication: replication information not available, can't generate the RDB file right now. Try later.");
+ retval = C_ERR;
+ }
/* If we failed to BGSAVE, remove the slaves waiting for a full
* resynchorinization from the list of salves, inform them with
@@ -1531,6 +1532,11 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* Setup the replication to continue. */
sdsfree(reply);
replicationResurrectCachedMaster(fd);
+
+ /* If this instance was restarted and we read the metadata to
+ * PSYNC from the persistence file, our replication backlog could
+ * be still not initialized. Create it. */
+ if (server.repl_backlog == NULL) createReplicationBacklog();
return PSYNC_CONTINUE;
}
@@ -2607,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 "
diff --git a/src/scripting.c b/src/scripting.c
index 8f8145b2c..d9f954068 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -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)
{
diff --git a/src/sds.c b/src/sds.c
index eafa13c29..ff633c8bc 100644
--- a/src/sds.c
+++ b/src/sds.c
@@ -248,16 +248,23 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
sds sdsRemoveFreeSpace(sds s) {
void *sh, *newsh;
char type, oldtype = s[-1] & SDS_TYPE_MASK;
- int hdrlen;
+ int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);
- sh = (char*)s-sdsHdrSize(oldtype);
+ sh = (char*)s-oldhdrlen;
+ /* Check what would be the minimum SDS header that is just good enough to
+ * fit this string. */
type = sdsReqType(len);
hdrlen = sdsHdrSize(type);
- if (oldtype==type) {
- newsh = s_realloc(sh, hdrlen+len+1);
+
+ /* If the type is the same, or at least a large enough type is still
+ * required, we just realloc(), letting the allocator to do the copy
+ * only if really needed. Otherwise if the change is huge, we manually
+ * reallocate the string to use the different header type. */
+ if (oldtype==type || type > SDS_TYPE_8) {
+ newsh = s_realloc(sh, oldhdrlen+len+1);
if (newsh == NULL) return NULL;
- s = (char*)newsh+hdrlen;
+ s = (char*)newsh+oldhdrlen;
} else {
newsh = s_malloc(hdrlen+len+1);
if (newsh == NULL) return NULL;
diff --git a/src/server.c b/src/server.c
index ef05f055d..6bc8bc66f 100644
--- a/src/server.c
+++ b/src/server.c
@@ -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},
@@ -908,12 +908,15 @@ void databasesCron(void) {
/* Rehash */
if (server.activerehashing) {
for (j = 0; j < dbs_per_call; j++) {
- int work_done = incrementallyRehash(rehash_db % server.dbnum);
- rehash_db++;
+ int work_done = incrementallyRehash(rehash_db);
if (work_done) {
/* If the function did some work, stop here, we'll do
* more at the next cron loop. */
break;
+ } else {
+ /* If this db didn't need rehash, we'll try the next one. */
+ rehash_db++;
+ rehash_db %= server.dbnum;
}
}
}
@@ -1092,7 +1095,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
{
serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, (int)sp->seconds);
- rdbSaveBackground(server.rdb_filename,NULL);
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ rdbSaveBackground(server.rdb_filename,rsiptr);
break;
}
}
@@ -1164,7 +1169,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
server.lastbgsave_status == C_OK))
{
- if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK)
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK)
server.rdb_bgsave_scheduled = 0;
}
@@ -2258,8 +2265,9 @@ void call(client *c, int flags) {
propagate_flags &= ~PROPAGATE_AOF;
/* Call propagate() only if at least one of AOF / replication
- * propagation is needed. */
- if (propagate_flags != PROPAGATE_NONE)
+ * propagation is needed. Note that modules commands handle replication
+ * in an explicit way, so we never replicate them automatically. */
+ if (propagate_flags != PROPAGATE_NONE && !(c->cmd->flags & CMD_MODULE))
propagate(c->cmd,c->db->id,c->argv,c->argc,propagate_flags);
}
@@ -2536,8 +2544,9 @@ int prepareForShutdown(int flags) {
"There is a child rewriting the AOF. Killing it!");
kill(server.aof_child_pid,SIGUSR1);
}
- /* Append only file: fsync() the AOF and exit */
+ /* Append only file: flush buffers and fsync() the AOF at exit */
serverLog(LL_NOTICE,"Calling fsync() on the AOF file.");
+ flushAppendOnlyFile(1);
aof_fsync(server.aof_fd);
}
@@ -2545,7 +2554,9 @@ int prepareForShutdown(int flags) {
if ((server.saveparamslen > 0 && !nosave) || save) {
serverLog(LL_NOTICE,"Saving the final RDB snapshot before exiting.");
/* Snapshotting. Perform a SYNC SAVE and exit */
- if (rdbSave(server.rdb_filename,NULL) != C_OK) {
+ rdbSaveInfo rsi, *rsiptr;
+ rsiptr = rdbPopulateSaveInfo(&rsi);
+ if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
/* Ooops.. error saving! The best we can do is to continue
* operating. Note that if there was a background saving process,
* in the next cron() Redis will be notified that the background
@@ -3525,13 +3536,21 @@ void loadDataFromDisk(void) {
(float)(ustime()-start)/1000000);
/* Restore the replication ID / offset from the RDB file. */
- if (rsi.repl_id_is_set && rsi.repl_offset != -1) {
+ if (server.masterhost &&
+ 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 in a wrong way, see more information
+ * in function rdbPopulateSaveInfo. */
+ rsi.repl_stream_db != -1)
+ {
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
server.master_repl_offset = rsi.repl_offset;
/* If we are a slave, create a cached master from this
* information, in order to allow partial resynchronizations
* with masters. */
- if (server.masterhost) replicationCacheMasterUsingMyself();
+ replicationCacheMasterUsingMyself();
+ selectDb(server.cached_master,rsi.repl_stream_db);
}
} else if (errno != ENOENT) {
serverLog(LL_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
diff --git a/src/setproctitle.c b/src/setproctitle.c
index f44253e16..6563242de 100644
--- a/src/setproctitle.c
+++ b/src/setproctitle.c
@@ -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
diff --git a/src/slowlog.c b/src/slowlog.c
index 805ee1d77..32ec4374c 100644
--- a/src/slowlog.c
+++ b/src/slowlog.c
@@ -72,9 +72,16 @@ slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long dur
(unsigned long)
sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
se->argv[j] = createObject(OBJ_STRING,s);
- } else {
+ } else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
se->argv[j] = argv[j];
- incrRefCount(argv[j]);
+ } else {
+ /* Here we need to dupliacate the string objects composing the
+ * argument vector of the command, because those may otherwise
+ * end shared with string objects stored into keys. Having
+ * shared objects between any part of Redis, and the data
+ * structure holding the data, is a problem: FLUSHALL ASYNC
+ * may release the shared string object and create a race. */
+ se->argv[j] = dupStringObject(argv[j]);
}
}
}
diff --git a/src/t_hash.c b/src/t_hash.c
index 700a6233a..be73932c5 100644
--- a/src/t_hash.c
+++ b/src/t_hash.c
@@ -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;
}
diff --git a/tests/instances.tcl b/tests/instances.tcl
index 2ba67ac19..357b34818 100644
--- a/tests/instances.tcl
+++ b/tests/instances.tcl
@@ -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
}
}
diff --git a/tests/unit/latency-monitor.tcl b/tests/unit/latency-monitor.tcl
index b736cad98..69da13f06 100644
--- a/tests/unit/latency-monitor.tcl
+++ b/tests/unit/latency-monitor.tcl
@@ -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]
+ }
}
diff --git a/tests/unit/type/incr.tcl b/tests/unit/type/incr.tcl
index 2287aaae2..a58710d39 100644
--- a/tests/unit/type/incr.tcl
+++ b/tests/unit/type/incr.tcl
@@ -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*}
}
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 82f76befe..564825ae9 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -696,6 +696,10 @@ start_server {tags {"zset"}} {
}
}
+ test "ZSET commands don't accept the empty strings as valid score" {
+ assert_error "*not*float*" {r zadd myzset "" abc}
+ }
+
proc stressers {encoding} {
if {$encoding == "ziplist"} {
# Little extra to allow proper fuzzing in the sorting stresser