diff --git a/src/networking.c b/src/networking.c index a4eee4643f33b926f2ebd3ed81e536c7f54392d3..629267d1cad2d025fe958407569c1a88043a810c 100644 --- a/src/networking.c +++ b/src/networking.c @@ -941,6 +941,9 @@ void clientCommand(redisClient *c) { } } +/* Rewrite the command vector of the client. All the new objects ref count + * is incremented. The old command vector is freed, and the old objects + * ref count is decremented. */ void rewriteClientCommandVector(redisClient *c, int argc, ...) { va_list ap; int j; @@ -967,3 +970,21 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) { redisAssert(c->cmd != NULL); va_end(ap); } + +/* Rewrite a single item in the command vector. + * The new val ref count is incremented, and the old decremented. */ +void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) { + robj *oldval; + + redisAssert(i < c->argc); + oldval = c->argv[i]; + c->argv[i] = newval; + incrRefCount(newval); + decrRefCount(oldval); + + /* If this is the command name make sure to fix c->cmd. */ + if (i == 0) { + c->cmd = lookupCommand(c->argv[0]->ptr); + redisAssert(c->cmd != NULL); + } +} diff --git a/src/object.c b/src/object.c index ce13429af94df159cf51307d721f8da52e152550..c1df4d1d2ad8dc91a904223c384ba5aa8bdf3959 100644 --- a/src/object.c +++ b/src/object.c @@ -192,6 +192,23 @@ void decrRefCount(void *obj) { } } +/* This function set the ref count to zero without freeing the object. + * It is useful in order to pass a new object to functions incrementing + * the ref count of the received object. Example: + * + * functionThatWillIncrementRefCount(resetRefCount(CreateObject(...))); + * + * Otherwise you need to resort to the less elegant pattern: + * + * *obj = createObject(...); + * functionThatWillIncrementRefCount(obj); + * decrRefCount(obj); + */ +robj *resetRefCount(robj *obj) { + obj->refcount = 0; + return obj; +} + int checkType(redisClient *c, robj *o, int type) { if (o->type != type) { addReply(c,shared.wrongtypeerr); diff --git a/src/redis.c b/src/redis.c index 7e9c6fd5ce72b177d146286642eae5fb984cfb19..1d7501f9376edcb462a9f48a4c3a665b2152b212 100644 --- a/src/redis.c +++ b/src/redis.c @@ -383,7 +383,7 @@ unsigned int dictEncObjHash(const void *key) { } } -/* Sets type and diskstore negative caching hash table */ +/* Sets type hash table */ dictType setDictType = { dictEncObjHash, /* hash function */ NULL, /* key dup */ diff --git a/src/redis.h b/src/redis.h index 1d094c1d7bfaf9fef4765be6d173d46008087af7..1a45cc8cd52260d525a8388fa52f36adcf06eb07 100644 --- a/src/redis.h +++ b/src/redis.h @@ -646,8 +646,9 @@ struct redisServer { int cluster_enabled; clusterState cluster; /* Scripting */ - lua_State *lua; - redisClient *lua_client; + lua_State *lua; /* The Lua interpreter. We use just one for all clients */ + redisClient *lua_client; /* The "fake client" to query Redis from Lua */ + dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ long long lua_time_limit; long long lua_time_start; }; @@ -742,6 +743,7 @@ extern struct sharedObjectsStruct shared; extern dictType setDictType; extern dictType zsetDictType; extern dictType clusterNodesDictType; +extern dictType dbDictType; extern double R_Zero, R_PosInf, R_NegInf, R_Nan; dictType hashDictType; @@ -782,6 +784,7 @@ void *dupClientReplyValue(void *o); void getClientsMaxBuffers(unsigned long *longest_output_list, unsigned long *biggest_input_buffer); void rewriteClientCommandVector(redisClient *c, int argc, ...); +void rewriteClientCommandArgument(redisClient *c, int i, robj *newval); #ifdef __GNUC__ void addReplyErrorFormat(redisClient *c, const char *fmt, ...) @@ -821,6 +824,7 @@ void touchWatchedKeysOnFlush(int dbid); /* Redis object implementation */ void decrRefCount(void *o); void incrRefCount(robj *o); +robj *resetRefCount(robj *obj); void freeStringObject(robj *o); void freeListObject(robj *o); void freeSetObject(robj *o); diff --git a/src/scripting.c b/src/scripting.c index 469d78708caf9cef1824632fbb067c856911dd8c..794cba5e39580141512af8f1e05e95a7cce4fb8f 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -251,6 +251,11 @@ void scriptingInit(void) { lua_State *lua = lua_open(); luaL_openlibs(lua); + /* Initialize a dictionary we use to map SHAs to scripts. + * This is useful for replication, as we need to replicate EVALSHA + * as EVAL, so we need to remember the associated script. */ + server.lua_scripts = dictCreate(&dbDictType,NULL); + /* Register the redis commands table and fields */ lua_newtable(lua); @@ -455,6 +460,16 @@ void evalGenericCommand(redisClient *c, int evalsha) { return; } lua_getglobal(lua, funcname); + + /* We also save a SHA1 -> Original script map in a dictionary + * so that we can replicate / write in the AOF all the + * EVALSHA commands as EVAL using the original script. */ + { + int retval = dictAdd(server.lua_scripts, + sdsnewlen(funcname+2,40),c->argv[1]); + redisAssert(retval == DICT_OK); + incrRefCount(c->argv[1]); + } } /* Populate the argv and keys table accordingly to the arguments that @@ -490,6 +505,25 @@ void evalGenericCommand(redisClient *c, int evalsha) { selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ luaReplyToRedisReply(c,lua); lua_gc(lua,LUA_GCSTEP,1); + + /* If we have slaves attached we want to replicate this command as + * EVAL instead of EVALSHA. We do this also in the AOF as currently there + * is no easy way to propagate a command in a different way in the AOF + * and in the replication link. + * + * IMPROVEMENT POSSIBLE: + * 1) Replicate this command as EVALSHA in the AOF. + * 2) Remember what slave already received a given script, and replicate + * the EVALSHA against this slaves when possible. + */ + if (evalsha) { + robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); + + redisAssert(script != NULL); + rewriteClientCommandArgument(c,0, + resetRefCount(createStringObject("EVAL",4))); + rewriteClientCommandArgument(c,1,script); + } } void evalCommand(redisClient *c) {