示例#1
0
void luaReplyToRedisReply(redisClient *c, lua_State *lua) {
    int t = lua_type(lua,-1);

    switch(t) {
    case LUA_TSTRING:
        addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
        break;
    case LUA_TBOOLEAN:
        addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
        break;
    case LUA_TNUMBER:
        addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
        break;
    case LUA_TTABLE:
        /* We need to check if it is an array, an error, or a status reply.
         * Error are returned as a single element table with 'err' field.
         * Status replies are returned as single element table with 'ok' field */
        lua_pushstring(lua,"err");
        lua_gettable(lua,-2);
        t = lua_type(lua,-1);
        if (t == LUA_TSTRING) {
            sds err = sdsnew(lua_tostring(lua,-1));
            sdsmapchars(err,"\r\n","  ",2);
            addReplySds(c,sdscatprintf(sdsempty(),"-%s\r\n",err));
            sdsfree(err);
            lua_pop(lua,2);
            return;
        }

        lua_pop(lua,1);
        lua_pushstring(lua,"ok");
        lua_gettable(lua,-2);
        t = lua_type(lua,-1);
        if (t == LUA_TSTRING) {
            sds ok = sdsnew(lua_tostring(lua,-1));
            sdsmapchars(ok,"\r\n","  ",2);
            addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
            sdsfree(ok);
            lua_pop(lua,1);
        } else {
            void *replylen = addDeferredMultiBulkLength(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++;
            }
            setDeferredMultiBulkLength(c,replylen,mbulklen);
        }
        break;
    default:
        addReply(c,shared.nullbulk);
    }
    lua_pop(lua,1);
}
示例#2
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;
    }

    /* 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);
        }
    }
}
示例#3
0
// LUA_COMMAND LUA_COMMAND LUA_COMMAND LUA_COMMAND LUA_COMMAND LUA_COMMAND
void luafuncCommand(redisClient *c) { //printf("luafuncCommand\n");
    if (luafunc_call(c, c->argc, c->argv)) return;
    luaReplyToRedisReply(c, server.lua);
    CLEAR_LUA_STACK
}
示例#4
0
    static int luaReplyToRedisReply(lua_State *lua, RedisReply& reply)
    {
        int t = lua_type(lua, -1);
        switch (t)
        {
            case LUA_TSTRING:
            {
                reply.type = REDIS_REPLY_STRING;
                reply.str.append((char*) lua_tostring(lua, -1), lua_strlen(lua, -1));
                break;
            }
            case LUA_TBOOLEAN:
                if (lua_toboolean(lua, -1))
                {
                    reply.type = REDIS_REPLY_INTEGER;
                    reply.integer = 1;
                }
                else
                {
                    reply.type = REDIS_REPLY_NIL;
                }
                break;
            case LUA_TNUMBER:
                reply.type = REDIS_REPLY_INTEGER;
                reply.integer = (long long) lua_tonumber(lua, -1);
                break;
            case LUA_TTABLE:
                /* We need to check if it is an array, an error, or a status reply.
                 * Error are returned as a single element table with 'err' field.
                 * Status replies are returned as single element table with 'ok' field */
                lua_pushstring(lua, "err");
                lua_gettable(lua, -2);
                t = lua_type(lua, -1);
                if (t == LUA_TSTRING)
                {
                    std::string err = lua_tostring(lua, -1);
                    string_replace(err, "\r\n", " ");
                    reply.type = REDIS_REPLY_ERROR;
                    reply.str = err;
                    lua_pop(lua, 2);
                    return 0;
                }

                lua_pop(lua, 1);
                lua_pushstring(lua, "ok");
                lua_gettable(lua, -2);
                t = lua_type(lua, -1);
                if (t == LUA_TSTRING)
                {
                    std::string ok = lua_tostring(lua, -1);
                    string_replace(ok, "\r\n", " ");
                    reply.str = ok;
                    reply.type = REDIS_REPLY_STATUS;
                    lua_pop(lua, 1);
                }
                else
                {
                    //void *replylen = addDeferredMultiBulkLength(c);
                    int j = 1, mbulklen = 0;

                    lua_pop(lua, 1);
                    /* Discard the 'ok' field value we popped */
                    reply.type = REDIS_REPLY_ARRAY;
                    while (1)
                    {
                        lua_pushnumber(lua, j++);
                        lua_gettable(lua, -2);
                        t = lua_type(lua, -1);
                        if (t == LUA_TNIL)
                        {
                            lua_pop(lua, 1);
                            break;
                        }
                        RedisReply& r = reply.AddMember();
                        luaReplyToRedisReply(lua, r);
                        mbulklen++;
                    }
                }
                break;
            default:
            {
                reply.type = REDIS_REPLY_NIL;
                break;
            }

        }
        lua_pop(lua, 1);
        return 0;
    }
示例#5
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);
        }
    }
}
示例#6
0
文件: scripting.c 项目: jqk6/rlite
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. */
	}
}
示例#7
0
文件: scripting.c 项目: jqk6/rlite
void luaReplyToRedisReply(rliteClient *c, lua_State *lua) {
	int t = lua_type(lua,-1);

	switch(t) {
	case LUA_TSTRING:
		c->reply = createStringObject((char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
		break;
	case LUA_TBOOLEAN:
		if (lua_toboolean(lua,-1)) {
			c->reply = createLongLongObject(1);
		} else {
			c->reply = createNullReplyObject();
		}
		break;
	case LUA_TNUMBER:
		c->reply = createLongLongObject((long long)lua_tonumber(lua,-1));
		break;
	case LUA_TTABLE:
		/* We need to check if it is an array, an error, or a status reply.
		 * Error are returned as a single element table with 'err' field.
		 * Status replies are returned as single element table with 'ok' field */
		lua_pushstring(lua,"err");
		lua_gettable(lua,-2);
		t = lua_type(lua,-1);
		if (t == LUA_TSTRING) {
			c->reply = luaReplyToStringReply(RLITE_REPLY_ERROR);
			lua_pop(lua,2);
			return;
		}

		lua_pop(lua,1);
		lua_pushstring(lua,"ok");
		lua_gettable(lua,-2);
		t = lua_type(lua,-1);
		if (t == LUA_TSTRING) {
			c->reply = luaReplyToStringReply(RLITE_REPLY_STATUS);
			lua_pop(lua,1);
		} else {
			rliteReply *reply = createArrayObject(2);
			void *tmp;
			size_t j = 1;

			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);
				if (j - 2 == reply->elements) {
					tmp = realloc(reply->element, sizeof(rliteReply *) * reply->elements * 2);
					if (!tmp) {
						// TODO: free stuff, panic
						c->reply = NULL;
						return;
					}
					reply->element = tmp;
				}
				reply->element[j - 2] = c->reply;
			}
			// TODO: shrink reply->element
			reply->elements = j - 2;
			c->reply = reply;
		}
		break;
	default:
		c->reply = createNullReplyObject();
	}
	lua_pop(lua,1);
}
示例#8
0
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);
    }
}
示例#9
0
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);
    }
}