From 115e3ff39e8cbf2f2e044fbd6c65c2e6602c537f Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 27 Oct 2011 14:49:10 +0200 Subject: [PATCH 1/1] If a Lua script executes for more time than the max time specified in the configuration Redis will log a warning, and will start accepting queries (re-entering the event loop), returning -SLOWSCRIPT error for all the commands but SHUTDOWN that remains callable. --- redis.conf | 13 ++++++++++--- src/redis.c | 9 +++++++++ src/redis.h | 4 +++- src/scripting.c | 14 +++++++++----- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/redis.conf b/redis.conf index 18a5dd03..71d6f70b 100644 --- a/redis.conf +++ b/redis.conf @@ -316,9 +316,16 @@ auto-aof-rewrite-min-size 64mb ################################ LUA SCRIPTING ############################### # Max execution time of a Lua script in milliseconds. -# This prevents that a programming error generating an infinite loop will block -# your server forever. Set it to 0 or a negative value for unlimited execution. -lua-time-limit 60000 +# +# If the maximum execution time is reached Redis will log that a script is +# still in execution after the maxium allowed time and will start to +# reply to queries with an error. +# +# The SHUTDOWN command will be available to shutdown the server without +# violating the database consistency if the script entered an infinite loop. +# +# Set it to 0 or a negative value for unlimited execution without warnings. +lua-time-limit 5000 ################################ REDIS CLUSTER ############################### # diff --git a/src/redis.c b/src/redis.c index f39d25b4..32644e33 100644 --- a/src/redis.c +++ b/src/redis.c @@ -793,6 +793,8 @@ void createSharedObjects(void) { "-NOSCRIPT No matching script. Please use EVAL.\r\n")); shared.loadingerr = createObject(REDIS_STRING,sdsnew( "-LOADING Redis is loading the dataset in memory\r\n")); + shared.slowscripterr = createObject(REDIS_STRING,sdsnew( + "-SLOWSCRIPT Redis is busy running a script. Please wait or stop the server with SHUTDOWN.\r\n")); shared.space = createObject(REDIS_STRING,sdsnew(" ")); shared.colon = createObject(REDIS_STRING,sdsnew(":")); shared.plus = createObject(REDIS_STRING,sdsnew("+")); @@ -871,6 +873,7 @@ void initServerConfig() { server.cluster.configfile = zstrdup("nodes.conf"); server.lua_time_limit = REDIS_LUA_TIME_LIMIT; server.lua_client = NULL; + server.lua_timedout = 0; updateLRUClock(); resetServerSaveParams(); @@ -1183,6 +1186,12 @@ int processCommand(redisClient *c) { return REDIS_OK; } + /* Lua script too slow? */ + if (server.lua_timedout && c->cmd->proc != shutdownCommand) { + addReply(c, shared.slowscripterr); + return REDIS_OK; + } + /* Exec the command */ if (c->flags & REDIS_MULTI && c->cmd->proc != execCommand && c->cmd->proc != discardCommand && diff --git a/src/redis.h b/src/redis.h index d62bd80e..1a9891ec 100644 --- a/src/redis.h +++ b/src/redis.h @@ -345,7 +345,7 @@ struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space, *colon, *nullbulk, *nullmultibulk, *queued, *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, - *outofrangeerr, *noscripterr, *loadingerr, *plus, + *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *plus, *select0, *select1, *select2, *select3, *select4, *select5, *select6, *select7, *select8, *select9, *messagebulk, *pmessagebulk, *subscribebulk, *unsubscribebulk, *mbulk3, @@ -639,6 +639,8 @@ struct redisServer { long long lua_time_start; int lua_random_dirty; /* True if a random command was called during the exection of the current script. */ + int lua_timedout; /* True if we reached the time limit for script + execution. */ }; typedef struct pubsubPattern { diff --git a/src/scripting.c b/src/scripting.c index d1e85e26..0b548873 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -273,13 +273,15 @@ int luaLogCommand(lua_State *lua) { void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { long long elapsed; REDIS_NOTUSED(ar); + REDIS_NOTUSED(lua); elapsed = (ustime()/1000) - server.lua_time_start; - if (elapsed >= server.lua_time_limit) { - redisLog(REDIS_NOTICE,"Lua script aborted for max execution time after %lld milliseconds of running time.",elapsed); - lua_pushstring(lua,"Script aborted for max execution time."); - lua_error(lua); + if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { + redisLog(REDIS_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can shut down the server using the SHUTDOWN command.",elapsed); + server.lua_timedout = 1; } + if (server.lua_timedout) + aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); } void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) { @@ -606,7 +608,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { * 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. */ - if (server.lua_time_limit > 0 && server.masterhost != NULL) { + if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); server.lua_time_start = ustime()/1000; } else { @@ -617,6 +619,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { * already defined, we can call it. We have zero arguments and expect * a single return value. */ if (lua_pcall(lua,0,1,0)) { + server.lua_timedout = 0; 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)); @@ -624,6 +627,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { lua_gc(lua,LUA_GCCOLLECT,0); return; } + server.lua_timedout = 0; selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ luaReplyToRedisReply(c,lua); lua_gc(lua,LUA_GCSTEP,1); -- 2.45.2