[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