From d462c77c0202c6e0b47339182757f6cf99670d84 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov <vdavydov@tarantool.org> Date: Mon, 27 Nov 2023 15:12:38 +0300 Subject: [PATCH] net.box: allow calling stored Lua and C module functions with self.call The fix is simple: look up the function in `box.func` by name and, if found, execute its `call` method. The only tricky part is to avoid the lookup before `box.cfg` is called because `box.func` is unavailable at the time. We achieve that by checking `box.ctl.is_recovery_finished`. Closes #9131 NO_DOC=bug fix (cherry picked from commit e92a8e7b20dc7e2ab44afe7391ff46973acf0bcf) --- .../gh-9131-net-box-self-call-stored-func.md | 4 + src/box/lua/net_box.lua | 6 + test/box-luatest/CMakeLists.txt | 2 + .../gh_9131_net_box_self_call_stored_func.c | 25 ++++ ...131_net_box_self_call_stored_func_test.lua | 125 ++++++++++++++++++ 5 files changed, 162 insertions(+) create mode 100644 changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md create mode 100644 test/box-luatest/gh_9131_net_box_self_call_stored_func.c create mode 100644 test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua diff --git a/changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md b/changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md new file mode 100644 index 0000000000..e6f84ee8b1 --- /dev/null +++ b/changelogs/unreleased/gh-9131-net-box-self-call-stored-func.md @@ -0,0 +1,4 @@ +## bugfix/net.box + +* It is now possible to call stored Lua functions and C module functions with + `require('net.box').self:call()` (gh-9131). diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua index e7a5bd3cd3..1dbbe14887 100644 --- a/src/box/lua/net_box.lua +++ b/src/box/lua/net_box.lua @@ -1296,6 +1296,12 @@ this_module.self = { check_call_args(args) args = args or {} proc_name = tostring(proc_name) + if box.ctl.is_recovery_finished() then + local f = box.func[proc_name] + if f ~= nil then + return handle_eval_result(pcall(f.call, f, args)) + end + end local status, proc, obj = pcall(box.internal.call_loadproc, proc_name) if not status then rollback() diff --git a/test/box-luatest/CMakeLists.txt b/test/box-luatest/CMakeLists.txt index 368a777721..953fcb8378 100644 --- a/test/box-luatest/CMakeLists.txt +++ b/test/box-luatest/CMakeLists.txt @@ -2,3 +2,5 @@ include_directories(${MSGPUCK_INCLUDE_DIRS}) build_module(gh_4799_lib gh_4799_fix_c_stored_functions_call.c) target_link_libraries(gh_4799_lib msgpuck) build_module(gh_6506_lib gh_6506_wakeup_writing_to_wal_fiber.c) +build_module(gh_9131_lib gh_9131_net_box_self_call_stored_func.c) +target_link_libraries(gh_9131_lib msgpuck) diff --git a/test/box-luatest/gh_9131_net_box_self_call_stored_func.c b/test/box-luatest/gh_9131_net_box_self_call_stored_func.c new file mode 100644 index 0000000000..c0d9142819 --- /dev/null +++ b/test/box-luatest/gh_9131_net_box_self_call_stored_func.c @@ -0,0 +1,25 @@ +#include "module.h" +#include "msgpuck.h" + +int +c_func_echo(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + const char *data = args; + uint32_t count = mp_decode_array(&data); + for (uint32_t i = 0; i < count; i++) { + const char *data_end = data; + mp_next(&data_end); + box_return_mp(ctx, data, data_end); + data = data_end; + } + return 0; +} + +int +c_func_error(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + (void)ctx; + (void)args; + (void)args_end; + return box_error_raise(ER_PROC_LUA, "test"); +} diff --git a/test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua b/test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua new file mode 100644 index 0000000000..e78b54d229 --- /dev/null +++ b/test/box-luatest/gh_9131_net_box_self_call_stored_func_test.lua @@ -0,0 +1,125 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group('gh_9131') + +g.before_all(function(cg) + cg.server = server:new() + cg.server:start() + cg.server:exec(function() + package.cpath = ('%s/test/box-luatest/?.%s;%s'):format( + os.getenv('BUILDDIR'), jit.os == 'OSX' and 'dylib' or 'so', + package.path) + box.schema.func.create('gh_9131_lib.c_func_echo', {language = 'c'}) + box.schema.func.create('gh_9131_lib.c_func_error', {language = 'c'}) + box.schema.func.create('gh_9131_lib.c_func_undef', {language = 'c'}) + box.schema.func.create('c_func_undef', {language = 'c'}) + rawset(_G, 'lua_func_echo', function(...) return ... end) + box.schema.func.create('lua_func_echo') + rawset(_G, 'lua_func_error', function() return error('test') end) + box.schema.func.create('lua_func_error') + box.schema.func.create('lua_func_undef') + box.schema.func.create('stored_lua_func_echo', { + body = [[function(...) return ... end]], + }) + box.schema.func.create('stored_lua_func_error', { + body = [[function() return error('test') end]], + }) + rawset(_G, 'unreg_lua_func_echo', + function(...) return ... end) + rawset(_G, 'unreg_lua_func_error', + function() return error('test') end) + end) +end) + +g.after_all(function(cg) + cg.server:drop() + cg.server = nil +end) + +g.test_c_func = function(cg) + cg.server:exec(function() + local self = require('net.box').self + t.assert_equals({self:call('gh_9131_lib.c_func_echo')}, {}) + t.assert_equals({self:call('gh_9131_lib.c_func_echo', {})}, {}) + t.assert_equals({self:call('gh_9131_lib.c_func_echo', {1})}, {1}) + t.assert_equals({self:call('gh_9131_lib.c_func_echo', {1LL})}, {1}) + t.assert_equals({self:call('gh_9131_lib.c_func_echo', {1, 2, 3})}, + {1, 2, 3}) + t.assert_error_msg_equals('test', self.call, self, + 'gh_9131_lib.c_func_error') + t.assert_error_msg_equals( + "Procedure 'gh_9131.c_func_undef' is not defined", + self.call, self, 'gh_9131.c_func_undef') + t.assert_error_msg_equals( + "Failed to dynamically load module 'c_func_undef': " .. + "module not found", self.call, self, 'c_func_undef') + end) +end + +g.test_lua_func = function(cg) + cg.server:exec(function() + local self = require('net.box').self + t.assert_equals({self:call('lua_func_echo')}, {}) + t.assert_equals({self:call('lua_func_echo', {})}, {}) + t.assert_equals({self:call('lua_func_echo', {1})}, {1}) + t.assert_equals({self:call('lua_func_echo', {1LL})}, {1}) + t.assert_equals({self:call('lua_func_echo', {1, 2, 3})}, {1, 2, 3}) + t.assert_error_msg_equals('test', self.call, self, 'lua_func_error') + t.assert_error_msg_equals("Procedure 'lua_func_undef' is not defined", + self.call, self, 'lua_func_undef') + end) +end + +g.test_stored_lua_func = function(cg) + cg.server:exec(function() + local self = require('net.box').self + t.assert_equals({self:call('stored_lua_func_echo')}, {}) + t.assert_equals({self:call('stored_lua_func_echo', {})}, {}) + t.assert_equals({self:call('stored_lua_func_echo', {1})}, {1}) + t.assert_equals({self:call('stored_lua_func_echo', {1LL})}, {1}) + t.assert_equals({self:call('stored_lua_func_echo', {1, 2, 3})}, + {1, 2, 3}) + t.assert_error_msg_equals('test', self.call, self, + 'stored_lua_func_error') + end) +end + +g.test_unreg_lua_func = function(cg) + cg.server:exec(function() + local self = require('net.box').self + t.assert_equals({self:call('unreg_lua_func_echo')}, {}) + t.assert_equals({self:call('unreg_lua_func_echo', {})}, {}) + t.assert_equals({self:call('unreg_lua_func_echo', {1})}, {1}) + t.assert_equals({self:call('unreg_lua_func_echo', {1LL})}, {1}) + t.assert_equals({self:call('unreg_lua_func_echo', {1, 2, 3})}, + {1, 2, 3}) + t.assert_error_msg_equals('test', self.call, self, + 'unreg_lua_func_error') + t.assert_error_msg_equals( + "Procedure 'unreg_lua_func_undef' is not defined", + self.call, self, 'unreg_lua_func_undef') + end) +end + +local g_local = t.group('gh_9131.local') + +g_local.before_all(function() + rawset(_G, 'local_func_echo', function(...) return ... end) + rawset(_G, 'local_func_error', function() return error('test') end) +end) + +g_local.after_all(function() + rawset(_G, 'local_func_echo', nil) + rawset(_G, 'local_func_error', nil) +end) + +g_local.test_box_not_configured = function() + local self = require('net.box').self + t.assert_equals({self:call('local_func_echo')}, {}) + t.assert_equals({self:call('local_func_echo', {})}, {}) + t.assert_equals({self:call('local_func_echo', {1})}, {1}) + t.assert_equals({self:call('local_func_echo', {1LL})}, {1}) + t.assert_equals({self:call('local_func_echo', {1, 2, 3})}, {1, 2, 3}) + t.assert_error_msg_equals('test', self.call, self, 'local_func_error') +end -- GitLab