diff options
-rw-r--r-- | src/cluster.c | 3 | ||||
-rw-r--r-- | src/db.c | 4 | ||||
-rw-r--r-- | src/debug.c | 4 | ||||
-rw-r--r-- | src/dict.c | 2 | ||||
-rw-r--r-- | src/expire.c | 19 | ||||
-rw-r--r-- | src/lzfP.h | 6 | ||||
-rw-r--r-- | src/module.c | 153 | ||||
-rw-r--r-- | src/modules/testmodule.c | 82 | ||||
-rw-r--r-- | src/object.c | 41 | ||||
-rw-r--r-- | src/rdb.c | 77 | ||||
-rw-r--r-- | src/rdb.h | 1 | ||||
-rw-r--r-- | src/redis-benchmark.c | 4 | ||||
-rw-r--r-- | src/redis-check-rdb.c | 9 | ||||
-rw-r--r-- | src/redis-cli.c | 90 | ||||
-rw-r--r-- | src/redismodule.h | 31 | ||||
-rw-r--r-- | src/replication.c | 47 | ||||
-rw-r--r-- | src/scripting.c | 8 | ||||
-rw-r--r-- | src/sds.c | 17 | ||||
-rw-r--r-- | src/server.c | 41 | ||||
-rw-r--r-- | src/setproctitle.c | 6 | ||||
-rw-r--r-- | src/slowlog.c | 11 | ||||
-rw-r--r-- | tests/unit/latency-monitor.tcl | 14 | ||||
-rw-r--r-- | tests/unit/type/incr.tcl | 7 | ||||
-rw-r--r-- | tests/unit/type/zset.tcl | 4 |
24 files changed, 578 insertions, 103 deletions
diff --git a/src/cluster.c b/src/cluster.c index a9fedce0c..2da0f54fc 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++); } @@ -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) { @@ -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; +} @@ -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..84eabf391 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 *--------------------------------------------------------------------------- */ @@ -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" 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) { @@ -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/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 |