This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 63960f9b3 fix(function): reset lua states from all workers in FUNCTION 
DELETE (#3115)
63960f9b3 is described below

commit 63960f9b32b75b257515090a635d5ebe7ed01619
Author: Twice <[email protected]>
AuthorDate: Sat Aug 16 11:02:04 2025 +0800

    fix(function): reset lua states from all workers in FUNCTION DELETE (#3115)
---
 src/storage/scripting.cc                     | 44 ++++++++++++++++------------
 src/storage/scripting.h                      |  4 +--
 tests/gocase/unit/scripting/function_test.go | 16 ++++++++++
 3 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/src/storage/scripting.cc b/src/storage/scripting.cc
index 93a9463d2..d4b9a2546 100644
--- a/src/storage/scripting.cc
+++ b/src/storage/scripting.cc
@@ -282,7 +282,7 @@ int RedisRegisterFunction(lua_State *lua) {
 }
 
 Status FunctionLoad(redis::Connection *conn, engine::Context *ctx, const 
std::string &script, bool need_to_store,
-                    bool replace, [[maybe_unused]] std::string *lib_name, bool 
read_only) {
+                    bool replace, [[maybe_unused]] std::string *lib_name) {
   std::string first_line, lua_code;
   if (auto pos = script.find('\n'); pos != std::string::npos) {
     first_line = script.substr(0, pos);
@@ -293,10 +293,7 @@ Status FunctionLoad(redis::Connection *conn, 
engine::Context *ctx, const std::st
 
   const auto libname = GET_OR_RET(ExtractLibNameFromShebang(first_line));
 
-  auto srv = conn->GetServer();
-  auto lua = conn->Owner()->Lua();
-
-  if (FunctionIsLibExist(conn, ctx, libname, need_to_store, read_only)) {
+  if (FunctionIsLibExist(conn, ctx, libname, need_to_store)) {
     if (!replace) {
       return {Status::NotOK, "library already exists, please specify REPLACE 
to force load"};
     }
@@ -304,10 +301,17 @@ Status FunctionLoad(redis::Connection *conn, 
engine::Context *ctx, const std::st
     if (!s) return s;
   }
 
+  auto srv = conn->GetServer();
+  auto lua = conn->Owner()->Lua();
+
   ScriptRunCtx script_run_ctx;
   script_run_ctx.conn = conn;
   script_run_ctx.ctx = ctx;
-  script_run_ctx.flags = read_only ? ScriptFlagType::kScriptNoWrites : 0;
+  // in FunctionLoad(), the script MUST be read-only,
+  // because this function can be called multiple times
+  // for one script from multiple threads,
+  // and it is dangerous and unexpected to allow writes here.
+  script_run_ctx.flags = ScriptFlagType::kScriptNoWrites;
 
   SaveOnRegistry(lua, REGISTRY_SCRIPT_RUN_CTX_NAME, &script_run_ctx);
 
@@ -339,7 +343,7 @@ Status FunctionLoad(redis::Connection *conn, 
engine::Context *ctx, const std::st
 
   RemoveFromRegistry(lua, REGISTRY_SCRIPT_RUN_CTX_NAME);
 
-  if (!FunctionIsLibExist(conn, ctx, libname, false, read_only)) {
+  if (!FunctionIsLibExist(conn, ctx, libname, false)) {
     return {Status::NotOK, "Please register some function in FUNCTION LOAD"};
   }
 
@@ -347,7 +351,7 @@ Status FunctionLoad(redis::Connection *conn, 
engine::Context *ctx, const std::st
 }
 
 bool FunctionIsLibExist(redis::Connection *conn, engine::Context *ctx, const 
std::string &libname,
-                        bool need_check_storage, bool read_only) {
+                        bool need_check_storage) {
   auto srv = conn->GetServer();
   auto lua = conn->Owner()->Lua();
 
@@ -374,7 +378,7 @@ bool FunctionIsLibExist(redis::Connection *conn, 
engine::Context *ctx, const std
   if (!s) return false;
 
   std::string lib_name;
-  s = FunctionLoad(conn, ctx, code, false, false, &lib_name, read_only);
+  s = FunctionLoad(conn, ctx, code, false, false, &lib_name);
   return static_cast<bool>(s);
 }
 
@@ -399,7 +403,7 @@ Status FunctionCall(redis::Connection *conn, 
engine::Context *ctx, const std::st
     std::string libcode;
     s = srv->FunctionGetCode(libname, &libcode);
     if (!s) return s;
-    s = FunctionLoad(conn, ctx, libcode, false, false, &libname, read_only);
+    s = FunctionLoad(conn, ctx, libcode, false, false, &libname);
     if (!s) return s;
 
     lua_getglobal(lua, (REDIS_LUA_REGISTER_FUNC_PREFIX + name).c_str());
@@ -568,6 +572,11 @@ Status FunctionListLib(redis::Connection *conn, const 
std::string &libname, std:
 Status FunctionDelete(engine::Context &ctx, redis::Connection *conn, const 
std::string &name) {
   auto lua = conn->Owner()->Lua();
 
+  // load the library into this lua state
+  if (!FunctionIsLibExist(conn, &ctx, name, true)) {
+    return {Status::NotOK, "the library does not exist in lua environment"};
+  }
+
   lua_getglobal(lua, REDIS_FUNCTION_LIBRARIES);
   if (lua_isnil(lua, -1)) {
     lua_pop(lua, 1);
@@ -586,22 +595,21 @@ Status FunctionDelete(engine::Context &ctx, 
redis::Connection *conn, const std::
   for (size_t i = 1; i <= lua_objlen(lua, -1); ++i) {
     lua_rawgeti(lua, -1, static_cast<int>(i));
     std::string func = lua_tostring(lua, -1);
-    lua_pushnil(lua);
-    lua_setglobal(lua, (REDIS_LUA_REGISTER_FUNC_PREFIX + func).c_str());
-    lua_pushnil(lua);
-    lua_setglobal(lua, (REDIS_LUA_REGISTER_FUNC_FLAGS_PREFIX + func).c_str());
     auto _ = storage->Delete(ctx, rocksdb::WriteOptions(), cf, 
engine::kLuaFuncLibPrefix + func);
     lua_pop(lua, 1);
   }
 
-  lua_pop(lua, 1);
-  lua_pushnil(lua);
-  lua_setfield(lua, -2, name.c_str());
-  lua_pop(lua, 1);
+  lua_pop(lua, 2);
 
   auto s = storage->Delete(ctx, rocksdb::WriteOptions(), cf, 
engine::kLuaLibCodePrefix + name);
   if (!s.ok()) return {Status::NotOK, s.ToString()};
 
+  // reset all lua context from all workers
+  // to ensure the library is removed from all lua states
+  // TODO: optimize it to only delete this library
+  // instead of resetting all libraries
+  conn->GetServer()->ScriptReset();
+
   return Status::OK();
 }
 
diff --git a/src/storage/scripting.h b/src/storage/scripting.h
index f41cf6e9a..86130aac4 100644
--- a/src/storage/scripting.h
+++ b/src/storage/scripting.h
@@ -70,7 +70,7 @@ Status EvalGenericCommand(redis::Connection *conn, 
engine::Context *ctx, const s
 bool ScriptExists(lua_State *lua, const std::string &sha);
 
 Status FunctionLoad(redis::Connection *conn, engine::Context *ctx, const 
std::string &script, bool need_to_store,
-                    bool replace, std::string *lib_name, bool read_only = 
false);
+                    bool replace, std::string *lib_name);
 Status FunctionCall(redis::Connection *conn, engine::Context *ctx, const 
std::string &name,
                     const std::vector<std::string> &keys, const 
std::vector<std::string> &argv, std::string *output,
                     bool read_only = false);
@@ -81,7 +81,7 @@ Status FunctionListFunc(Server *srv, const redis::Connection 
*conn, engine::Cont
 Status FunctionListLib(redis::Connection *conn, const std::string &libname, 
std::string *output);
 Status FunctionDelete(engine::Context &ctx, redis::Connection *conn, const 
std::string &name);
 bool FunctionIsLibExist(redis::Connection *conn, engine::Context *ctx, const 
std::string &libname,
-                        bool need_check_storage = true, bool read_only = 
false);
+                        bool need_check_storage = true);
 
 const char *RedisProtocolToLuaType(lua_State *lua, const char *reply);
 const char *RedisProtocolToLuaTypeInt(lua_State *lua, const char *reply);
diff --git a/tests/gocase/unit/scripting/function_test.go 
b/tests/gocase/unit/scripting/function_test.go
index 43043a614..f58934952 100644
--- a/tests/gocase/unit/scripting/function_test.go
+++ b/tests/gocase/unit/scripting/function_test.go
@@ -289,6 +289,22 @@ var testFunctions = func(t *testing.T, config 
util.KvrocksServerConfigs) {
                        Name: "mylib3", Engine: "lua", Functions: 
[]interface{}{"myget", "myset"},
                }, decodeListLibResult(t, r))
        })
+
+       t.Run("FUNCTION DELETE from multiple clients", func(t *testing.T) {
+               // we expect that rdb2 is accepted from another server thread,
+               // but it may not (and that's fine)
+               rdb2 := srv.NewClient()
+               defer func() { require.NoError(t, rdb2.Close()) }()
+
+               require.Equal(t, rdb.Do(ctx, "FCALL", "reverse", 0, 
"abc").Val(), "cba")
+
+               require.NoError(t, rdb2.Do(ctx, "FUNCTION", "DELETE", 
"mylib1").Err())
+               util.ErrorRegexp(t, rdb.Do(ctx, "FCALL", "reverse", 0, 
"abc").Err(), ".*No such function name.*")
+               util.ErrorRegexp(t, rdb2.Do(ctx, "FCALL", "reverse", 0, 
"abc").Err(), ".*No such function name.*")
+
+               require.NoError(t, rdb.Do(ctx, "FCALL", "myset", 1, 
"func-test-tmp-a", 1).Err())
+               require.NoError(t, rdb2.Do(ctx, "FCALL", "myget", 1, 
"func-test-tmp-a").Err())
+       })
 }
 
 func TestFunctionScriptFlags(t *testing.T) {

Reply via email to