diff options
Diffstat (limited to 'src/scripting.c')
-rw-r--r-- | src/scripting.c | 293 |
1 files changed, 252 insertions, 41 deletions
diff --git a/src/scripting.c b/src/scripting.c index cbbf43fb1..7cf21f408 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -42,7 +42,10 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply); char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply); char *redisProtocolToLuaType_Status(lua_State *lua, char *reply); char *redisProtocolToLuaType_Error(lua_State *lua, char *reply); -char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype); +char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype); +char *redisProtocolToLuaType_Null(lua_State *lua, char *reply); +char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf); +char *redisProtocolToLuaType_Double(lua_State *lua, char *reply); int redis_math_random (lua_State *L); int redis_math_randomseed (lua_State *L); void ldbInit(void); @@ -58,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx); #define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ #define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ struct ldbState { - int fd; /* Socket of the debugging client. */ + connection *conn; /* Connection of the debugging client. */ int active; /* Are we debugging EVAL right now? */ int forked; /* Is this a fork()ed debugging session? */ list *logs; /* List of messages to send to the client. */ @@ -132,9 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) { case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break; case '+': p = redisProtocolToLuaType_Status(lua,reply); break; case '-': p = redisProtocolToLuaType_Error(lua,reply); break; - case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break; - case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break; - case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break; + case '*': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break; + case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break; + case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break; + case '_': p = redisProtocolToLuaType_Null(lua,reply); break; + case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break; + case ',': p = redisProtocolToLuaType_Double(lua,reply); break; } return p; } @@ -182,13 +188,13 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) { return p+2; } -char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) { +char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) { char *p = strchr(reply+1,'\r'); long long mbulklen; int j = 0; string2ll(reply+1,p-reply-1,&mbulklen); - if (server.lua_caller->resp == 2 || atype == '*') { + if (server.lua_client->resp == 2 || atype == '*') { p += 2; if (mbulklen == -1) { lua_pushboolean(lua,0); @@ -200,11 +206,15 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) { p = redisProtocolToLuaType(lua,p); lua_settable(lua,-3); } - } else if (server.lua_caller->resp == 3) { + } else if (server.lua_client->resp == 3) { /* Here we handle only Set and Map replies in RESP3 mode, since arrays - * follow the above RESP2 code path. */ + * follow the above RESP2 code path. Note that those are represented + * as a table with the "map" or "set" field populated with the actual + * table representing the set or the map type. */ p += 2; lua_newtable(lua); + lua_pushstring(lua,atype == '%' ? "map" : "set"); + lua_newtable(lua); for (j = 0; j < mbulklen; j++) { p = redisProtocolToLuaType(lua,p); if (atype == '%') { @@ -214,10 +224,44 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) { } lua_settable(lua,-3); } + lua_settable(lua,-3); } return p; } +char *redisProtocolToLuaType_Null(lua_State *lua, char *reply) { + char *p = strchr(reply+1,'\r'); + lua_pushnil(lua); + return p+2; +} + +char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf) { + char *p = strchr(reply+1,'\r'); + lua_pushboolean(lua,tf == 't'); + return p+2; +} + +char *redisProtocolToLuaType_Double(lua_State *lua, char *reply) { + char *p = strchr(reply+1,'\r'); + char buf[MAX_LONG_DOUBLE_CHARS+1]; + size_t len = p-reply-1; + double d; + + if (len <= MAX_LONG_DOUBLE_CHARS) { + memcpy(buf,reply+1,len); + buf[len] = '\0'; + d = strtod(buf,NULL); /* We expect a valid representation. */ + } else { + d = 0; + } + + lua_newtable(lua); + lua_pushstring(lua,"double"); + lua_pushnumber(lua,d); + lua_settable(lua,-3); + return p+2; +} + /* This function is used in order to push an error on the Lua stack in the * format used by redis.pcall to return errors, which is a lua table * with a single "err" field set to the error string. Note that this @@ -292,6 +336,8 @@ void luaSortArray(lua_State *lua) { * Lua reply to Redis reply conversion functions. * ------------------------------------------------------------------------- */ +/* Reply to client 'c' converting the top element in the Lua stack to a + * Redis reply. As a side effect the element is consumed from the stack. */ void luaReplyToRedisReply(client *c, lua_State *lua) { int t = lua_type(lua,-1); @@ -300,7 +346,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1)); break; case LUA_TBOOLEAN: - addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.null[c->resp]); + if (server.lua_client->resp == 2) + addReply(c,lua_toboolean(lua,-1) ? shared.cone : + shared.null[c->resp]); + else + addReplyBool(c,lua_toboolean(lua,-1)); break; case LUA_TNUMBER: addReplyLongLong(c,(long long)lua_tonumber(lua,-1)); @@ -310,6 +360,8 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { * Error are returned as a single element table with 'err' field. * Status replies are returned as single element table with 'ok' * field. */ + + /* Handle error reply. */ lua_pushstring(lua,"err"); lua_gettable(lua,-2); t = lua_type(lua,-1); @@ -321,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { lua_pop(lua,2); return; } + lua_pop(lua,1); /* Discard field name pushed before. */ - lua_pop(lua,1); + /* Handle status reply. */ lua_pushstring(lua,"ok"); lua_gettable(lua,-2); t = lua_type(lua,-1); @@ -331,25 +384,81 @@ void luaReplyToRedisReply(client *c, lua_State *lua) { sdsmapchars(ok,"\r\n"," ",2); addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok)); sdsfree(ok); - lua_pop(lua,1); - } else { + lua_pop(lua,2); + return; + } + lua_pop(lua,1); /* Discard field name pushed before. */ + + /* Handle double reply. */ + lua_pushstring(lua,"double"); + lua_gettable(lua,-2); + t = lua_type(lua,-1); + if (t == LUA_TNUMBER) { + addReplyDouble(c,lua_tonumber(lua,-1)); + lua_pop(lua,2); + return; + } + lua_pop(lua,1); /* Discard field name pushed before. */ + + /* Handle map reply. */ + lua_pushstring(lua,"map"); + lua_gettable(lua,-2); + t = lua_type(lua,-1); + if (t == LUA_TTABLE) { + int maplen = 0; void *replylen = addReplyDeferredLen(c); - int j = 1, mbulklen = 0; - - lua_pop(lua,1); /* Discard the 'ok' field value we popped */ - while(1) { - lua_pushnumber(lua,j++); - lua_gettable(lua,-2); - t = lua_type(lua,-1); - if (t == LUA_TNIL) { - lua_pop(lua,1); - break; - } - luaReplyToRedisReply(c, lua); - mbulklen++; + lua_pushnil(lua); /* Use nil to start iteration. */ + while (lua_next(lua,-2)) { + /* Stack now: table, key, value */ + luaReplyToRedisReply(c, lua); /* Return value. */ + lua_pushvalue(lua,-1); /* Dup key before consuming. */ + luaReplyToRedisReply(c, lua); /* Return key. */ + /* Stack now: table, key. */ + maplen++; } - setDeferredArrayLen(c,replylen,mbulklen); + setDeferredMapLen(c,replylen,maplen); + lua_pop(lua,2); + return; } + lua_pop(lua,1); /* Discard field name pushed before. */ + + /* Handle set reply. */ + lua_pushstring(lua,"set"); + lua_gettable(lua,-2); + t = lua_type(lua,-1); + if (t == LUA_TTABLE) { + int setlen = 0; + void *replylen = addReplyDeferredLen(c); + lua_pushnil(lua); /* Use nil to start iteration. */ + while (lua_next(lua,-2)) { + /* Stack now: table, key, true */ + lua_pop(lua,1); /* Discard the boolean value. */ + lua_pushvalue(lua,-1); /* Dup key before consuming. */ + luaReplyToRedisReply(c, lua); /* Return key. */ + /* Stack now: table, key. */ + setlen++; + } + setDeferredSetLen(c,replylen,setlen); + lua_pop(lua,2); + return; + } + lua_pop(lua,1); /* Discard field name pushed before. */ + + /* Handle the array reply. */ + void *replylen = addReplyDeferredLen(c); + int j = 1, mbulklen = 0; + while(1) { + lua_pushnumber(lua,j++); + lua_gettable(lua,-2); + t = lua_type(lua,-1); + if (t == LUA_TNIL) { + lua_pop(lua,1); + break; + } + luaReplyToRedisReply(c, lua); + mbulklen++; + } + setDeferredArrayLen(c,replylen,mbulklen); break; default: addReplyNull(c); @@ -462,6 +571,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) { c->argc = argc; c->user = server.lua_caller->user; + /* Process module hooks */ + moduleCallCommandFilters(c); + argv = c->argv; + argc = c->argc; + /* Log the command if debugging is active. */ if (ldb.active && ldb.step) { sds cmdlog = sdsnew("<redis>"); @@ -854,6 +968,25 @@ int luaLogCommand(lua_State *lua) { return 0; } +/* redis.setresp() */ +int luaSetResp(lua_State *lua) { + int argc = lua_gettop(lua); + + if (argc != 1) { + lua_pushstring(lua, "redis.setresp() requires one argument."); + return lua_error(lua); + } + + int resp = lua_tonumber(lua,-argc); + if (resp != 2 && resp != 3) { + lua_pushstring(lua, "RESP version must be 2 or 3."); + return lua_error(lua); + } + + server.lua_client->resp = resp; + return 0; +} + /* --------------------------------------------------------------------------- * Lua engine initialization and reset. * ------------------------------------------------------------------------- */ @@ -950,6 +1083,7 @@ void scriptingInit(int setup) { if (setup) { server.lua_client = NULL; server.lua_caller = NULL; + server.lua_cur_script = NULL; server.lua_timedout = 0; ldbInit(); } @@ -981,6 +1115,11 @@ void scriptingInit(int setup) { lua_pushcfunction(lua,luaLogCommand); lua_settable(lua,-3); + /* redis.setresp */ + lua_pushstring(lua,"setresp"); + lua_pushcfunction(lua,luaSetResp); + lua_settable(lua,-3); + lua_pushstring(lua,"LOG_DEBUG"); lua_pushnumber(lua,LL_DEBUG); lua_settable(lua,-3); @@ -1104,7 +1243,7 @@ void scriptingInit(int setup) { * Note: there is no need to create it again when this function is called * by scriptingReset(). */ if (server.lua_client == NULL) { - server.lua_client = createClient(-1); + server.lua_client = createClient(NULL); server.lua_client->flags |= CLIENT_LUA; } @@ -1269,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { /* Set the timeout condition if not already set and the maximum * execution time was reached. */ if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { - serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed); + serverLog(LL_WARNING, + "Lua slow script detected: still in execution after %lld milliseconds. " + "You can try killing the script using the SCRIPT KILL command. " + "Script SHA1 is: %s", + elapsed, server.lua_cur_script); server.lua_timedout = 1; /* Once the script timeouts we reenter the event loop to permit others * to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason @@ -1374,8 +1517,9 @@ void evalGenericCommand(client *c, int evalsha) { luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); - /* Select the right DB in the context of the Lua client */ + /* Set the Lua client database and protocol. */ selectDb(server.lua_client,c->db->id); + server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */ /* Set a hook in order to be able to stop the script execution if it * is running for too much time. @@ -1385,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) { * If we are debugging, we set instead a "line" hook so that the * debugger is call-back at every line executed by the script. */ server.lua_caller = c; + server.lua_cur_script = funcname + 2; server.lua_time_start = mstime(); server.lua_kill = 0; if (server.lua_time_limit > 0 && ldb.active == 0) { @@ -1411,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) { queueClientForReprocessing(server.master); } server.lua_caller = NULL; + server.lua_cur_script = NULL; /* Call the Lua garbage collector from time to time to avoid a * full cycle performed by Lua, which adds too latency. @@ -1588,7 +1734,7 @@ NULL /* Initialize Lua debugger data structures. */ void ldbInit(void) { - ldb.fd = -1; + ldb.conn = NULL; ldb.active = 0; ldb.logs = listCreate(); listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree); @@ -1610,7 +1756,7 @@ void ldbFlushLog(list *log) { void ldbEnable(client *c) { c->flags |= CLIENT_LUA_DEBUG; ldbFlushLog(ldb.logs); - ldb.fd = c->fd; + ldb.conn = c->conn; ldb.step = 1; ldb.bpcount = 0; ldb.luabp = 0; @@ -1665,7 +1811,7 @@ void ldbSendLogs(void) { proto = sdscatlen(proto,"\r\n",2); listDelNode(ldb.logs,ln); } - if (write(ldb.fd,proto,sdslen(proto)) == -1) { + if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) { /* Avoid warning. We don't check the return value of write() * since the next read() will catch the I/O error and will * close the debugging session. */ @@ -1688,7 +1834,7 @@ void ldbSendLogs(void) { int ldbStartSession(client *c) { ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0; if (ldb.forked) { - pid_t cp = fork(); + pid_t cp = redisFork(); if (cp == -1) { addReplyError(c,"Fork() failed: can't run EVAL in debugging mode."); return 0; @@ -1705,7 +1851,6 @@ int ldbStartSession(client *c) { * socket to make sure if the parent crashes a reset is sent * to the clients. */ serverLog(LL_WARNING,"Redis forked for debugging eval"); - closeListeningSockets(0); } else { /* Parent */ listAddNodeTail(ldb.children,(void*)(unsigned long)cp); @@ -1718,8 +1863,8 @@ int ldbStartSession(client *c) { } /* Setup our debugging session. */ - anetBlock(NULL,ldb.fd); - anetSendTimeout(NULL,ldb.fd,5000); + connBlock(ldb.conn); + connSendTimeout(ldb.conn,5000); ldb.active = 1; /* First argument of EVAL is the script itself. We split it into different @@ -1746,7 +1891,7 @@ void ldbEndSession(client *c) { /* If it's a fork()ed session, we just exit. */ if (ldb.forked) { - writeToClient(c->fd, c, 0); + writeToClient(c,0); serverLog(LL_WARNING,"Lua debugging session child exiting"); exitFromChild(0); } else { @@ -1755,8 +1900,8 @@ void ldbEndSession(client *c) { } /* Otherwise let's restore client's state. */ - anetNonBlock(NULL,ldb.fd); - anetSendTimeout(NULL,ldb.fd,0); + connNonBlock(ldb.conn); + connSendTimeout(ldb.conn,0); /* Close the client connectin after sending the final EVAL reply * in order to signal the end of the debugging session. */ @@ -2048,6 +2193,11 @@ char *ldbRedisProtocolToHuman_Int(sds *o, char *reply); char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply); char *ldbRedisProtocolToHuman_Status(sds *o, char *reply); char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply); +char *ldbRedisProtocolToHuman_Set(sds *o, char *reply); +char *ldbRedisProtocolToHuman_Map(sds *o, char *reply); +char *ldbRedisProtocolToHuman_Null(sds *o, char *reply); +char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply); +char *ldbRedisProtocolToHuman_Double(sds *o, char *reply); /* Get Redis protocol from 'reply' and appends it in human readable form to * the passed SDS string 'o'. @@ -2062,6 +2212,11 @@ char *ldbRedisProtocolToHuman(sds *o, char *reply) { case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break; case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break; case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break; + case '~': p = ldbRedisProtocolToHuman_Set(o,reply); break; + case '%': p = ldbRedisProtocolToHuman_Map(o,reply); break; + case '_': p = ldbRedisProtocolToHuman_Null(o,reply); break; + case '#': p = ldbRedisProtocolToHuman_Bool(o,reply); break; + case ',': p = ldbRedisProtocolToHuman_Double(o,reply); break; } return p; } @@ -2116,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) { return p; } +char *ldbRedisProtocolToHuman_Set(sds *o, char *reply) { + char *p = strchr(reply+1,'\r'); + long long mbulklen; + int j = 0; + + string2ll(reply+1,p-reply-1,&mbulklen); + p += 2; + *o = sdscatlen(*o,"~(",2); + for (j = 0; j < mbulklen; j++) { + p = ldbRedisProtocolToHuman(o,p); + if (j != mbulklen-1) *o = sdscatlen(*o,",",1); + } + *o = sdscatlen(*o,")",1); + return p; +} + +char *ldbRedisProtocolToHuman_Map(sds *o, char *reply) { + char *p = strchr(reply+1,'\r'); + long long mbulklen; + int j = 0; + + string2ll(reply+1,p-reply-1,&mbulklen); + p += 2; + *o = sdscatlen(*o,"{",1); + for (j = 0; j < mbulklen; j++) { + p = ldbRedisProtocolToHuman(o,p); + *o = sdscatlen(*o," => ",4); + p = ldbRedisProtocolToHuman(o,p); + if (j != mbulklen-1) *o = sdscatlen(*o,",",1); + } + *o = sdscatlen(*o,"}",1); + return p; +} + +char *ldbRedisProtocolToHuman_Null(sds *o, char *reply) { + char *p = strchr(reply+1,'\r'); + *o = sdscatlen(*o,"(null)",6); + return p+2; +} + +char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply) { + char *p = strchr(reply+1,'\r'); + if (reply[1] == 't') + *o = sdscatlen(*o,"#true",5); + else + *o = sdscatlen(*o,"#false",6); + return p+2; +} + +char *ldbRedisProtocolToHuman_Double(sds *o, char *reply) { + char *p = strchr(reply+1,'\r'); + *o = sdscatlen(*o,"(double) ",9); + *o = sdscatlen(*o,reply+1,p-reply-1); + return p+2; +} + /* Log a Redis reply as debugger output, in an human readable format. * If the resulting string is longer than 'len' plus a few more chars * used as prefix, it gets truncated. */ @@ -2327,7 +2538,7 @@ int ldbRepl(lua_State *lua) { while(1) { while((argv = ldbReplParseCommand(&argc)) == NULL) { char buf[1024]; - int nread = read(ldb.fd,buf,sizeof(buf)); + int nread = connRead(ldb.conn,buf,sizeof(buf)); if (nread <= 0) { /* Make sure the script runs without user input since the * client is no longer connected. */ |