Commit 4ab8695d authored by antirez's avatar antirez
Browse files

New script timeout semantics and SCRIPT KILL implemented. SHUTDOWN NOSAVE and...

New script timeout semantics and SCRIPT KILL implemented. SHUTDOWN NOSAVE and SHUTDOWN SAVE implemented.
parent 5c85257b
...@@ -328,8 +328,22 @@ void typeCommand(redisClient *c) { ...@@ -328,8 +328,22 @@ void typeCommand(redisClient *c) {
} }
void shutdownCommand(redisClient *c) { void shutdownCommand(redisClient *c) {
if (prepareForShutdown() == REDIS_OK) int flags = 0;
exit(0);
if (c->argc > 2) {
addReply(c,shared.syntaxerr);
return;
} else if (c->argc == 2) {
if (!strcasecmp(c->argv[1]->ptr,"nosave")) {
flags |= REDIS_SHUTDOWN_NOSAVE;
} else if (!strcasecmp(c->argv[1]->ptr,"save")) {
flags |= REDIS_SHUTDOWN_SAVE;
} else {
addReply(c,shared.syntaxerr);
return;
}
}
if (prepareForShutdown(flags) == REDIS_OK) exit(0);
addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
} }
......
...@@ -187,7 +187,7 @@ struct redisCommand redisCommandTable[] = { ...@@ -187,7 +187,7 @@ struct redisCommand redisCommandTable[] = {
{"save",saveCommand,1,"ar",0,NULL,0,0,0,0,0}, {"save",saveCommand,1,"ar",0,NULL,0,0,0,0,0},
{"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0}, {"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
{"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0}, {"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,1,"ar",0,NULL,0,0,0,0,0}, {"shutdown",shutdownCommand,-1,"ar",0,NULL,0,0,0,0,0},
{"lastsave",lastsaveCommand,1,"r",0,NULL,0,0,0,0,0}, {"lastsave",lastsaveCommand,1,"r",0,NULL,0,0,0,0,0},
{"type",typeCommand,2,"r",0,NULL,1,1,1,0,0}, {"type",typeCommand,2,"r",0,NULL,1,1,1,0,0},
{"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0}, {"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0},
...@@ -628,7 +628,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -628,7 +628,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* We received a SIGTERM, shutting down here in a safe way, as it is /* We received a SIGTERM, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */ * not ok doing so inside the signal handler. */
if (server.shutdown_asap) { if (server.shutdown_asap) {
if (prepareForShutdown() == REDIS_OK) exit(0); if (prepareForShutdown(0) == REDIS_OK) exit(0);
redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information"); redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information");
} }
...@@ -805,7 +805,7 @@ void createSharedObjects(void) { ...@@ -805,7 +805,7 @@ void createSharedObjects(void) {
shared.loadingerr = createObject(REDIS_STRING,sdsnew( shared.loadingerr = createObject(REDIS_STRING,sdsnew(
"-LOADING Redis is loading the dataset in memory\r\n")); "-LOADING Redis is loading the dataset in memory\r\n"));
shared.slowscripterr = createObject(REDIS_STRING,sdsnew( shared.slowscripterr = createObject(REDIS_STRING,sdsnew(
"-BUSY Redis is busy running a script. Please wait or stop the server with SHUTDOWN.\r\n")); "-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));
shared.space = createObject(REDIS_STRING,sdsnew(" ")); shared.space = createObject(REDIS_STRING,sdsnew(" "));
shared.colon = createObject(REDIS_STRING,sdsnew(":")); shared.colon = createObject(REDIS_STRING,sdsnew(":"));
shared.plus = createObject(REDIS_STRING,sdsnew("+")); shared.plus = createObject(REDIS_STRING,sdsnew("+"));
...@@ -884,6 +884,7 @@ void initServerConfig() { ...@@ -884,6 +884,7 @@ void initServerConfig() {
server.repl_timeout = REDIS_REPL_TIMEOUT; server.repl_timeout = REDIS_REPL_TIMEOUT;
server.cluster_enabled = 0; server.cluster_enabled = 0;
server.cluster.configfile = zstrdup("nodes.conf"); server.cluster.configfile = zstrdup("nodes.conf");
server.lua_caller = NULL;
server.lua_time_limit = REDIS_LUA_TIME_LIMIT; server.lua_time_limit = REDIS_LUA_TIME_LIMIT;
server.lua_client = NULL; server.lua_client = NULL;
server.lua_timedout = 0; server.lua_timedout = 0;
...@@ -1232,8 +1233,15 @@ int processCommand(redisClient *c) { ...@@ -1232,8 +1233,15 @@ int processCommand(redisClient *c) {
return REDIS_OK; return REDIS_OK;
} }
/* Lua script too slow? */ /* Lua script too slow? Only allow SHUTDOWN NOSAVE and SCRIPT KILL. */
if (server.lua_timedout && c->cmd->proc != shutdownCommand) { if (server.lua_timedout &&
!(c->cmd->proc != shutdownCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
!(c->cmd->proc == scriptCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'k'))
{
addReply(c, shared.slowscripterr); addReply(c, shared.slowscripterr);
return REDIS_OK; return REDIS_OK;
} }
...@@ -1253,7 +1261,10 @@ int processCommand(redisClient *c) { ...@@ -1253,7 +1261,10 @@ int processCommand(redisClient *c) {
/*================================== Shutdown =============================== */ /*================================== Shutdown =============================== */
int prepareForShutdown() { int prepareForShutdown(int flags) {
int save = flags & REDIS_SHUTDOWN_SAVE;
int nosave = flags & REDIS_SHUTDOWN_NOSAVE;
redisLog(REDIS_WARNING,"User requested shutdown..."); redisLog(REDIS_WARNING,"User requested shutdown...");
/* Kill the saving child if there is a background saving in progress. /* Kill the saving child if there is a background saving in progress.
We want to avoid race conditions, for instance our saving child may We want to avoid race conditions, for instance our saving child may
...@@ -1275,7 +1286,7 @@ int prepareForShutdown() { ...@@ -1275,7 +1286,7 @@ int prepareForShutdown() {
redisLog(REDIS_NOTICE,"Calling fsync() on the AOF file."); redisLog(REDIS_NOTICE,"Calling fsync() on the AOF file.");
aof_fsync(server.appendfd); aof_fsync(server.appendfd);
} }
if (server.saveparamslen > 0) { if ((server.saveparamslen > 0 && !nosave) || save) {
redisLog(REDIS_NOTICE,"Saving the final RDB snapshot before exiting."); redisLog(REDIS_NOTICE,"Saving the final RDB snapshot before exiting.");
/* Snapshotting. Perform a SYNC SAVE and exit */ /* Snapshotting. Perform a SYNC SAVE and exit */
if (rdbSave(server.dbfilename) != REDIS_OK) { if (rdbSave(server.dbfilename) != REDIS_OK) {
......
...@@ -215,6 +215,11 @@ ...@@ -215,6 +215,11 @@
#define UNIT_SECONDS 0 #define UNIT_SECONDS 0
#define UNIT_MILLISECONDS 1 #define UNIT_MILLISECONDS 1
/* SHUTDOWN flags */
#define REDIS_SHUTDOWN_SAVE 1 /* Force SAVE on SHUTDOWN even if no save
points are configured. */
#define REDIS_SHUTDOWN_NOSAVE 2 /* Don't SAVE on SHUTDOWN. */
/* We can print the stacktrace, so our assert is defined this way: */ /* We can print the stacktrace, so our assert is defined this way: */
#define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1))) #define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) #define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
...@@ -538,7 +543,7 @@ struct redisServer { ...@@ -538,7 +543,7 @@ struct redisServer {
off_t auto_aofrewrite_base_size;/* AOF size on latest startup or rewrite. */ off_t auto_aofrewrite_base_size;/* AOF size on latest startup or rewrite. */
off_t appendonly_current_size; /* AOF current size. */ off_t appendonly_current_size; /* AOF current size. */
int aofrewrite_scheduled; /* Rewrite once BGSAVE terminates. */ int aofrewrite_scheduled; /* Rewrite once BGSAVE terminates. */
int shutdown_asap; int shutdown_asap; /* SHUTDOWN needed */
int activerehashing; int activerehashing;
char *requirepass; char *requirepass;
/* Persistence */ /* Persistence */
...@@ -615,13 +620,17 @@ struct redisServer { ...@@ -615,13 +620,17 @@ struct redisServer {
/* Scripting */ /* Scripting */
lua_State *lua; /* The Lua interpreter. We use just one for all clients */ lua_State *lua; /* The Lua interpreter. We use just one for all clients */
redisClient *lua_client; /* The "fake client" to query Redis from Lua */ redisClient *lua_client; /* The "fake client" to query Redis from Lua */
redisClient *lua_caller; /* The client running EVAL right now, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
long long lua_time_limit; long long lua_time_limit;
long long lua_time_start; long long lua_time_start;
int lua_write_dirty; /* True if a write command was called during the
execution of the current script. */
int lua_random_dirty; /* True if a random command was called during the int lua_random_dirty; /* True if a random command was called during the
exection of the current script. */ execution of the current script. */
int lua_timedout; /* True if we reached the time limit for script int lua_timedout; /* True if we reached the time limit for script
execution. */ execution. */
int lua_kill; /* Kill the script if true. */
}; };
typedef struct pubsubPattern { typedef struct pubsubPattern {
......
...@@ -187,6 +187,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) { ...@@ -187,6 +187,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
} }
if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1; if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1;
/* Run the command */ /* Run the command */
cmd->proc(c); cmd->proc(c);
...@@ -277,11 +278,22 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { ...@@ -277,11 +278,22 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
elapsed = (ustime()/1000) - server.lua_time_start; elapsed = (ustime()/1000) - server.lua_time_start;
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { 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); redisLog(REDIS_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
server.lua_timedout = 1; server.lua_timedout = 1;
/* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
* we need to mask the client executing the script from the event loop.
* If we don't do that the client may disconnect and could no longer be
* here when the EVAL command will return. */
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
} }
if (server.lua_timedout) if (server.lua_timedout)
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
if (server.lua_kill) {
redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL.");
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
lua_error(lua);
}
} }
void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) { void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) {
...@@ -553,6 +565,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { ...@@ -553,6 +565,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
* Thanks to this flag we'll raise an error every time a write command * Thanks to this flag we'll raise an error every time a write command
* is called after a random command was used. */ * is called after a random command was used. */
server.lua_random_dirty = 0; server.lua_random_dirty = 0;
server.lua_write_dirty = 0;
/* Get the number of arguments that are keys */ /* Get the number of arguments that are keys */
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK)
...@@ -610,7 +623,6 @@ void evalGenericCommand(redisClient *c, int evalsha) { ...@@ -610,7 +623,6 @@ void evalGenericCommand(redisClient *c, int evalsha) {
* make the Lua script execution slower. */ * 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); lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
server.lua_time_start = ustime()/1000;
} else { } else {
lua_sethook(lua,luaMaskCountHook,0,0); lua_sethook(lua,luaMaskCountHook,0,0);
} }
...@@ -618,8 +630,18 @@ void evalGenericCommand(redisClient *c, int evalsha) { ...@@ -618,8 +630,18 @@ void evalGenericCommand(redisClient *c, int evalsha) {
/* At this point whatever this script was never seen before or if it was /* 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 * already defined, we can call it. We have zero arguments and expect
* a single return value. */ * a single return value. */
server.lua_caller = c;
server.lua_time_start = ustime()/1000;
server.lua_kill = 0;
if (lua_pcall(lua,0,1,0)) { if (lua_pcall(lua,0,1,0)) {
server.lua_timedout = 0; 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 */ selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */
addReplyErrorFormat(c,"Error running script (call to %s): %s\n", addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
funcname, lua_tostring(lua,-1)); funcname, lua_tostring(lua,-1));
...@@ -628,6 +650,7 @@ void evalGenericCommand(redisClient *c, int evalsha) { ...@@ -628,6 +650,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
return; return;
} }
server.lua_timedout = 0; server.lua_timedout = 0;
server.lua_caller = NULL;
selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */
luaReplyToRedisReply(c,lua); luaReplyToRedisReply(c,lua);
lua_gc(lua,LUA_GCSTEP,1); lua_gc(lua,LUA_GCSTEP,1);
...@@ -743,6 +766,15 @@ void scriptCommand(redisClient *c) { ...@@ -743,6 +766,15 @@ void scriptCommand(redisClient *c) {
} }
addReplyBulkCBuffer(c,funcname+2,40); addReplyBulkCBuffer(c,funcname+2,40);
sdsfree(sha); sdsfree(sha);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
if (server.lua_caller == NULL) {
addReplyError(c,"No scripts in execution right now.");
} else if (server.lua_write_dirty) {
addReplyError(c, "Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.");
} else {
server.lua_kill = 1;
addReply(c,shared.ok);
}
} else { } else {
addReplyError(c, "Unknown SCRIPT subcommand or wrong # of args."); addReplyError(c, "Unknown SCRIPT subcommand or wrong # of args.");
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment