in src/storage/scripting.cc [608:714]
Status EvalGenericCommand(redis::Connection *conn, engine::Context *ctx, const std::string &body_or_sha,
const std::vector<std::string> &keys, const std::vector<std::string> &argv, bool evalsha,
std::string *output, bool read_only) {
Server *srv = conn->GetServer();
// Use the worker's private Lua VM when entering the read-only mode
lua_State *lua = conn->Owner()->Lua();
/* We obtain the script SHA1, then check if this function is already
* defined into the Lua state */
char funcname[2 + 40 + 1] = {};
memcpy(funcname, REDIS_LUA_FUNC_SHA_PREFIX, sizeof(REDIS_LUA_FUNC_SHA_PREFIX));
if (!evalsha) {
SHA1Hex(funcname + 2, body_or_sha.c_str(), body_or_sha.size());
} else {
for (int j = 0; j < 40; j++) {
funcname[j + 2] = static_cast<char>(tolower(body_or_sha[j]));
}
}
/* 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 */
std::string body;
if (evalsha) {
auto s = srv->ScriptGet(funcname + 2, &body);
if (!s.IsOK()) {
lua_pop(lua, 1); /* remove the error handler from the stack. */
return {Status::RedisNoScript, redis::errNoMatchingScript};
}
} else {
body = body_or_sha;
}
std::string sha = funcname + 2;
auto s = CreateFunction(srv, body, &sha, lua, false);
if (!s.IsOK()) {
lua_pop(lua, 1); /* remove the error handler from the stack. */
return s;
}
/* Now the following is guaranteed to return non nil */
lua_getglobal(lua, funcname);
}
ScriptRunCtx current_script_run_ctx;
current_script_run_ctx.conn = conn;
current_script_run_ctx.ctx = ctx;
current_script_run_ctx.flags = read_only ? ScriptFlagType::kScriptNoWrites : 0;
lua_getglobal(lua, fmt::format(REDIS_LUA_FUNC_SHA_FLAGS, funcname + 2).c_str());
if (!lua_isnil(lua, -1)) {
// It should be ensured that the conversion is successful
auto script_flags = lua_tointeger(lua, -1);
current_script_run_ctx.flags |= script_flags;
}
lua_pop(lua, 1);
SaveOnRegistry(lua, REGISTRY_SCRIPT_RUN_CTX_NAME, ¤t_script_run_ctx);
// For the Lua script, should be always run with RESP2 protocol,
// unless users explicitly set the protocol version in the script via `redis.setresp`.
// So we need to save the current protocol version and set it to RESP2,
// and then restore it after the script execution.
auto saved_protocol_version = conn->GetProtocolVersion();
conn->SetProtocolVersion(redis::RESP::v2);
/* Populate the argv and keys table accordingly to the arguments that
* EVAL received. */
SetGlobalArray(lua, "KEYS", keys);
SetGlobalArray(lua, "ARGV", argv);
if (lua_pcall(lua, 0, 1, -2)) {
auto msg = fmt::format("running script (call to {}): {}", funcname, lua_tostring(lua, -1));
*output = redis::Error({Status::NotOK, msg});
lua_pop(lua, 2);
} else {
*output = ReplyToRedisReply(conn, lua);
lua_pop(lua, 2);
}
conn->SetProtocolVersion(saved_protocol_version);
// clean global variables to prevent information leak in function commands
lua_pushnil(lua);
lua_setglobal(lua, "KEYS");
lua_pushnil(lua);
lua_setglobal(lua, "ARGV");
RemoveFromRegistry(lua, REGISTRY_SCRIPT_RUN_CTX_NAME);
/* 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. */
constexpr int64_t LUA_GC_CYCLE_PERIOD = 50;
static int64_t gc_count = 0;
gc_count++;
if (gc_count == LUA_GC_CYCLE_PERIOD) {
lua_gc(lua, LUA_GCSTEP, LUA_GC_CYCLE_PERIOD);
gc_count = 0;
}
return Status::OK();
}