void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0, err; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__redis__err__handler"); /* Try to lookup the Lua function */ lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ addReply(c, shared.noscripterr); return; } if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) { lua_pop(lua,1); /* remove the error handler from the stack. */ /* The error is sent to the client by luaCreateFunction() * itself when it returns REDIS_ERR. */ return; } /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,-1)); } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ 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 */ selectDb(server.lua_client,c->db->id); /* Set a hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ server.lua_caller = c; server.lua_time_start = mstime(); server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ err = lua_pcall(lua,0,1,-2); /* Perform some cleanup that we need to do both on error and success. */ if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } server.lua_caller = NULL; selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ lua_gc(lua,LUA_GCSTEP,1); if (err) { addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */ } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */ lua_pop(lua,1); /* Remove the error handler. */ } /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless * we are sure that the script was already in the context of all the * attached slaves *and* the current AOF file if enabled. * * To do so we use a cache of SHA1s of scripts that we already propagated * as full EVAL, that's called the Replication Script Cache. * * For repliation, everytime a new slave attaches to the master, we need to * flush our cache of scripts that can be replicated as EVALSHA, while * for AOF we need to do so every time we rewrite the AOF file. */ if (evalsha) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) { /* This script is not in our script cache, replicate it as * EVAL, then add it into the script cache, as from now on * slaves and AOF know about it. */ robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); replicationScriptCacheAdd(c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF); } } }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0, err; // 随机数的种子,在产生哈希值的时候会用到 /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; // 检查参数的有效性 /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } // 函数名以 f_ 开头 /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; // 如果没有哈希值,需要计算 lua 脚本的哈希值 if (!evalsha) { // 计算哈希值,会放入到 SHA1 -> lua_script 哈希表中 // c->argv[1]->ptr 是用户指定的 lua 脚本 // sha1hex() 产生的哈希值存在 funcname 中 /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { // 用户自己指定了哈希值 /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } // 将错误处理函数入栈 // lua_getglobal() 会将读取指定的全局变量,且将其入栈 /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__redis__err__handler"); /* Try to lookup the Lua function */ // 在 lua 中查找是否注册了此函数。这一句尝试将 funcname 入栈 lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { // funcname 在 lua 中不存在 // 将 nil 出栈 lua_pop(lua,1); /* remove the nil from the stack */ // 已经确定 funcname 在 lua 中没有定义,需要创建 /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ addReply(c, shared.noscripterr); return; } // 创建 lua 函数 funcname // c->argv[1] 指向用户指定的 lua 脚本 if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) { lua_pop(lua,1); /* remove the error handler from the stack. */ /* The error is sent to the client by luaCreateFunction() * itself when it returns REDIS_ERR. */ return; } // 现在 lua 中已经有 funcname 这个全局变量了,将其读取并入栈, // 准备调用 /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,-1)); } // 设置参数,包括键和值 /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); // 选择数据集,lua_client 有专用的数据集 /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); // 设置超时回调函数,以在 lua 脚本执行过长时间的时候停止脚本的运行 /* Set a hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ server.lua_caller = c; server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { // 当 lua 解释器执行了 100000,luaMaskCountHook() 会被调用 lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } // 现在,我们确定函数已经注册成功了.可以直接调用 lua 脚本 /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ err = lua_pcall(lua,0,1,-2); // 删除超时回调函数 /* Perform some cleanup that we need to do both on error and success. */ if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ // 如果已经超时了,说明 lua 脚本已在超时后背 SCRPIT KILL 终结了 // 恢复监听发送 lua 脚本命令的客户端 if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } // lua_caller 置空 server.lua_caller = NULL; // 执行 lua 脚本用的是 lua 脚本执行专用的数据集。现在恢复原有的数据集 selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // Garbage collection 垃圾回收 lua_gc(lua,LUA_GCSTEP,1); // 处理执行 lua 脚本的错误 if (err) { // 告知客户端 addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */ // 成功了 } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */ lua_pop(lua,1); /* Remove the error handler. */ } // 将 lua 脚本发布到主从复制上,并写入 AOF 文件 /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless * we are sure that the script was already in the context of all the * attached slaves *and* the current AOF file if enabled. * * To do so we use a cache of SHA1s of scripts that we already propagated * as full EVAL, that's called the Replication Script Cache. * * For repliation, everytime a new slave attaches to the master, we need to * flush our cache of scripts that can be replicated as EVALSHA, while * for AOF we need to do so every time we rewrite the AOF file. */ if (evalsha) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) { /* This script is not in our script cache, replicate it as * EVAL, then add it into the script cache, as from now on * slaves and AOF know about it. */ // 从 server.lua_scripts 获取 lua 脚本 // c->argv[1]->ptr 是 SHA1 robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); // 添加到主从复制专用的脚本缓存中 replicationScriptCacheAdd(c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); // 重写命令 // 参数 1 为:EVAL // 参数 2 为:lua_script // 如此一来在执行 AOF 持久化和主从复制的时候,lua 脚本就能得到传播 rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } } }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. * * 在每次执行 EVAL 时重置随机 seed ,从而保证可以生成相同的随机序列。 */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. * * 用两个变量,对命令进行检查 * 确保在调用随机命令之后,再调用写命令将出现错误 */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; /* Get the number of arguments that are keys */ // 获取输入键的数量 if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; // 对键的正确性做一个快速检查 if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ // 组合出函数的名字,例如 f_282297a0228f48cd3fc6a55de6316f31422f5d17 funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ // 如果执行的是 EVAL 命令,那么计算脚本的 SHA1 校验和 sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { // 如果执行的是 EVALSHA 命令,直接使用传入的 SHA1 值 /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } /* Try to lookup the Lua function */ // 按名查找函数 lua_getglobal(lua, funcname); if (lua_isnil(lua,1)) { // 没找到脚本相应的脚本 lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the funciton. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { // 如果执行的是 EVALSHA ,返回脚本未找到错误 addReply(c, shared.noscripterr); return; } // 如果执行的是 EVAL ,那么创建并执行新函数,然后将代码添加到脚本字典中 if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return; /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,1)); } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ // 设置 KEYS 和 ARGV 全局变量到 Lua 环境 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 */ // 为 Lua 所属的(伪)客户端设置数据库 selectDb(server.lua_client,c->db->id); /* Set an hook in order to be able to stop the script execution if it * is running for too much time. * * 设置一个钩子,用于在运行时间过长时停止脚本的运作。 * * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. * * 只在开启了时间限制选项时使用钩子,因为它会拖慢脚本的运行速度。 */ // 调用客户端 server.lua_caller = c; // 脚本开始时间 server.lua_time_start = ustime()/1000; // 是否杀死脚本 server.lua_kill = 0; // 只在开启时间限制时使用钩子 if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whatever this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ // 执行脚本(所属的函数) if (lua_pcall(lua,0,1,0)) { // 以下是脚本执行出错的代码。。。 // 删除钩子 if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ // 脚本执行已超时 if (server.lua_timedout) { // 清除超时 FLAG server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ // 将超时钩子里删除的读事件重新加上 aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } // 清空调用者 server.lua_caller = NULL; // 更新目标数据库 selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); // 弹出函数 lua_pop(lua,1); // 执行完整的废料回首循环(full garbage-collection cycle) lua_gc(lua,LUA_GCCOLLECT,0); // 返回 return; } // 以下是脚本执行成功时执行的代码。。。 // 删除钩子 if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ // 清空超时 FLAG server.lua_timedout = 0; // 清空调用者 server.lua_caller = NULL; // 更新 DB selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // 将 Lua 回复转换成 Redis 回复 luaReplyToRedisReply(c,lua); // 执行 1 步渐进式 GC 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. * * 如果有附属节点,那么使用 EVAL 而不是 EVALSHA 来传播脚本 * 因为目前还没有代码可以检测脚本是否已经传送到附属节点中 * * 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. */ // 如果执行的是 EVALSHA 命令 if (evalsha) { // 取出脚本代码体(body) robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); // 重写客户端命令为 EVAL rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } }
void evalGenericCommand(rliteClient *c, int evalsha) { scriptingInit(); lua_client->context = c->context; char funcname[43]; long long numkeys; int delhook = 0, err; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ rliteSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ lua_random_dirty = 0; lua_write_dirty = 0; /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],c->argvlen[2],&numkeys,NULL) != RLITE_OK) return; if (numkeys > (c->argc - 3)) { c->reply = createErrorObject("Number of keys can't be greater than number of args"); return; } else if (numkeys < 0) { c->reply = createErrorObject("Number of keys can't be negative"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1],c->argvlen[1]); } else { /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]; /* Convert to lowercase. We don't use tolower since the function * managed to always show up in the profiler output consuming * a non trivial amount of time. */ for (j = 0; j < 40; j++) funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ? sha[j]+('a'-'A') : sha[j]; funcname[42] = '\0'; char *body; long bodylen; int retval = getScript(c, funcname + 2, &body, &bodylen); if (retval != RL_OK) { c->reply = createErrorObject(RLITE_NOSCRIPTERR); return; } luaCreateFunction(c, lua, funcname, body, bodylen); rl_free(body); } /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__rlite__err__handler"); /* Try to lookup the Lua function */ lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ c->reply = createErrorObject(RLITE_NOSCRIPTERR); return; } if (luaCreateFunction(c,lua,funcname,c->argv[1], c->argvlen[1]) == RLITE_ERR) { lua_pop(lua,1); /* remove the error handler from the stack. */ /* The error is sent to the client by luaCreateFunction() * itself when it returns RLITE_ERR. */ return; } /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { // TODO: panic return; } } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ luaSetGlobalArray(lua,"KEYS",c->argv+3,c->argvlen+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argvlen+3+numkeys,c->argc-3-numkeys); /* Set a hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ lua_caller = c; lua_time_start = rl_mstime(); lua_kill = 0; if (lua_time_limit > 0) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ err = lua_pcall(lua,0,1,-2); /* Perform some cleanup that we need to do both on error and success. */ if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ if (lua_timedout) { lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ } lua_caller = NULL; /* Call the Lua garbage collector from time to time to avoid a * full cycle performed by Lua, which adds too latency. * * The call is performed every LUA_GC_CYCLE_PERIOD executed commands * (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it * for every command uses too much CPU. */ #define LUA_GC_CYCLE_PERIOD 50 { static long gc_count = 0; gc_count++; if (gc_count == LUA_GC_CYCLE_PERIOD) { lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD); gc_count = 0; } } if (err) { char err[1024]; snprintf(err, 1024, "Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); c->reply = createErrorObject(err); lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */ } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */ lua_pop(lua,1); /* Remove the error handler. */ } }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } /* Try to lookup the Lua function */ lua_getglobal(lua, funcname); if (lua_isnil(lua,1)) { lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the funciton. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { addReply(c, shared.noscripterr); return; } if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return; /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,1)); } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ 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 */ selectDb(server.lua_client,c->db->id); /* Set an hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ server.lua_caller = c; server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whatever this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ if (lua_pcall(lua,0,1,0)) { if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } server.lua_caller = NULL; selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,1); lua_gc(lua,LUA_GCCOLLECT,0); return; } if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ server.lua_timedout = 0; server.lua_caller = NULL; 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); redisAssertWithInfo(c,NULL,script != NULL); rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } }