Redis Functions - Move Lua related variable into luaCtx struct

The following variable was renamed:
1. lua_caller 			-> script_caller
2. lua_time_limit 		-> script_time_limit
3. lua_timedout 		-> script_timedout
4. lua_oom 			-> script_oom
5. lua_disable_deny_script 	-> script_disable_deny_script
6. in_eval			-> in_script

The following variables was moved to lctx under eval.c
1.  lua
2.  lua_client
3.  lua_cur_script
4.  lua_scripts
5.  lua_scripts_mem
6.  lua_replicate_commands
7.  lua_write_dirty
8.  lua_random_dirty
9.  lua_multi_emitted
10. lua_repl
11. lua_kill
12. lua_time_start
13. lua_time_snapshot

This commit is in a low risk of introducing any issues and it
is just moving varibales around and not changing any logic.
This commit is contained in:
meir@redislabs.com 2021-10-05 17:03:12 +03:00 committed by meir
parent 22aab1ce94
commit e0cd580aef
15 changed files with 253 additions and 170 deletions

View File

@ -1897,7 +1897,7 @@ void addACLLogEntry(client *c, int reason, int context, int argpos, sds username
} }
client *realclient = c; client *realclient = c;
if (realclient->flags & CLIENT_LUA) realclient = server.lua_caller; if (realclient->flags & CLIENT_LUA) realclient = server.script_caller;
le->cinfo = catClientInfoString(sdsempty(),realclient); le->cinfo = catClientInfoString(sdsempty(),realclient);
le->context = context; le->context = context;

View File

@ -2650,7 +2650,7 @@ standardConfig configs[] = {
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL), createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
/* Long Long configs */ /* Long Long configs */
createLongLongConfig("lua-time-limit", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.lua_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */ createLongLongConfig("script-time-limit", "lua-time-limit", MODIFIABLE_CONFIG, 0, LONG_MAX, server.script_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */
createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL),
createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL), createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL),

View File

@ -1501,8 +1501,8 @@ int keyIsExpired(redisDb *db, robj *key) {
* only the first time it is accessed and not in the middle of the * only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent. * script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */ * See issue #1525 on Github for more information. */
if (server.lua_caller) { if (server.script_caller) {
now = server.lua_time_snapshot; now = evalTimeSnapshot();
} }
/* If we are in the middle of a command execution, we still want to use /* If we are in the middle of a command execution, we still want to use
* a reference time that does not change: in that case we just use the * a reference time that does not change: in that case we just use the

View File

@ -919,7 +919,7 @@ NULL
addReplyStatus(c,"Apparently Redis did not crash: test passed"); addReplyStatus(c,"Apparently Redis did not crash: test passed");
} else if (!strcasecmp(c->argv[1]->ptr,"set-disable-deny-scripts") && c->argc == 3) } else if (!strcasecmp(c->argv[1]->ptr,"set-disable-deny-scripts") && c->argc == 3)
{ {
server.lua_disable_deny_script = atoi(c->argv[2]->ptr);; server.script_disable_deny_script = atoi(c->argv[2]->ptr);;
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"config-rewrite-force-all") && c->argc == 2) } else if (!strcasecmp(c->argv[1]->ptr,"config-rewrite-force-all") && c->argc == 2)
{ {

View File

@ -939,7 +939,7 @@ long defragOtherGlobals() {
/* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc. /* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc.
* but we assume most of these are short lived, we only need to defrag allocations * but we assume most of these are short lived, we only need to defrag allocations
* that remain static for a long time */ * that remain static for a long time */
defragged += activeDefragSdsDict(server.lua_scripts, DEFRAG_SDS_DICT_VAL_IS_STROB); defragged += activeDefragSdsDict(evalScriptsDict(), DEFRAG_SDS_DICT_VAL_IS_STROB);
defragged += activeDefragSdsListAndDict(server.repl_scriptcache_fifo, server.repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL); defragged += activeDefragSdsListAndDict(server.repl_scriptcache_fifo, server.repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL);
defragged += moduleDefragGlobals(); defragged += moduleDefragGlobals();
return defragged; return defragged;

View File

@ -50,6 +50,9 @@ void ldbLog(sds entry);
void ldbLogRedisReply(char *reply); void ldbLogRedisReply(char *reply);
sds ldbCatStackValue(sds s, lua_State *lua, int idx); sds ldbCatStackValue(sds s, lua_State *lua, int idx);
/* Lua context */
luaCtx lctx;
/* Debugger shared state is stored inside this global structure. */ /* Debugger shared state is stored inside this global structure. */
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ #define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ #define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
@ -138,10 +141,10 @@ int luaRedisDebugCommand(lua_State *lua) {
* already started to write, returns false and stick to whole scripts * already started to write, returns false and stick to whole scripts
* replication, which is our default. */ * replication, which is our default. */
int luaRedisReplicateCommandsCommand(lua_State *lua) { int luaRedisReplicateCommandsCommand(lua_State *lua) {
if (server.lua_write_dirty) { if (lctx.lua_write_dirty) {
lua_pushboolean(lua,0); lua_pushboolean(lua,0);
} else { } else {
server.lua_replicate_commands = 1; lctx.lua_replicate_commands = 1;
/* When we switch to single commands replication, we can provide /* When we switch to single commands replication, we can provide
* different math.random() sequences at every call, which is what * different math.random() sequences at every call, which is what
* the user normally expects. */ * the user normally expects. */
@ -165,19 +168,19 @@ void scriptingInit(int setup) {
lua_State *lua = lua_open(); lua_State *lua = lua_open();
if (setup) { if (setup) {
server.lua_client = NULL; lctx.lua_client = NULL;
server.lua_caller = NULL; server.script_caller = NULL;
server.lua_cur_script = NULL; lctx.lua_cur_script = NULL;
server.lua_timedout = 0; server.script_timedout = 0;
server.lua_disable_deny_script = 0; server.script_disable_deny_script = 0;
ldbInit(); ldbInit();
} }
/* Initialize a dictionary we use to map SHAs to scripts. /* Initialize a dictionary we use to map SHAs to scripts.
* This is useful for replication, as we need to replicate EVALSHA * This is useful for replication, as we need to replicate EVALSHA
* as EVAL, so we need to remember the associated script. */ * as EVAL, so we need to remember the associated script. */
server.lua_scripts = dictCreate(&shaScriptObjectDictType); lctx.lua_scripts = dictCreate(&shaScriptObjectDictType);
server.lua_scripts_mem = 0; lctx.lua_scripts_mem = 0;
luaEngineRegisterRedisAPI(lua); luaEngineRegisterRedisAPI(lua);
@ -238,12 +241,12 @@ void scriptingInit(int setup) {
* inside the Lua interpreter. * inside the Lua interpreter.
* Note: there is no need to create it again when this function is called * Note: there is no need to create it again when this function is called
* by scriptingReset(). */ * by scriptingReset(). */
if (server.lua_client == NULL) { if (lctx.lua_client == NULL) {
server.lua_client = createClient(NULL); lctx.lua_client = createClient(NULL);
server.lua_client->flags |= CLIENT_LUA; lctx.lua_client->flags |= CLIENT_LUA;
/* We do not want to allow blocking commands inside Lua */ /* We do not want to allow blocking commands inside Lua */
server.lua_client->flags |= CLIENT_DENY_BLOCKING; lctx.lua_client->flags |= CLIENT_DENY_BLOCKING;
} }
/* Lua beginners often don't use "local", this is likely to introduce /* Lua beginners often don't use "local", this is likely to introduce
@ -251,18 +254,18 @@ void scriptingInit(int setup) {
* to global variables. */ * to global variables. */
scriptingEnableGlobalsProtection(lua); scriptingEnableGlobalsProtection(lua);
server.lua = lua; lctx.lua = lua;
} }
/* Release resources related to Lua scripting. /* Release resources related to Lua scripting.
* This function is used in order to reset the scripting environment. */ * This function is used in order to reset the scripting environment. */
void scriptingRelease(int async) { void scriptingRelease(int async) {
if (async) if (async)
freeLuaScriptsAsync(server.lua_scripts); freeLuaScriptsAsync(lctx.lua_scripts);
else else
dictRelease(server.lua_scripts); dictRelease(lctx.lua_scripts);
server.lua_scripts_mem = 0; lctx.lua_scripts_mem = 0;
lua_close(server.lua); lua_close(lctx.lua);
} }
void scriptingReset(int async) { void scriptingReset(int async) {
@ -291,7 +294,7 @@ void scriptingReset(int async) {
* *
* If 'c' is not NULL, on error the client is informed with an appropriate * If 'c' is not NULL, on error the client is informed with an appropriate
* error describing the nature of the problem and the Lua interpreter error. */ * error describing the nature of the problem and the Lua interpreter error. */
sds luaCreateFunction(client *c, lua_State *lua, robj *body) { sds luaCreateFunction(client *c, robj *body) {
char funcname[43]; char funcname[43];
dictEntry *de; dictEntry *de;
@ -300,7 +303,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
sha1hex(funcname+2,body->ptr,sdslen(body->ptr)); sha1hex(funcname+2,body->ptr,sdslen(body->ptr));
sds sha = sdsnewlen(funcname+2,40); sds sha = sdsnewlen(funcname+2,40);
if ((de = dictFind(server.lua_scripts,sha)) != NULL) { if ((de = dictFind(lctx.lua_scripts,sha)) != NULL) {
sdsfree(sha); sdsfree(sha);
return dictGetKey(de); return dictGetKey(de);
} }
@ -312,25 +315,25 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
funcdef = sdscatlen(funcdef,"\nend",4); funcdef = sdscatlen(funcdef,"\nend",4);
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { if (luaL_loadbuffer(lctx.lua,funcdef,sdslen(funcdef),"@user_script")) {
if (c != NULL) { if (c != NULL) {
addReplyErrorFormat(c, addReplyErrorFormat(c,
"Error compiling script (new function): %s\n", "Error compiling script (new function): %s\n",
lua_tostring(lua,-1)); lua_tostring(lctx.lua,-1));
} }
lua_pop(lua,1); lua_pop(lctx.lua,1);
sdsfree(sha); sdsfree(sha);
sdsfree(funcdef); sdsfree(funcdef);
return NULL; return NULL;
} }
sdsfree(funcdef); sdsfree(funcdef);
if (lua_pcall(lua,0,0,0)) { if (lua_pcall(lctx.lua,0,0,0)) {
if (c != NULL) { if (c != NULL) {
addReplyErrorFormat(c,"Error running script (new function): %s\n", addReplyErrorFormat(c,"Error running script (new function): %s\n",
lua_tostring(lua,-1)); lua_tostring(lctx.lua,-1));
} }
lua_pop(lua,1); lua_pop(lctx.lua,1);
sdsfree(sha); sdsfree(sha);
return NULL; return NULL;
} }
@ -338,31 +341,31 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
/* We also save a SHA1 -> Original script map in a dictionary /* We also save a SHA1 -> Original script map in a dictionary
* so that we can replicate / write in the AOF all the * so that we can replicate / write in the AOF all the
* EVALSHA commands as EVAL using the original script. */ * EVALSHA commands as EVAL using the original script. */
int retval = dictAdd(server.lua_scripts,sha,body); int retval = dictAdd(lctx.lua_scripts,sha,body);
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK); serverAssertWithInfo(c ? c : lctx.lua_client,NULL,retval == DICT_OK);
server.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body); lctx.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
incrRefCount(body); incrRefCount(body);
return sha; return sha;
} }
void prepareLuaClient(void) { void prepareLuaClient(void) {
/* Select the right DB in the context of the Lua client */ /* Select the right DB in the context of the Lua client */
selectDb(server.lua_client,server.lua_caller->db->id); selectDb(lctx.lua_client,server.script_caller->db->id);
server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */ lctx.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
/* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */ /* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */
if (server.lua_caller->flags & CLIENT_MULTI) { if (server.script_caller->flags & CLIENT_MULTI) {
server.lua_client->flags |= CLIENT_MULTI; lctx.lua_client->flags |= CLIENT_MULTI;
} }
} }
void resetLuaClient(void) { void resetLuaClient(void) {
/* After the script done, remove the MULTI state. */ /* After the script done, remove the MULTI state. */
server.lua_client->flags &= ~CLIENT_MULTI; lctx.lua_client->flags &= ~CLIENT_MULTI;
} }
void evalGenericCommand(client *c, int evalsha) { void evalGenericCommand(client *c, int evalsha) {
lua_State *lua = server.lua; lua_State *lua = lctx.lua;
char funcname[43]; char funcname[43];
long long numkeys; long long numkeys;
long long initial_server_dirty = server.dirty; long long initial_server_dirty = server.dirty;
@ -380,11 +383,11 @@ void evalGenericCommand(client *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; lctx.lua_random_dirty = 0;
server.lua_write_dirty = 0; lctx.lua_write_dirty = 0;
server.lua_replicate_commands = server.lua_always_replicate_commands; lctx.lua_replicate_commands = server.lua_always_replicate_commands;
server.lua_multi_emitted = 0; lctx.lua_multi_emitted = 0;
server.lua_repl = PROPAGATE_AOF|PROPAGATE_REPL; lctx.lua_repl = PROPAGATE_AOF|PROPAGATE_REPL;
/* Get the number of arguments that are keys */ /* Get the number of arguments that are keys */
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK) if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != C_OK)
@ -433,7 +436,7 @@ void evalGenericCommand(client *c, int evalsha) {
addReplyErrorObject(c, shared.noscripterr); addReplyErrorObject(c, shared.noscripterr);
return; return;
} }
if (luaCreateFunction(c,lua,c->argv[1]) == NULL) { if (luaCreateFunction(c,c->argv[1]) == NULL) {
lua_pop(lua,1); /* remove the error handler from the stack. */ lua_pop(lua,1); /* remove the error handler from the stack. */
/* The error is sent to the client by luaCreateFunction() /* The error is sent to the client by luaCreateFunction()
* itself when it returns NULL. */ * itself when it returns NULL. */
@ -456,17 +459,17 @@ void evalGenericCommand(client *c, int evalsha) {
* *
* If we are debugging, we set instead a "line" hook so that the * If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */ * debugger is call-back at every line executed by the script. */
server.in_eval = 1; server.in_script = 1;
server.lua_caller = c; server.script_caller = c;
server.lua_cur_script = funcname + 2; lctx.lua_cur_script = funcname + 2;
server.lua_time_start = getMonotonicUs(); lctx.lua_time_start = getMonotonicUs();
server.lua_time_snapshot = mstime(); lctx.lua_time_snapshot = mstime();
server.lua_kill = 0; lctx.lua_kill = 0;
if (server.lua_time_limit > 0 && ldb.active == 0) { if (server.script_time_limit > 0 && ldb.active == 0) {
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
delhook = 1; delhook = 1;
} else if (ldb.active) { } else if (ldb.active) {
lua_sethook(server.lua,luaLdbLineHook,LUA_MASKLINE|LUA_MASKCOUNT,100000); lua_sethook(lctx.lua,luaLdbLineHook,LUA_MASKLINE|LUA_MASKCOUNT,100000);
delhook = 1; delhook = 1;
} }
@ -481,8 +484,8 @@ void evalGenericCommand(client *c, int evalsha) {
/* Perform some cleanup that we need to do both on error and success. */ /* Perform some cleanup that we need to do both on error and success. */
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */ if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
if (server.lua_timedout) { if (server.script_timedout) {
server.lua_timedout = 0; server.script_timedout = 0;
blockingOperationEnds(); blockingOperationEnds();
/* Restore the client that was protected when the script timeout /* Restore the client that was protected when the script timeout
* was detected. */ * was detected. */
@ -490,9 +493,9 @@ void evalGenericCommand(client *c, int evalsha) {
if (server.masterhost && server.master) if (server.masterhost && server.master)
queueClientForReprocessing(server.master); queueClientForReprocessing(server.master);
} }
server.in_eval = 0; server.in_script = 0;
server.lua_caller = NULL; server.script_caller = NULL;
server.lua_cur_script = NULL; lctx.lua_cur_script = NULL;
/* Call the Lua garbage collector from time to time to avoid a /* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua, which adds too latency. * full cycle performed by Lua, which adds too latency.
@ -524,9 +527,9 @@ void evalGenericCommand(client *c, int evalsha) {
/* If we are using single commands replication, emit EXEC if there /* If we are using single commands replication, emit EXEC if there
* was at least a write. */ * was at least a write. */
if (server.lua_replicate_commands) { if (lctx.lua_replicate_commands) {
preventCommandPropagation(c); preventCommandPropagation(c);
if (server.lua_multi_emitted) { if (lctx.lua_multi_emitted) {
execCommandPropagateExec(c->db->id); execCommandPropagateExec(c->db->id);
} }
} }
@ -541,12 +544,12 @@ void evalGenericCommand(client *c, int evalsha) {
* For replication, every time a new slave attaches to the master, we need to * For replication, every time a new slave attaches to the master, we need to
* flush our cache of scripts that can be replicated as EVALSHA, while * 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. */ * for AOF we need to do so every time we rewrite the AOF file. */
if (evalsha && !server.lua_replicate_commands) { if (evalsha && !lctx.lua_replicate_commands) {
if (!replicationScriptCacheExists(c->argv[1]->ptr)) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) {
/* This script is not in our script cache, replicate it as /* This script is not in our script cache, replicate it as
* EVAL, then add it into the script cache, as from now on * EVAL, then add it into the script cache, as from now on
* slaves and AOF know about it. */ * slaves and AOF know about it. */
robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); robj *script = dictFetchValue(lctx.lua_scripts,c->argv[1]->ptr);
replicationScriptCacheAdd(c->argv[1]->ptr); replicationScriptCacheAdd(c->argv[1]->ptr);
serverAssertWithInfo(c,NULL,script != NULL); serverAssertWithInfo(c,NULL,script != NULL);
@ -648,25 +651,25 @@ NULL
addReplyArrayLen(c, c->argc-2); addReplyArrayLen(c, c->argc-2);
for (j = 2; j < c->argc; j++) { for (j = 2; j < c->argc; j++) {
if (dictFind(server.lua_scripts,c->argv[j]->ptr)) if (dictFind(lctx.lua_scripts,c->argv[j]->ptr))
addReply(c,shared.cone); addReply(c,shared.cone);
else else
addReply(c,shared.czero); addReply(c,shared.czero);
} }
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"load")) { } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"load")) {
sds sha = luaCreateFunction(c,server.lua,c->argv[2]); sds sha = luaCreateFunction(c,c->argv[2]);
if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */ if (sha == NULL) return; /* The error was sent by luaCreateFunction(). */
addReplyBulkCBuffer(c,sha,40); addReplyBulkCBuffer(c,sha,40);
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF); forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) { } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
if (server.lua_caller == NULL) { if (server.script_caller == NULL) {
addReplyError(c,"-NOTBUSY No scripts in execution right now."); addReplyError(c,"-NOTBUSY No scripts in execution right now.");
} else if (server.lua_caller->flags & CLIENT_MASTER) { } else if (server.script_caller->flags & CLIENT_MASTER) {
addReplyError(c,"-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed."); addReplyError(c,"-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.");
} else if (server.lua_write_dirty) { } else if (lctx.lua_write_dirty) {
addReplyError(c,"-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command."); addReplyError(c,"-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.");
} else { } else {
server.lua_kill = 1; lctx.lua_kill = 1;
addReply(c,shared.ok); addReply(c,shared.ok);
} }
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"debug")) { } else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"debug")) {
@ -693,6 +696,26 @@ NULL
} }
} }
unsigned long evalMemory() {
return lua_gc(lctx.lua, LUA_GCCOUNT, 0) * 1024LL;
}
dict* evalScriptsDict() {
return lctx.lua_scripts;
}
unsigned long evalScriptsMemory() {
return lctx.lua_scripts_mem +
dictSize(lctx.lua_scripts) * sizeof(dictEntry) +
dictSlots(lctx.lua_scripts) * sizeof(dictEntry*);
}
/* Returns the time when the script invocation started */
mstime_t evalTimeSnapshot() {
return lctx.lua_time_snapshot;
}
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
* LDB: Redis Lua debugging facilities * LDB: Redis Lua debugging facilities
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@ -717,6 +740,10 @@ void ldbFlushLog(list *log) {
listDelNode(log,ln); listDelNode(log,ln);
} }
int ldbIsEnabled(){
return ldb.active && ldb.step;
}
/* Enable debug mode of Lua scripts for this client. */ /* Enable debug mode of Lua scripts for this client. */
void ldbEnable(client *c) { void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG; c->flags |= CLIENT_LUA_DEBUG;
@ -1447,7 +1474,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
* implementation, with ldb.step enabled, so as a side effect the Redis command * implementation, with ldb.step enabled, so as a side effect the Redis command
* and its reply are logged. */ * and its reply are logged. */
void ldbRedis(lua_State *lua, sds *argv, int argc) { void ldbRedis(lua_State *lua, sds *argv, int argc) {
int j, saved_rc = server.lua_replicate_commands; int j, saved_rc = lctx.lua_replicate_commands;
if (!lua_checkstack(lua, argc + 1)) { if (!lua_checkstack(lua, argc + 1)) {
/* Increase the Lua stack if needed to make sure there is enough room /* Increase the Lua stack if needed to make sure there is enough room
@ -1466,10 +1493,10 @@ void ldbRedis(lua_State *lua, sds *argv, int argc) {
for (j = 1; j < argc; j++) for (j = 1; j < argc; j++)
lua_pushlstring(lua,argv[j],sdslen(argv[j])); lua_pushlstring(lua,argv[j],sdslen(argv[j]));
ldb.step = 1; /* Force redis.call() to log. */ ldb.step = 1; /* Force redis.call() to log. */
server.lua_replicate_commands = 1; lctx.lua_replicate_commands = 1;
lua_pcall(lua,argc-1,1,0); /* Stack: redis, result */ lua_pcall(lua,argc-1,1,0); /* Stack: redis, result */
ldb.step = 0; /* Disable logging. */ ldb.step = 0; /* Disable logging. */
server.lua_replicate_commands = saved_rc; lctx.lua_replicate_commands = saved_rc;
lua_pop(lua,2); /* Discard the result and clean the stack. */ lua_pop(lua,2); /* Discard the result and clean the stack. */
} }
@ -1657,9 +1684,9 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
/* Check if a timeout occurred. */ /* Check if a timeout occurred. */
if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) { if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) {
mstime_t elapsed = elapsedMs(server.lua_time_start); mstime_t elapsed = elapsedMs(server.script_time_limit);
mstime_t timelimit = server.lua_time_limit ? mstime_t timelimit = server.script_time_limit ?
server.lua_time_limit : 5000; server.script_time_limit : 5000;
if (elapsed >= timelimit) { if (elapsed >= timelimit) {
timeout = 1; timeout = 1;
ldb.step = 1; ldb.step = 1;
@ -1687,7 +1714,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
lua_pushstring(lua, "timeout during Lua debugging with client closing connection"); lua_pushstring(lua, "timeout during Lua debugging with client closing connection");
lua_error(lua); lua_error(lua);
} }
server.lua_time_start = getMonotonicUs(); lctx.lua_time_start = getMonotonicUs();
server.lua_time_snapshot = mstime(); lctx.lua_time_snapshot = mstime();
} }
} }

View File

@ -472,7 +472,7 @@ static int evictionTimeProc(
static int isSafeToPerformEvictions(void) { static int isSafeToPerformEvictions(void) {
/* - There must be no script in timeout condition. /* - There must be no script in timeout condition.
* - Nor we are loading data right now. */ * - Nor we are loading data right now. */
if (server.lua_timedout || server.loading) return 0; if (server.script_timedout || server.loading) return 0;
/* By default replicas should ignore maxmemory /* By default replicas should ignore maxmemory
* and just be masters exact copies. */ * and just be masters exact copies. */

View File

@ -629,7 +629,7 @@ void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
/* If this command is executed from with Lua or MULTI/EXEC we do not /* If this command is executed from with Lua or MULTI/EXEC we do not
* need to propagate EXEC */ * need to propagate EXEC */
if (server.in_eval || server.in_exec) return; if (server.in_script || server.in_exec) return;
/* Handle the replication of the final EXEC, since whatever a command /* Handle the replication of the final EXEC, since whatever a command
* emits is always wrapped around MULTI/EXEC. */ * emits is always wrapped around MULTI/EXEC. */
@ -2333,7 +2333,7 @@ int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) {
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) { void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
/* Skip this if client explicitly wrap the command with MULTI, or if /* Skip this if client explicitly wrap the command with MULTI, or if
* the module command was called by a script. */ * the module command was called by a script. */
if (server.in_eval || server.in_exec) return; if (server.in_script || server.in_exec) return;
/* If we already emitted MULTI return ASAP. */ /* If we already emitted MULTI return ASAP. */
if (server.propagate_in_transaction) return; if (server.propagate_in_transaction) return;
/* If this is a thread safe context, we do not want to wrap commands /* If this is a thread safe context, we do not want to wrap commands
@ -2709,7 +2709,7 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
} }
} }
if (server.in_eval) if (server.in_script)
flags |= REDISMODULE_CTX_FLAGS_LUA; flags |= REDISMODULE_CTX_FLAGS_LUA;
if (server.in_exec) if (server.in_exec)
@ -6215,7 +6215,7 @@ void unblockClientFromModule(client *c) {
*/ */
RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) { RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) {
client *c = ctx->client; client *c = ctx->client;
int islua = server.in_eval; int islua = server.in_script;
int ismulti = server.in_exec; int ismulti = server.in_exec;
c->bpop.module_blocked_handle = zmalloc(sizeof(RedisModuleBlockedClient)); c->bpop.module_blocked_handle = zmalloc(sizeof(RedisModuleBlockedClient));

View File

@ -2199,7 +2199,7 @@ int processInputBuffer(client *c) {
* condition on the slave. We want just to accumulate the replication * condition on the slave. We want just to accumulate the replication
* stream (instead of replying -BUSY like we do with other clients) and * stream (instead of replying -BUSY like we do with other clients) and
* later resume the processing. */ * later resume the processing. */
if (server.lua_timedout && c->flags & CLIENT_MASTER) break; if (server.script_timedout && c->flags & CLIENT_MASTER) break;
/* CLIENT_CLOSE_AFTER_REPLY closes the connection once the reply is /* CLIENT_CLOSE_AFTER_REPLY closes the connection once the reply is
* written to the client. Make sure to not let the reply grow after * written to the client. Make sure to not let the reply grow after

View File

@ -1203,9 +1203,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mh->aof_buffer = mem; mh->aof_buffer = mem;
mem_total+=mem; mem_total+=mem;
mem = server.lua_scripts_mem; mem = evalScriptsMemory();
mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
dictSlots(server.lua_scripts) * sizeof(dictEntry*);
mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) + mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*); dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
if (listLength(server.repl_scriptcache_fifo) > 0) { if (listLength(server.repl_scriptcache_fifo) > 0) {
@ -1325,7 +1323,7 @@ sds getMemoryDoctorReport(void) {
} }
/* Too many scripts are cached? */ /* Too many scripts are cached? */
if (dictSize(server.lua_scripts) > 1000) { if (dictSize(evalScriptsDict()) > 1000) {
many_scripts = 1; many_scripts = 1;
num_reports++; num_reports++;
} }

View File

@ -1303,8 +1303,8 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
* the script cache as well: on successful PSYNC after a restart, we need * the script cache as well: on successful PSYNC after a restart, we need
* to be able to process any EVALSHA inside the replication backlog the * to be able to process any EVALSHA inside the replication backlog the
* master will send us. */ * master will send us. */
if (rsi && dictSize(server.lua_scripts)) { if (rsi && dictSize(evalScriptsDict())) {
di = dictGetIterator(server.lua_scripts); di = dictGetIterator(evalScriptsDict());
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
robj *body = dictGetVal(de); robj *body = dictGetVal(de);
if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1) if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
@ -2808,7 +2808,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi, redisDb *dbarray) {
if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10); if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);
} else if (!strcasecmp(auxkey->ptr,"lua")) { } else if (!strcasecmp(auxkey->ptr,"lua")) {
/* Load the script back in memory. */ /* Load the script back in memory. */
if (luaCreateFunction(NULL,server.lua,auxval) == NULL) { if (luaCreateFunction(NULL, auxval) == NULL) {
rdbReportCorruptRDB( rdbReportCorruptRDB(
"Can't load Lua script from RDB file! " "Can't load Lua script from RDB file! "
"BODY: %s", (char*)auxval->ptr); "BODY: %s", (char*)auxval->ptr);

View File

@ -399,7 +399,7 @@ void luaPushError(lua_State *lua, char *error) {
/* If debugging is active and in step mode, log errors resulting from /* If debugging is active and in step mode, log errors resulting from
* Redis commands. */ * Redis commands. */
if (ldb.active && ldb.step) { if (ldbIsEnabled()) {
ldbLog(sdscatprintf(sdsempty(),"<error> %s",error)); ldbLog(sdscatprintf(sdsempty(),"<error> %s",error));
} }
@ -483,7 +483,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1)); addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
break; break;
case LUA_TBOOLEAN: case LUA_TBOOLEAN:
if (server.lua_client->resp == 2) if (lctx.lua_client->resp == 2)
addReply(c,lua_toboolean(lua,-1) ? shared.cone : addReply(c,lua_toboolean(lua,-1) ? shared.cone :
shared.null[c->resp]); shared.null[c->resp]);
else else
@ -653,7 +653,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
int luaRedisGenericCommand(lua_State *lua, int raise_error) { int luaRedisGenericCommand(lua_State *lua, int raise_error) {
int j, argc = lua_gettop(lua); int j, argc = lua_gettop(lua);
struct redisCommand *cmd; struct redisCommand *cmd;
client *c = server.lua_client; client *c = lctx.lua_client;
sds reply; sds reply;
/* Cached across calls. */ /* Cached across calls. */
@ -744,7 +744,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* Setup our fake client for command execution */ /* Setup our fake client for command execution */
c->argv = argv; c->argv = argv;
c->argc = argc; c->argc = argc;
c->user = server.lua_caller->user; c->user = server.script_caller->user;
/* Process module hooks */ /* Process module hooks */
moduleCallCommandFilters(c); moduleCallCommandFilters(c);
@ -752,7 +752,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
argc = c->argc; argc = c->argc;
/* Log the command if debugging is active. */ /* Log the command if debugging is active. */
if (ldb.active && ldb.step) { if (ldbIsEnabled()) {
sds cmdlog = sdsnew("<redis>"); sds cmdlog = sdsnew("<redis>");
for (j = 0; j < c->argc; j++) { for (j = 0; j < c->argc; j++) {
if (j == 10) { if (j == 10) {
@ -782,14 +782,14 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
c->cmd = c->lastcmd = cmd; c->cmd = c->lastcmd = cmd;
/* There are commands that are not allowed inside scripts. */ /* There are commands that are not allowed inside scripts. */
if (!server.lua_disable_deny_script && (cmd->flags & CMD_NOSCRIPT)) { if (!server.script_disable_deny_script && (cmd->flags & CMD_NOSCRIPT)) {
luaPushError(lua, "This Redis command is not allowed from scripts"); luaPushError(lua, "This Redis command is not allowed from scripts");
goto cleanup; goto cleanup;
} }
/* This check is for EVAL_RO, EVALSHA_RO. We want to allow only read only commands */ /* This check is for EVAL_RO, EVALSHA_RO. We want to allow only read only commands */
if ((server.lua_caller->cmd->proc == evalRoCommand || if ((server.script_caller->cmd->proc == evalRoCommand ||
server.lua_caller->cmd->proc == evalShaRoCommand) && server.script_caller->cmd->proc == evalShaRoCommand) &&
(cmd->flags & CMD_WRITE)) (cmd->flags & CMD_WRITE))
{ {
luaPushError(lua, "Write commands are not allowed from read-only scripts"); luaPushError(lua, "Write commands are not allowed from read-only scripts");
@ -828,13 +828,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
* of this script. */ * of this script. */
if (cmd->flags & CMD_WRITE) { if (cmd->flags & CMD_WRITE) {
int deny_write_type = writeCommandsDeniedByDiskError(); int deny_write_type = writeCommandsDeniedByDiskError();
if (server.lua_random_dirty && !server.lua_replicate_commands) { if (lctx.lua_random_dirty && !lctx.lua_replicate_commands) {
luaPushError(lua, luaPushError(lua,
"Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode."); "Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
goto cleanup; goto cleanup;
} else if (server.masterhost && server.repl_slave_ro && } else if (server.masterhost && server.repl_slave_ro &&
server.lua_caller->id != CLIENT_ID_AOF && server.script_caller->id != CLIENT_ID_AOF &&
!(server.lua_caller->flags & CLIENT_MASTER)) !(server.script_caller->flags & CLIENT_MASTER))
{ {
luaPushError(lua, shared.roslaveerr->ptr); luaPushError(lua, shared.roslaveerr->ptr);
goto cleanup; goto cleanup;
@ -857,29 +857,29 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
* first write in the context of this script, otherwise we can't stop * first write in the context of this script, otherwise we can't stop
* in the middle. */ * in the middle. */
if (server.maxmemory && /* Maxmemory is actually enabled. */ if (server.maxmemory && /* Maxmemory is actually enabled. */
server.lua_caller->id != CLIENT_ID_AOF && /* Don't care about mem if loading from AOF. */ server.script_caller->id != CLIENT_ID_AOF && /* Don't care about mem if loading from AOF. */
!server.masterhost && /* Slave must execute the script. */ !server.masterhost && /* Slave must execute the script. */
server.lua_write_dirty == 0 && /* Script had no side effects so far. */ lctx.lua_write_dirty == 0 && /* Script had no side effects so far. */
server.lua_oom && /* Detected OOM when script start. */ server.script_oom && /* Detected OOM when script start. */
(cmd->flags & CMD_DENYOOM)) (cmd->flags & CMD_DENYOOM))
{ {
luaPushError(lua, shared.oomerr->ptr); luaPushError(lua, shared.oomerr->ptr);
goto cleanup; goto cleanup;
} }
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1; if (cmd->flags & CMD_RANDOM) lctx.lua_random_dirty = 1;
if (cmd->flags & CMD_WRITE) server.lua_write_dirty = 1; if (cmd->flags & CMD_WRITE) lctx.lua_write_dirty = 1;
/* If this is a Redis Cluster node, we need to make sure Lua is not /* If this is a Redis Cluster node, we need to make sure Lua is not
* trying to access non-local keys, with the exception of commands * trying to access non-local keys, with the exception of commands
* received from our master or when loading the AOF back in memory. */ * received from our master or when loading the AOF back in memory. */
if (server.cluster_enabled && server.lua_caller->id != CLIENT_ID_AOF && if (server.cluster_enabled && server.script_caller->id != CLIENT_ID_AOF &&
!(server.lua_caller->flags & CLIENT_MASTER)) !(server.script_caller->flags & CLIENT_MASTER))
{ {
int error_code; int error_code;
/* Duplicate relevant flags in the lua client. */ /* Duplicate relevant flags in the lua client. */
c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING); c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING);
c->flags |= server.lua_caller->flags & (CLIENT_READONLY|CLIENT_ASKING); c->flags |= server.script_caller->flags & (CLIENT_READONLY|CLIENT_ASKING);
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,&error_code) != if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,&error_code) !=
server.cluster->myself) server.cluster->myself)
{ {
@ -904,14 +904,14 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* If we are using single commands replication, we need to wrap what /* If we are using single commands replication, we need to wrap what
* we propagate into a MULTI/EXEC block, so that it will be atomic like * we propagate into a MULTI/EXEC block, so that it will be atomic like
* a Lua script in the context of AOF and slaves. */ * a Lua script in the context of AOF and slaves. */
if (server.lua_replicate_commands && if (lctx.lua_replicate_commands &&
!server.lua_multi_emitted && !lctx.lua_multi_emitted &&
!(server.lua_caller->flags & CLIENT_MULTI) && !(server.script_caller->flags & CLIENT_MULTI) &&
server.lua_write_dirty && lctx.lua_write_dirty &&
server.lua_repl != PROPAGATE_NONE) lctx.lua_repl != PROPAGATE_NONE)
{ {
execCommandPropagateMulti(server.lua_caller->db->id); execCommandPropagateMulti(server.script_caller->db->id);
server.lua_multi_emitted = 1; lctx.lua_multi_emitted = 1;
/* Now we are in the MULTI context, the lua_client should be /* Now we are in the MULTI context, the lua_client should be
* flag as CLIENT_MULTI. */ * flag as CLIENT_MULTI. */
c->flags |= CLIENT_MULTI; c->flags |= CLIENT_MULTI;
@ -919,11 +919,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* Run the command */ /* Run the command */
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS; int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
if (server.lua_replicate_commands) { if (lctx.lua_replicate_commands) {
/* Set flags according to redis.set_repl() settings. */ /* Set flags according to redis.set_repl() settings. */
if (server.lua_repl & PROPAGATE_AOF) if (lctx.lua_repl & PROPAGATE_AOF)
call_flags |= CMD_CALL_PROPAGATE_AOF; call_flags |= CMD_CALL_PROPAGATE_AOF;
if (server.lua_repl & PROPAGATE_REPL) if (lctx.lua_repl & PROPAGATE_REPL)
call_flags |= CMD_CALL_PROPAGATE_REPL; call_flags |= CMD_CALL_PROPAGATE_REPL;
} }
call(c,call_flags); call(c,call_flags);
@ -953,13 +953,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
redisProtocolToLuaType(lua,reply); redisProtocolToLuaType(lua,reply);
/* If the debugger is active, log the reply from Redis. */ /* If the debugger is active, log the reply from Redis. */
if (ldb.active && ldb.step) if (ldbIsEnabled())
ldbLogRedisReply(reply); ldbLogRedisReply(reply);
/* Sort the output array if needed, assuming it is a non-null multi bulk /* Sort the output array if needed, assuming it is a non-null multi bulk
* reply as expected. */ * reply as expected. */
if ((cmd->flags & CMD_SORT_FOR_SCRIPT) && if ((cmd->flags & CMD_SORT_FOR_SCRIPT) &&
(server.lua_replicate_commands == 0) && (lctx.lua_replicate_commands == 0) &&
(reply[0] == '*' && reply[1] != '-')) { (reply[0] == '*' && reply[1] != '-')) {
luaSortArray(lua); luaSortArray(lua);
} }
@ -1076,7 +1076,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
int argc = lua_gettop(lua); int argc = lua_gettop(lua);
int flags; int flags;
if (server.lua_replicate_commands == 0) { if (lctx.lua_replicate_commands == 0) {
lua_pushstring(lua, "You can set the replication behavior only after turning on single commands replication with redis.replicate_commands()."); lua_pushstring(lua, "You can set the replication behavior only after turning on single commands replication with redis.replicate_commands().");
return lua_error(lua); return lua_error(lua);
} else if (argc != 1) { } else if (argc != 1) {
@ -1089,7 +1089,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_REPLICA, REPL_ALL or REPL_NONE."); lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_REPLICA, REPL_ALL or REPL_NONE.");
return lua_error(lua); return lua_error(lua);
} }
server.lua_repl = flags; lctx.lua_repl = flags;
return 0; return 0;
} }
@ -1145,7 +1145,7 @@ int luaSetResp(lua_State *lua) {
return lua_error(lua); return lua_error(lua);
} }
server.lua_client->resp = resp; lctx.lua_client->resp = resp;
return 0; return 0;
} }
@ -1385,29 +1385,29 @@ int redis_math_randomseed (lua_State *L) {
/* This is the Lua script "count" hook that we use to detect scripts timeout. */ /* This is the Lua script "count" hook that we use to detect scripts timeout. */
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
long long elapsed = elapsedMs(server.lua_time_start); long long elapsed = elapsedMs(lctx.lua_time_start);
UNUSED(ar); UNUSED(ar);
UNUSED(lua); UNUSED(lua);
/* Set the timeout condition if not already set and the maximum /* Set the timeout condition if not already set and the maximum
* execution time was reached. */ * execution time was reached. */
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { if (elapsed >= server.script_time_limit && server.script_timedout == 0) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Lua slow script detected: still in execution after %lld milliseconds. " "Lua slow script detected: still in execution after %lld milliseconds. "
"You can try killing the script using the SCRIPT KILL command. " "You can try killing the script using the SCRIPT KILL command. "
"Script SHA1 is: %s", "Script SHA1 is: %s",
elapsed, server.lua_cur_script); elapsed, lctx.lua_cur_script);
server.lua_timedout = 1; server.script_timedout = 1;
blockingOperationStarts(); blockingOperationStarts();
/* Once the script timeouts we reenter the event loop to permit others /* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason * 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. * 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 * If we don't do that the client may disconnect and could no longer be
* here when the EVAL command will return. */ * here when the EVAL command will return. */
protectClient(server.lua_caller); protectClient(server.script_caller);
} }
if (server.lua_timedout) processEventsWhileBlocked(); if (server.script_timedout) processEventsWhileBlocked();
if (server.lua_kill) { if (lctx.lua_kill) {
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL."); serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
/* /*

65
src/script_lua.h Normal file
View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2009-2021, Redis Labs Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __SCRIPT_LUA_H_
#define __SCRIPT_LUA_H_
#include "server.h"
#include "script.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
typedef struct luaCtx {
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
client *lua_client; /* The "fake client" to query Redis from Lua */
char *lua_cur_script; /* SHA1 of the script currently running, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
int lua_replicate_commands; /* True if we are doing single commands repl. */
int lua_write_dirty;
int lua_random_dirty;
int lua_multi_emitted;
int lua_repl;
int lua_kill;
monotime lua_time_start; /* monotonic timer to detect timed-out script */
mstime_t lua_time_snapshot; /* Snapshot of mstime when script is started */
} luaCtx;
extern luaCtx lctx;
void luaEngineRegisterRedisAPI(lua_State* lua);
void scriptingEnableGlobalsProtection(lua_State *lua);
void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec);
void luaMaskCountHook(lua_State *lua, lua_Debug *ar);
void luaReplyToRedisReply(client *c, lua_State *lua);
#endif /* __SCRIPT_LUA_H_ */

View File

@ -2994,7 +2994,7 @@ void cronUpdateMemoryStats() {
/* LUA memory isn't part of zmalloc_used, but it is part of the process RSS, /* LUA memory isn't part of zmalloc_used, but it is part of the process RSS,
* so we must deduct it in order to be able to calculate correct * so we must deduct it in order to be able to calculate correct
* "allocator fragmentation" ratio */ * "allocator fragmentation" ratio */
size_t lua_memory = lua_gc(server.lua,LUA_GCCOUNT,0)*1024LL; size_t lua_memory = evalMemory();
server.cron_malloc_stats.allocator_resident = server.cron_malloc_stats.process_rss - lua_memory; server.cron_malloc_stats.allocator_resident = server.cron_malloc_stats.process_rss - lua_memory;
} }
if (!server.cron_malloc_stats.allocator_active) if (!server.cron_malloc_stats.allocator_active)
@ -4247,7 +4247,7 @@ void initServer(void) {
server.pubsub_channels = dictCreate(&keylistDictType); server.pubsub_channels = dictCreate(&keylistDictType);
server.pubsub_patterns = dictCreate(&keylistDictType); server.pubsub_patterns = dictCreate(&keylistDictType);
server.cronloops = 0; server.cronloops = 0;
server.in_eval = 0; server.in_script = 0;
server.in_exec = 0; server.in_exec = 0;
server.propagate_in_transaction = 0; server.propagate_in_transaction = 0;
server.client_pause_in_transaction = 0; server.client_pause_in_transaction = 0;
@ -4937,11 +4937,11 @@ void call(client *c, int flags) {
/* If the caller is Lua, we want to force the EVAL caller to propagate /* If the caller is Lua, we want to force the EVAL caller to propagate
* the script if the command flag or client flag are forcing the * the script if the command flag or client flag are forcing the
* propagation. */ * propagation. */
if (c->flags & CLIENT_LUA && server.lua_caller) { if (c->flags & CLIENT_LUA && server.script_caller) {
if (c->flags & CLIENT_FORCE_REPL) if (c->flags & CLIENT_FORCE_REPL)
server.lua_caller->flags |= CLIENT_FORCE_REPL; server.script_caller->flags |= CLIENT_FORCE_REPL;
if (c->flags & CLIENT_FORCE_AOF) if (c->flags & CLIENT_FORCE_AOF)
server.lua_caller->flags |= CLIENT_FORCE_AOF; server.script_caller->flags |= CLIENT_FORCE_AOF;
} }
/* Note: the code below uses the real command that was executed /* Note: the code below uses the real command that was executed
@ -5070,8 +5070,8 @@ void call(client *c, int flags) {
/* If the client has keys tracking enabled for client side caching, /* If the client has keys tracking enabled for client side caching,
* make sure to remember the keys it fetched via this command. */ * make sure to remember the keys it fetched via this command. */
if (c->cmd->flags & CMD_READONLY) { if (c->cmd->flags & CMD_READONLY) {
client *caller = (c->flags & CLIENT_LUA && server.lua_caller) ? client *caller = (c->flags & CLIENT_LUA && server.script_caller) ?
server.lua_caller : c; server.script_caller : c;
if (caller->flags & CLIENT_TRACKING && if (caller->flags & CLIENT_TRACKING &&
!(caller->flags & CLIENT_TRACKING_BCAST)) !(caller->flags & CLIENT_TRACKING_BCAST))
{ {
@ -5172,14 +5172,14 @@ void populateCommandMovableKeys(struct redisCommand *cmd) {
* other operations can be performed by the caller. Otherwise * other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */ * if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) { int processCommand(client *c) {
if (!server.lua_timedout) { if (!server.script_timedout) {
/* Both EXEC and EVAL call call() directly so there should be /* Both EXEC and EVAL call call() directly so there should be
* no way in_exec or in_eval or propagate_in_transaction is 1. * no way in_exec or in_eval or propagate_in_transaction is 1.
* That is unless lua_timedout, in which case client may run * That is unless lua_timedout, in which case client may run
* some commands. */ * some commands. */
serverAssert(!server.propagate_in_transaction); serverAssert(!server.propagate_in_transaction);
serverAssert(!server.in_exec); serverAssert(!server.in_exec);
serverAssert(!server.in_eval); serverAssert(!server.in_script);
} }
moduleCallCommandFilters(c); moduleCallCommandFilters(c);
@ -5274,7 +5274,7 @@ int processCommand(client *c) {
if (server.cluster_enabled && if (server.cluster_enabled &&
!(c->flags & CLIENT_MASTER) && !(c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_LUA && !(c->flags & CLIENT_LUA &&
server.lua_caller->flags & CLIENT_MASTER) && server.script_caller->flags & CLIENT_MASTER) &&
!(!c->cmd->movablekeys && c->cmd->key_specs_num == 0 && !(!c->cmd->movablekeys && c->cmd->key_specs_num == 0 &&
c->cmd->proc != execCommand)) c->cmd->proc != execCommand))
{ {
@ -5309,7 +5309,7 @@ int processCommand(client *c) {
* the event loop since there is a busy Lua script running in timeout * the event loop since there is a busy Lua script running in timeout
* condition, to avoid mixing the propagation of scripts with the * condition, to avoid mixing the propagation of scripts with the
* propagation of DELs due to eviction. */ * propagation of DELs due to eviction. */
if (server.maxmemory && !server.lua_timedout) { if (server.maxmemory && !server.script_timedout) {
int out_of_memory = (performEvictions() == EVICT_FAIL); int out_of_memory = (performEvictions() == EVICT_FAIL);
/* performEvictions may evict keys, so we need flush pending tracking /* performEvictions may evict keys, so we need flush pending tracking
@ -5345,7 +5345,7 @@ int processCommand(client *c) {
* until first write within script, memory used by lua stack and * until first write within script, memory used by lua stack and
* arguments might interfere. */ * arguments might interfere. */
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) { if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
server.lua_oom = out_of_memory; server.script_oom = out_of_memory;
} }
} }
@ -5432,7 +5432,7 @@ int processCommand(client *c) {
* the MULTI plus a few initial commands refused, then the timeout * the MULTI plus a few initial commands refused, then the timeout
* condition resolves, and the bottom-half of the transaction gets * condition resolves, and the bottom-half of the transaction gets
* executed, see Github PR #7022. */ * executed, see Github PR #7022. */
if (server.lua_timedout && if (server.script_timedout &&
c->cmd->proc != authCommand && c->cmd->proc != authCommand &&
c->cmd->proc != helloCommand && c->cmd->proc != helloCommand &&
c->cmd->proc != replconfCommand && c->cmd->proc != replconfCommand &&
@ -6286,7 +6286,7 @@ sds genRedisInfoString(const char *section) {
size_t zmalloc_used = zmalloc_used_memory(); size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size; size_t total_system_mem = server.system_memory_size;
const char *evict_policy = evictPolicyToString(); const char *evict_policy = evictPolicyToString();
long long memory_lua = server.lua ? (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024 : 0; long long memory_lua = evalMemory();
struct redisMemOverhead *mh = getMemoryOverheadData(); struct redisMemOverhead *mh = getMemoryOverheadData();
/* Peak memory is updated from time to time by serverCron() so it /* Peak memory is updated from time to time by serverCron() so it
@ -6369,7 +6369,7 @@ sds genRedisInfoString(const char *section) {
used_memory_lua_hmem, used_memory_lua_hmem,
(long long) mh->lua_caches, (long long) mh->lua_caches,
used_memory_scripts_hmem, used_memory_scripts_hmem,
dictSize(server.lua_scripts), dictSize(evalScriptsDict()),
server.maxmemory, server.maxmemory,
maxmemory_hmem, maxmemory_hmem,
evict_policy, evict_policy,

View File

@ -1334,7 +1334,7 @@ struct redisServer {
int sentinel_mode; /* True if this instance is a Sentinel. */ int sentinel_mode; /* True if this instance is a Sentinel. */
size_t initial_memory_usage; /* Bytes used after initialization. */ size_t initial_memory_usage; /* Bytes used after initialization. */
int always_show_logo; /* Show logo even for non-stdout logging. */ int always_show_logo; /* Show logo even for non-stdout logging. */
int in_eval; /* Are we inside EVAL? */ int in_script; /* Are we inside EVAL? */
int in_exec; /* Are we inside EXEC? */ int in_exec; /* Are we inside EXEC? */
int propagate_in_transaction; /* Make sure we don't propagate nested MULTI/EXEC */ int propagate_in_transaction; /* Make sure we don't propagate nested MULTI/EXEC */
char *ignore_warnings; /* Config: warnings that should be ignored. */ char *ignore_warnings; /* Config: warnings that should be ignored. */
@ -1719,28 +1719,13 @@ struct redisServer {
is down? */ is down? */
int cluster_config_file_lock_fd; /* cluster config fd, will be flock */ int cluster_config_file_lock_fd; /* cluster config fd, will be flock */
/* Scripting */ /* Scripting */
lua_State *lua; /* The Lua interpreter. We use just one for all clients */ client *script_caller; /* The client running script right now, or NULL */
client *lua_client; /* The "fake client" to query Redis from Lua */ mstime_t script_time_limit; /* Script timeout in milliseconds */
client *lua_caller; /* The client running EVAL right now, or NULL */ int script_timedout; /* True if we reached the time limit for script
char* lua_cur_script; /* SHA1 of the script currently running, or NULL */ execution. */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
mstime_t lua_time_limit; /* Script timeout in milliseconds */
monotime lua_time_start; /* monotonic timer to detect timed-out script */
mstime_t lua_time_snapshot; /* Snapshot of mstime when script is started */
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
execution of the current script. */
int lua_replicate_commands; /* True if we are doing single commands repl. */
int lua_multi_emitted;/* True if we already propagated MULTI. */
int lua_repl; /* Script replication flags for redis.set_repl(). */
int lua_timedout; /* True if we reached the time limit for script
execution. */
int lua_kill; /* Kill the script if true. */
int lua_always_replicate_commands; /* Default replication type. */ int lua_always_replicate_commands; /* Default replication type. */
int lua_oom; /* OOM detected when script start? */ int script_oom; /* OOM detected when script start */
int lua_disable_deny_script; /* Allow running commands marked "no-script" inside a script. */ int script_disable_deny_script; /* Allow running commands marked "no-script" inside a script. */
/* Lazy free */ /* Lazy free */
int lazyfree_lazy_eviction; int lazyfree_lazy_eviction;
int lazyfree_lazy_expire; int lazyfree_lazy_expire;
@ -2722,8 +2707,16 @@ void scriptingInit(int setup);
int ldbRemoveChild(pid_t pid); int ldbRemoveChild(pid_t pid);
void ldbKillForkedSessions(void); void ldbKillForkedSessions(void);
int ldbPendingChildren(void); int ldbPendingChildren(void);
sds luaCreateFunction(client *c, lua_State *lua, robj *body); sds luaCreateFunction(client *c, robj *body);
void freeLuaScriptsAsync(dict *lua_scripts); void freeLuaScriptsAsync(dict *lua_scripts);
int ldbIsEnabled();
void ldbLog(sds entry);
void ldbLogRedisReply(char *reply);
void sha1hex(char *digest, char *script, size_t len);
unsigned long evalMemory();
dict* evalScriptsDict();
unsigned long evalScriptsMemory();
mstime_t evalTimeSnapshot();
/* Blocked clients */ /* Blocked clients */
void processUnblockedClients(void); void processUnblockedClients(void);