[PATCH v2 6/9] box: load persistent Lua functions on creation
Kirill Shcherbatov
kshcherbatov at tarantool.org
Thu Jun 6 15:04:02 MSK 2019
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
More information about the Tarantool-patches
mailing list