From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Kirill Shcherbatov Subject: [PATCH v2 6/9] box: load persistent Lua functions on creation Date: Thu, 6 Jun 2019 15:04:02 +0300 Message-Id: <1711e6a83d3d3ca2895d932b3c82b24065b10f2d.1559822429.git.kshcherbatov@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit To: tarantool-patches@freelists.org, vdavydov.dev@gmail.com Cc: Kirill Shcherbatov List-ID: This patch proceed persistent Lua function load on function object creation. Part of #4182 Needed for #1260 --- src/box/func.c | 77 +++++++++++++++-- src/box/func.h | 30 +++++-- src/box/func_def.h | 6 ++ test/box/persistent_func.result | 136 ++++++++++++++++++++++++++++++ test/box/persistent_func.test.lua | 61 ++++++++++++++ 5 files changed, 292 insertions(+), 18 deletions(-) create mode 100644 test/box/persistent_func.result create mode 100644 test/box/persistent_func.test.lua diff --git a/src/box/func.c b/src/box/func.c index 71c6bb6eb..b21221d9e 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -438,6 +438,7 @@ func_new(struct func_def *def) */ } else { assert(def->language == FUNC_LANGUAGE_LUA); + func->lua_ref = LUA_REFNIL; func->vtab = &func_lua_vtab; } return func; @@ -562,12 +563,14 @@ func_lua_unload(struct func *func) { assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); assert(func->vtab == &func_lua_vtab); - (void) func; + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, func->lua_ref); + func->lua_ref = LUA_REFNIL; } struct func_lua_call_impl_ctx { const char *args; const char *args_end; + struct func *func; const char *func_name; const char *func_name_end; }; @@ -579,11 +582,17 @@ func_lua_call_impl_cb(struct lua_State *L) (struct func_lua_call_impl_ctx *) lua_topointer(L, 1); lua_settop(L, 0); - int oc = 0; - if (luaT_func_find(L, ctx->func_name, ctx->func_name_end, &oc) != 0) { - diag_set(ClientError, ER_NO_SUCH_PROC, - ctx->func_name_end - ctx->func_name, ctx->func_name); - return luaT_error(L); + int oc = 1; + if (ctx->func != NULL && func_def_is_persistent(ctx->func->def)) { + assert(ctx->func->lua_ref != LUA_REFNIL); + lua_rawgeti(L, LUA_REGISTRYINDEX, ctx->func->lua_ref); + } else { + if (luaT_func_find(L, ctx->func_name, + ctx->func_name_end, &oc) != 0) { + diag_set(ClientError, ER_NO_SUCH_PROC, + ctx->func_name_end - ctx->func_name, ctx->func_name); + return luaT_error(L); + } } const char *args = ctx->args; @@ -611,12 +620,53 @@ func_lua_call_impl(lua_CFunction handler, struct func_lua_call_impl_ctx *ctx, return 0; } +static int +func_lua_load_cb(struct lua_State *L) +{ + struct func *func = (struct func *) lua_topointer(L, 1); + assert(func->lua_ref == LUA_REFNIL); + lua_settop(L, 0); + + struct region *region = &fiber()->gc; + size_t region_svp = region_used(region); + const char *load_pref = "return "; + uint32_t load_str_sz = strlen(load_pref) + strlen(func->def->body) + 1; + char *load_str = region_alloc(region, load_str_sz); + if (load_str == NULL) { + diag_set(OutOfMemory, load_str_sz, "region", "load_str"); + goto error; + } + sprintf(load_str, "%s%s", load_pref, func->def->body); + if (luaL_loadstring(L, load_str) != 0 || + luaT_call(L, 0, 1) != 0 || !lua_isfunction(L, -1)) { + diag_set(ClientError, ER_LOAD_FUNCTION, func->def->name, + func->def->body); + goto error; + } + func->lua_ref = luaL_ref(L, LUA_REGISTRYINDEX); + region_truncate(region, region_svp); + return lua_gettop(L); +error: + region_truncate(region, region_svp); + return luaT_error(L); +} + static int func_lua_load(struct func *func) { assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); assert(func->vtab == &func_lua_vtab); - (void) func; + assert(func->lua_ref == LUA_REFNIL); + + struct lua_State *L = lua_newthread(tarantool_L); + int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX); + lua_pushcfunction(L, func_lua_load_cb); + lua_pushlightuserdata(L, func); + int rc = luaT_call(L, 1, LUA_MULTRET); + luaL_unref(tarantool_L, LUA_REGISTRYINDEX, coro_ref); + if (rc != 0) + return -1; + assert(func->lua_ref != LUA_REFNIL); return 0; } @@ -627,7 +677,12 @@ func_lua_call(struct func *func, struct port *port, const char *args, assert(func != NULL && func->def->language == FUNC_LANGUAGE_LUA); assert(func->vtab == &func_lua_vtab); + if (func_def_is_persistent(func->def) && func->lua_ref == LUA_REFNIL && + func_lua_load(func) != 0) + return -1; + struct func_lua_call_impl_ctx ctx; + ctx.func = func; ctx.func_name = func->def->name; ctx.func_name_end = func->def->name + strlen(func->def->name); ctx.args = args; @@ -642,8 +697,11 @@ func_lua_reuse_runtime(struct func *new_func, struct func *old_func) assert(new_func->vtab == &func_c_vtab); assert(old_func != NULL && old_func->def->language == FUNC_LANGUAGE_LUA); assert(old_func->vtab == &func_c_vtab); - (void)new_func; - (void)old_func; + if (old_func->def->body != NULL && new_func->def->body != NULL && + strcmp(old_func->def->body, new_func->def->body) == 0) { + new_func->lua_ref = old_func->lua_ref; + old_func->lua_ref = LUA_REFNIL; + } } static struct func_vtab func_lua_vtab = { @@ -752,6 +810,7 @@ box_func_execute_by_name(const char *name, uint32_t name_len, struct port *port, * it exists. */ struct func_lua_call_impl_ctx ctx; + ctx.func = NULL; ctx.func_name = name; ctx.func_name_end = name + name_len; ctx.args = args; diff --git a/src/box/func.h b/src/box/func.h index 931718bba..7b920d7d3 100644 --- a/src/box/func.h +++ b/src/box/func.h @@ -70,15 +70,6 @@ struct func { * Anchor for module membership. */ struct rlist item; - /** - * For C functions, the body of the function. - */ - box_function_f c_func; - /** - * Each stored function keeps a handle to the - * dynamic library for the C callback. - */ - struct module *module; /** * Authentication id of the owner of the function, * used for set-user-id functions. @@ -88,6 +79,27 @@ struct func { * Cached runtime access information. */ struct access access[BOX_USER_MAX]; + /** Function runtime context. */ + union { + /** + * The reference index of Lua function object. + * Is equal to LUA_REFNIL when undefined. + */ + int lua_ref; + struct { + /** + * For C functions, the body of the + * function. + */ + box_function_f c_func; + /** + * Each stored C function keeps a handle + * to the dynamic library for the C + * callback. + */ + struct module *module; + }; + }; }; /** diff --git a/src/box/func_def.h b/src/box/func_def.h index 7a920f65e..8953e4776 100644 --- a/src/box/func_def.h +++ b/src/box/func_def.h @@ -89,6 +89,12 @@ func_def_sizeof(uint32_t name_len, uint32_t body_len) return sz; } +static inline bool +func_def_is_persistent(struct func_def *def) +{ + return def->body != NULL; +} + /** * API of C stored function. */ diff --git a/test/box/persistent_func.result b/test/box/persistent_func.result new file mode 100644 index 000000000..f5e03dd5b --- /dev/null +++ b/test/box/persistent_func.result @@ -0,0 +1,136 @@ +env = require('test_run') +--- +... +test_run = env.new() +--- +... +-- +-- gh-4182: Add persistent LUA functions. +-- +box.schema.user.grant('guest', 'execute', 'universe') +--- +... +net = require('net.box') +--- +... +conn = net.connect(box.cfg.listen) +--- +... +-- Test valid function. +test_run:cmd("setopt delimiter ';'") +--- +- true +... +body = [[function(tuple) + if type(tuple.address) ~= 'string' then return nil, 'Invalid field type' end + local t = tuple.address:lower():split() + for k,v in pairs(t) do t[k] = {v} end + return t +end +]] +test_run:cmd("setopt delimiter ''"); +--- +... +box.schema.func.create('test', {body = body, language = "C"}) +--- +- error: 'Failed to create function ''test'': function body may be specified only + for Lua language' +... +box.schema.func.create('test', {body = body}) +--- +... +box.schema.func.exists('test') +--- +- true +... +conn:call("test", {{address = "Moscow Dolgoprudny"}}) +--- +- [['moscow'], ['dolgoprudny']] +... +box.schema.func.create('test2', {body = body, is_deterministic = true}) +--- +... +-- Test function with spell error - case 1. +test_run:cmd("setopt delimiter ';'") +--- +- true +... +body_bad2 = [[function(tuple) + ret tuple +end +]] +test_run:cmd("setopt delimiter ''"); +--- +... +box.schema.func.create('body_bad2', {body = body_bad2}) +--- +... +conn:call("body_bad2", {{address = "Moscow Dolgoprudny"}}) +--- +- error: "Failed to dynamically load function 'body_bad2': function(tuple) \tret tuple + end " +... +box.schema.func.drop('body_bad2') +--- +... +-- Test function with spell error - case 2. +test_run:cmd("setopt delimiter ';'") +--- +- true +... +body_bad3 = [[func(tuple) + return tuple +end +]] +test_run:cmd("setopt delimiter ''"); +--- +... +box.schema.func.create('body_bad3', {body = body_bad3}) +--- +... +conn:call("body_bad3", {{address = "Moscow Dolgoprudny"}}) +--- +- error: "Failed to dynamically load function 'body_bad3': func(tuple) \treturn tuple + end " +... +box.schema.func.drop('body_bad3') +--- +... +conn:close() +--- +... +-- Restart server. +test_run:cmd("restart server default") +net = require('net.box') +--- +... +test_run = require('test_run').new() +--- +... +conn = net.connect(box.cfg.listen) +--- +... +conn:call("test", {{address = "Moscow Dolgoprudny"}}) +--- +- [['moscow'], ['dolgoprudny']] +... +conn:close() +--- +... +box.schema.func.exists('test') +--- +- true +... +box.schema.func.drop('test') +--- +... +box.schema.func.exists('test') +--- +- false +... +box.schema.func.drop('test2') +--- +... +box.schema.user.revoke('guest', 'execute', 'universe') +--- +... diff --git a/test/box/persistent_func.test.lua b/test/box/persistent_func.test.lua new file mode 100644 index 000000000..89b3f6c34 --- /dev/null +++ b/test/box/persistent_func.test.lua @@ -0,0 +1,61 @@ +env = require('test_run') +test_run = env.new() + +-- +-- gh-4182: Add persistent LUA functions. +-- +box.schema.user.grant('guest', 'execute', 'universe') +net = require('net.box') +conn = net.connect(box.cfg.listen) + +-- Test valid function. +test_run:cmd("setopt delimiter ';'") +body = [[function(tuple) + if type(tuple.address) ~= 'string' then return nil, 'Invalid field type' end + local t = tuple.address:lower():split() + for k,v in pairs(t) do t[k] = {v} end + return t +end +]] +test_run:cmd("setopt delimiter ''"); +box.schema.func.create('test', {body = body, language = "C"}) +box.schema.func.create('test', {body = body}) +box.schema.func.exists('test') +conn:call("test", {{address = "Moscow Dolgoprudny"}}) +box.schema.func.create('test2', {body = body, is_deterministic = true}) + +-- Test function with spell error - case 1. +test_run:cmd("setopt delimiter ';'") +body_bad2 = [[function(tuple) + ret tuple +end +]] +test_run:cmd("setopt delimiter ''"); +box.schema.func.create('body_bad2', {body = body_bad2}) +conn:call("body_bad2", {{address = "Moscow Dolgoprudny"}}) +box.schema.func.drop('body_bad2') + +-- Test function with spell error - case 2. +test_run:cmd("setopt delimiter ';'") +body_bad3 = [[func(tuple) + return tuple +end +]] +test_run:cmd("setopt delimiter ''"); +box.schema.func.create('body_bad3', {body = body_bad3}) +conn:call("body_bad3", {{address = "Moscow Dolgoprudny"}}) +box.schema.func.drop('body_bad3') + +conn:close() +-- Restart server. +test_run:cmd("restart server default") +net = require('net.box') +test_run = require('test_run').new() +conn = net.connect(box.cfg.listen) +conn:call("test", {{address = "Moscow Dolgoprudny"}}) +conn:close() +box.schema.func.exists('test') +box.schema.func.drop('test') +box.schema.func.exists('test') +box.schema.func.drop('test2') +box.schema.user.revoke('guest', 'execute', 'universe') -- 2.21.0