/* In Redis commands are always executed in the context of a client, so in * order to load the append only file we need to create a fake client. */ struct redisClient *createFakeClient(void) { struct redisClient *c = zmalloc(sizeof(*c)); selectDb(c,0); c->fd = -1; c->querybuf = sdsempty(); c->querybuf_peak = 0; c->argc = 0; c->argv = NULL; c->bufpos = 0; c->flags = 0; /* We set the fake client as a slave waiting for the synchronization * so that Redis will not try to send replies to this client. */ c->replstate = REDIS_REPL_WAIT_BGSAVE_START; c->reply = listCreate(); c->reply_bytes = 0; c->obuf_soft_limit_reached_time = 0; c->watched_keys = listCreate(); listSetFreeMethod(c->reply,decrRefCount); listSetDupMethod(c->reply,dupClientReplyValue); initClientMultiState(c); return c; }
void RedisConnectionOverSsh::runCommand(const Command &command) { if (command.hasDbIndex()) { selectDb(command.getDbIndex()); } resp.clear(); commandRunning = true; runningCommand = command; executionTimer->start(config.executeTimeout); if (command.isEmpty()) { return sendResponse(); } /* * Send command */ QByteArray byteArray = command.getByteRepresentation(); const char* cString = byteArray.constData(); socket->write(cString, byteArray.size()); }
bool DbUtil::connect() { if (!(m_mysql = mysql_init(NULL))) { myerror("DB connect-> mysql_init() failed"); return false; } /* Load connection parameters file and group */ if (mysql_options(m_mysql, MYSQL_READ_DEFAULT_FILE, m_default_file.c_str()) || mysql_options(m_mysql, MYSQL_READ_DEFAULT_GROUP, m_default_group.c_str())) { myerror("DB Connect -> mysql_options failed"); disconnect(); return false; } /* Connect, read settings from my.cnf NOTE! user and password can be stored there as well */ if (mysql_real_connect(m_mysql, NULL, m_user.c_str(), m_pass.c_str(), m_dbname.c_str(), 0, NULL, 0) == NULL) { myerror("connection failed"); disconnect(); return false; } selectDb(); m_connected= true; assert(m_mysql); return true; }
void RedisConnection::runCommand(const Command &command) { if (command.hasDbIndex()) { selectDb(command.getDbIndex()); } resp.clear(); commandRunning = true; runningCommand = command; executionTimer->start(config.executeTimeout); if (command.isEmpty()) { return sendResponse(); } QString formattedCommand = command.getFormattedString(); /* * Send command */ QTextStream out(socket); out << formattedCommand; out.flush(); }
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); } } }
static int cliSendCommand(int argc, char **argv, int repeat) { struct redisCommand *rc = lookupCommand(argv[0]); int fd, j, retval = 0; sds cmd; if (!rc) { fprintf(stderr,"Unknown command '%s'\n",argv[0]); return 1; } config.raw_output = (rc->flags & CMDFLAG_RAWOUTPUT); if ((rc->arity > 0 && argc != rc->arity) || (rc->arity < 0 && argc < -rc->arity)) { fprintf(stderr,"Wrong number of arguments for '%s'\n",rc->name); return 1; } if (!strcasecmp(rc->name,"shutdown")) config.shutdown = 1; if (!strcasecmp(rc->name,"monitor")) config.monitor_mode = 1; if (!strcasecmp(rc->name,"subscribe") || !strcasecmp(rc->name,"psubscribe")) config.pubsub_mode = 1; if ((fd = cliConnect()) == -1) return 1; /* Select db number */ retval = selectDb(fd); if (retval) { fprintf(stderr,"Error setting DB num\n"); return 1; } while(repeat--) { /* Build the command to send */ cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc); for (j = 0; j < argc; j++) { cmd = sdscatprintf(cmd,"$%lu\r\n", (unsigned long)sdslen(argv[j])); cmd = sdscatlen(cmd,argv[j],sdslen(argv[j])); cmd = sdscatlen(cmd,"\r\n",2); } anetWrite(fd,cmd,sdslen(cmd)); sdsfree(cmd); while (config.monitor_mode) { cliReadSingleLineReply(fd,0); } if (config.pubsub_mode) { printf("Reading messages... (press Ctrl-c to quit)\n"); while (1) { cliReadReply(fd); printf("\n"); } } retval = cliReadReply(fd); if (retval) { return retval; } } return 0; }
static int cliSendCommand(int argc, char **argv) { struct redisCommand *rc = lookupCommand(argv[0]); int fd, j, retval = 0; int read_forever = 0; sds cmd; if (!rc) { fprintf(stderr,"Unknown command '%s'\n",argv[0]); return 1; } if ((rc->arity > 0 && argc != rc->arity) || (rc->arity < 0 && argc < -rc->arity)) { fprintf(stderr,"Wrong number of arguments for '%s'\n",rc->name); return 1; } if (!strcasecmp(rc->name,"monitor")) read_forever = 1; if ((fd = cliConnect()) == -1) return 1; /* Select db number */ retval = selectDb(fd); if (retval) { fprintf(stderr,"Error setting DB num\n"); return 1; } while(config.repeat--) { /* Build the command to send */ cmd = sdsempty(); if (rc->flags & REDIS_CMD_MULTIBULK) { cmd = sdscatprintf(cmd,"*%d\r\n",argc); for (j = 0; j < argc; j++) { cmd = sdscatprintf(cmd,"$%lu\r\n", (unsigned long)sdslen(argv[j])); cmd = sdscatlen(cmd,argv[j],sdslen(argv[j])); cmd = sdscatlen(cmd,"\r\n",2); } } else { for (j = 0; j < argc; j++) { if (j != 0) cmd = sdscat(cmd," "); if (j == argc-1 && rc->flags & REDIS_CMD_BULK) { cmd = sdscatprintf(cmd,"%lu", (unsigned long)sdslen(argv[j])); } else { cmd = sdscatlen(cmd,argv[j],sdslen(argv[j])); } } cmd = sdscat(cmd,"\r\n"); if (rc->flags & REDIS_CMD_BULK) { cmd = sdscatlen(cmd,argv[argc-1],sdslen(argv[argc-1])); cmd = sdscatlen(cmd,"\r\n",2); } } anetWrite(fd,cmd,sdslen(cmd)); sdsfree(cmd); while (read_forever) { cliReadSingleLineReply(fd,0); } retval = cliReadReply(fd); if (retval) { return retval; } } return 0; }
int triggle_event(struct redisClient *c,sds funcname) { redisSrand48(0); server.lua_random_dirty = 0; server.lua_write_dirty = 0; //redisLog(REDIS_NOTICE,"step into stack: %d",lua_gettop(server.lua)); lua_getglobal(server.lua,funcname); if (lua_isnil(server.lua,1)) { lua_pop(server.lua,1); /* remove the nil from the stack */ redisLog(REDIS_NOTICE,"no funcname triggle_scipts in lua"); struct dictEntry *de = dictFind(server.bridge_db.triggle_scipts[c->db->id],funcname); if(de) { struct bridge_db_triggle_t * tmptrg=dictGetVal(de); if (luatriggleCreateFunction(server.lua,funcname,tmptrg->lua_scripts) == REDIS_ERR) return -1; /* Now the following is guaranteed to return non nil */ lua_getglobal(server.lua, funcname); redisAssert(!lua_isnil(server.lua,1)); } else { redisLog(REDIS_WARNING,"triggle not found"); return -1; } } luaTriggleSetGlobalArray(server.lua,"KEYS",c->argv,c->argc); // redisLog(REDIS_NOTICE,"stack: %d",lua_gettop(server.lua)); /* for(int i=0;i<c->argc;i++){ redisLog(REDIS_NOTICE,"%s",c->argv[i]->ptr); } */ /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0) { lua_sethook(server.lua,luaMaskCountHook,LUA_MASKCOUNT,100000); server.lua_time_start = ustime()/1000; } else { lua_sethook(server.lua,luaMaskCountHook,0,0); } /* 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(server.lua,0,1,0)) { selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ redisLog(REDIS_WARNING,"Error running script (call to %s): %s\n", (char*)funcname, lua_tostring(server.lua,-1)); lua_pop(server.lua,1); lua_gc(server.lua,LUA_GCCOLLECT,0); return -1; } selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // luaReplyToRedisReply(c,server.lua); lua_pop(server.lua,1); server.lua_timedout = 0; server.lua_caller = NULL; lua_gc(server.lua,LUA_GCSTEP,1); // redisLog(REDIS_NOTICE,"after stack: %d",lua_gettop(server.lua)); //for slaves // return 0; }
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); } } }
//TODO this can be a static redisClient (but make sure call is not nested) redisClient *rsql_createFakeClient() { int curr_db_id = server.dbid; redisClient *fc = createFakeClient(); selectDb(fc, curr_db_id); return fc; }
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(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); } }