[Tarantool-patches] [PATCH v12 7/8] box/cmod: implement cmod Lua module

Cyrill Gorcunov gorcunov at gmail.com
Mon Jan 18 23:35:55 MSK 2021


Currently to run "C" function from some external module
one have to register it first in "_func" system space. This
is a problem if node is in read-only mode (replica).

Still people would like to have a way to run such functions
even in ro mode. For this sake we implement "cmod" lua module.

Fixes #4642

Signed-off-by: Cyrill Gorcunov <gorcunov at gmail.com>

@TarantoolBot document
Title: cmod module

Overview
========

`cmod` module provides a way to create, delete and execute
`C` procedures. Unlike `box.schema.func` methods the functions
created with `cmod` help are not persistent and live purely
in memory. Once a node get turned off they are vanished.
An initial purpose for them is to execute them on nodes
which are running in read-only mode.

Module functions
================

`require('cmod').load(path) -> obj | nil, err`
----------------------------------------------

Loads a module from `path` and return an object instance
associate with the module. The `path` should not end up
with shared object extension (such as `.so`), only a file
name shall be there.

Possible errors:

- IllegalParams: module path is either not supplied
  or not a string.
- SystemError: unable to open a module due to a system error.
- ClientError: a module does not exist.
- OutOfMemory: unable to allocate a module.

`module:unload() -> true | nil, err`
------------------------------------

Unloads a module. Once the module is unloaded one can't load
new functions from this module instance.

Possible errors:

- IllegalParams: a module is not supplied.
- IllegalParams: a module is already unloaded.

Example:

``` Lua
m, err = require('cmod').load('path')
if not m then
    print(err)
end
ok, err = m:unload()
if not ok then
    print(err)
end
```

If there are functions from this module referenced somewhere
in other Lua code they still can be called because module
continue sitting in memory until the last reference is closed.

If the module become a target to the Lua's garbage collector
then unload is called implicitly.

`module:reload() -> true | nil, err`
------------------------------------

Reloads a module and all functions which were associated with
the module. Each module keeps a list of functions belonging to
the module and reload procedure cause the bound function to update
their addresses such that function execution will be routed via
a new library.

Modules are loaded with that named local binding which means
that reload of module symbols won't affect the functions which
are started execution already, only new calls will be rerouted.

Possible errors:
 - IllegalParams: a module is not supplied.
 - ClientError: a module does not exist.

On success `true` is returned, otherwise `nil,error` pair.

Example:

``` Lua
m, err = require('cmod').load('path')
if not m then
    print(err)
end
ok, err = m:reload()
if not ok then
    print(err)
end
```

module:load(name) -> obj | nil, err`
------------------------------------

Loads a new function with name `name` from the `module` object
and return a callable object instance associate with the function.

Possible errors:
 - IllegalParams: function name is either not supplied
   or not a string.
 - OutOfMemory: unable to allocate a function.

Example:

``` Lua
m, err = require('cmod').load('path')
if not m then
    print(err)
end
f, err = m:load('foo')
if not ok then
    print(err)
end
```

`function:unload() -> true | nil, err`
--------------------------------------

Unloads a function.

Possible errors:
 - IllegalParams: function name is either not supplied
   or not a string.
 - IllegalParams: the function does not exist.

Example:

``` Lua
m, err = require('cmod').load('path')
if not m then
    print(err)
end
f, err = m:load('foo')
if not ok then
    print(err)
end
ok, err = f:unload()
if not ok then
    print(err)
end
```

Executing a loaded function
===========================

Once function is loaded it can be executed by ordinary Lua call.
Lets consider the following example. We have a `C` function which
takes two numbers and returns their sum.

``` C
int
cfunc_sum(box_function_ctx_t *ctx, const char *args, const char *args_end)
{
	uint32_t arg_count = mp_decode_array(&args);
	if (arg_count != 2) {
		return box_error_set(__FILE__, __LINE__, ER_PROC_C, "%s",
			"invalid argument count");
	}
	uint64_t a = mp_decode_uint(&args);
	uint64_t b = mp_decode_uint(&args);

	char res[16];
	char *end = mp_encode_uint(res, a + b);
	box_return_mp(ctx, res, end);
	return 0;
}
```

The name of the function is `cfunc_sum` and the function is built into
`cfunc.so` shared library.

First we should load it as

``` Lua
m, err = require('cmod').load('cfunc')
if not m then
    print(err)
end
cfunc_sum, err = m:load('cfunc_sum')
if not cfunc_sum then
    print(err)
end
```

Once successfully loaded we can execute it. Note that unlike regular
Lua functions the context of `C` functions is different. They never
thrown an exception but return `true|nil, res` form where first value
set to `nil` in case of error condition and `res` carries an error
description.

Lets call the `cfunc_sum` with wrong number of arguments

``` Lua
local ok, res = cfunc_sum()
if not ok then
    print(res)
end
```

We will the `"invalid argument count"` message in output.
The error message has been set by the `box_error_set` in `C`
code above.

On success the first returned value set to `true` and `res` represent
function execution result.

``` Lua
local ok, res = cfunc_sum(1, 2)
assert(ok);
print(res)
```

We will see the number `3` in output.
---
 src/box/CMakeLists.txt |   1 +
 src/box/lua/cmod.c     | 718 +++++++++++++++++++++++++++++++++++++++++
 src/box/lua/cmod.h     |  24 ++
 src/box/lua/init.c     |   2 +
 4 files changed, 745 insertions(+)
 create mode 100644 src/box/lua/cmod.c
 create mode 100644 src/box/lua/cmod.h

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index 339e2c8a9..feba5a037 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -195,6 +195,7 @@ add_library(box STATIC
     lua/init.c
     lua/call.c
     lua/cfg.cc
+    lua/cmod.c
     lua/console.c
     lua/serialize_lua.c
     lua/tuple.c
diff --git a/src/box/lua/cmod.c b/src/box/lua/cmod.c
new file mode 100644
index 000000000..c3a44e3df
--- /dev/null
+++ b/src/box/lua/cmod.c
@@ -0,0 +1,718 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#include <string.h>
+#include <lua.h>
+
+#include "assoc.h"
+#include "diag.h"
+
+#include "box/module_cache.h"
+#include "box/error.h"
+#include "box/port.h"
+#include "tt_static.h"
+
+#include "trivia/util.h"
+#include "lua/utils.h"
+
+/**
+ * Function descriptor.
+ */
+struct cmod_func {
+	/**
+	 * Symbol descriptor for the function in
+	 * an associated module.
+	 */
+	struct module_sym mod_sym;
+	/**
+	 * Number of loads of the function.
+	 */
+	int64_t load_count;
+	/**
+	 * The function name (without a package path).
+	 */
+	const char *func_name;
+	/**
+	 * Length of @a name member.
+	 */
+	size_t len;
+	/**
+	 * Module path with function name separated
+	 * by a point, like "module.func".
+	 *
+	 * See parse_func_name helper, we simply have
+	 * to keep this strange architecture for backward
+	 * compatibility sake (ie for functions which
+	 * are created via box.schema.func).
+	 */
+	char name[0];
+};
+
+/** Get a handle associated with an object. */
+static void *
+cmod_get_handle(struct lua_State *L, const char *uname)
+{
+	void **pptr = luaL_testudata(L, 1, uname);
+	return pptr != NULL ? *pptr : NULL;
+}
+
+/** Set a handle to a new value. */
+static void
+cmod_set_handle(struct lua_State *L, const char *uname, void *ptr)
+{
+	void **pptr = luaL_testudata(L, 1, uname);
+	if (pptr != NULL)
+		*pptr = ptr;
+}
+
+/** Setup a new handle and associate it with an object. */
+static void
+cmod_setup_handle(struct lua_State *L, const char *uname, void *ptr)
+{
+	*(void **)lua_newuserdata(L, sizeof(void *)) = ptr;
+	luaL_getmetatable(L, uname);
+	lua_setmetatable(L, -2);
+}
+
+/** A type to find a module handle from an object. */
+static const char *cmod_module_handle_uname = "cmod_module_handle";
+
+static struct module *
+cmod_get_module_handle(struct lua_State *L, bool mandatory)
+{
+	struct module *m = cmod_get_handle(L, cmod_module_handle_uname);
+	if (mandatory && m == NULL) {
+		const char *fmt = "The module is already unloaded";
+		diag_set(IllegalParams, fmt);
+	}
+	return m;
+}
+
+static void
+cmod_set_module_handle(struct lua_State *L, struct module *module)
+{
+	cmod_set_handle(L, cmod_module_handle_uname, module);
+}
+
+static void
+cmod_setup_module_handle(struct lua_State *L, struct module *module)
+{
+	cmod_setup_handle(L, cmod_module_handle_uname, module);
+}
+
+/** A type to find a function handle from an object. */
+static const char *cmod_func_handle_uname = "cmod_func_handle";
+
+static struct cmod_func *
+cmod_get_func_handle(struct lua_State *L, bool mandatory)
+{
+	struct cmod_func *cf = cmod_get_handle(L, cmod_func_handle_uname);
+	if (mandatory && cf == NULL) {
+		const char *fmt = "The function is already unloaded";
+		diag_set(IllegalParams, fmt);
+	}
+	return cf;
+}
+
+static void
+cmod_set_func_handle(struct lua_State *L, struct cmod_func *cf)
+{
+	cmod_set_handle(L, cmod_func_handle_uname, cf);
+}
+
+static void
+cmod_setup_func_handle(struct lua_State *L, struct cmod_func *cf)
+{
+	cmod_setup_handle(L, cmod_func_handle_uname, cf);
+}
+
+/**
+ * Function name to cmod_func hash. The name includes
+ * module package path without file extension.
+ */
+static struct mh_strnptr_t *func_hash = NULL;
+
+/**
+ * Find function in cmod_func hash.
+ *
+ * @param name function name.
+ * @param name_len function name length.
+ *
+ * @returns function descriptor if found, NULL otherwise.
+ */
+struct cmod_func *
+cmod_func_find(const char *name, size_t name_len)
+{
+	mh_int_t e = mh_strnptr_find_inp(func_hash, name, name_len);
+	if (e == mh_end(func_hash))
+		return NULL;
+	return mh_strnptr_node(func_hash, e)->val;
+}
+
+/**
+ * Delete a function instance from the hash or decrease
+ * a reference if the function is still loaded.
+ *
+ * @param cf function descriptor.
+ *
+ * @retval true if the function has no more loaded instances
+ * and removed from the hash.
+ *
+ * @retval false if there are loaded instances left and function
+ * is kept in the hash.
+ */
+static bool
+cmod_func_del(struct cmod_func *cf)
+{
+	assert(cf->load_count > 0);
+	if (cf->load_count-- != 1)
+		return false;
+
+	mh_int_t e = mh_strnptr_find_inp(func_hash, cf->name, cf->len);
+	assert(e != mh_end(func_hash));
+	mh_strnptr_del(func_hash, e, NULL);
+	return true;
+}
+
+/**
+ * Add a function instance into the hash or increase
+ * a reference if the function is already exist.
+ *
+ * @param cf Function descriptor.
+ *
+ * Possible errors:
+ *
+ * - OutOfMemory: unable to allocate a hash entry.
+ *
+ * @retval 0 on success.
+ * @retval -1 on error, diag is set.
+ */
+static int
+cmod_func_add(struct cmod_func *cf)
+{
+	assert(cf->load_count >= 0);
+	if (cf->load_count++ != 0)
+		return 0;
+
+	const struct mh_strnptr_node_t nd = {
+		.str	= cf->name,
+		.len	= cf->len,
+		.hash	= mh_strn_hash(cf->name, cf->len),
+		.val	= cf,
+	};
+
+	mh_int_t e = mh_strnptr_put(func_hash, &nd, NULL, NULL);
+	if (e == mh_end(func_hash)) {
+		diag_set(OutOfMemory, sizeof(nd),
+			 "malloc", "cmod_func node");
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ * Unload a symbol and free a function instance.
+ *
+ * @param cf function descriptor.
+ */
+static void
+cmod_func_free(struct cmod_func *cf)
+{
+	module_sym_unload(&cf->mod_sym);
+	TRASH(cf);
+	free(cf);
+}
+
+/**
+ * Allocate a new function instance and resolve a symbol address.
+ *
+ * @param name package path and a function name, ie "module.foo"
+ * @param len length of @a name.
+ * @param func_name_len function name length, ie "3" for "module.foo"
+ *
+ * @returns function instance on success, NULL otherwise setting diag area.
+ */
+static struct cmod_func *
+cmod_func_new(const char *name, size_t len, size_t func_name_len)
+{
+	const ssize_t cf_size = sizeof(struct cmod_func);
+	size_t size = cf_size + len + 1;
+	struct cmod_func *cf = malloc(size);
+	if (cf == NULL) {
+		diag_set(OutOfMemory, size, "malloc", "cf");
+		return NULL;
+	}
+
+	cf->mod_sym.addr	= NULL;
+	cf->mod_sym.module	= NULL;
+	cf->load_count		= 0;
+	cf->mod_sym.name	= cf->name;
+	cf->func_name		= &cf->name[len - func_name_len];
+	cf->len			= len;
+
+	memcpy(cf->name, name, len);
+	cf->name[len] = '\0';
+
+	if (module_sym_load(&cf->mod_sym) != 0) {
+		cmod_func_free(cf);
+		return NULL;
+	}
+
+	return cf;
+}
+
+/**
+ * Load a new function.
+ *
+ * This function takes a function name from the caller
+ * stack @a L and creates a new function object. If
+ * the function is already loaded we simply return
+ * a reference to existing one.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: function name is either not supplied
+ *   or not a string.
+ * - IllegalParams: function references limit exceeded.
+ * - OutOfMemory: unable to allocate a function.
+ *
+ * @returns function object on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcmod_func_load(struct lua_State *L)
+{
+	const char *method = "function = module:load";
+	struct cmod_func *cf = NULL;
+
+	if (lua_gettop(L) != 2 || !lua_isstring(L, 2)) {
+		const char *fmt =
+			"Expects %s(\'name\') but no name passed";
+		diag_set(IllegalParams, fmt, method);
+		return luaT_push_nil_and_error(L);
+	}
+
+	struct module *m = cmod_get_module_handle(L, false);
+	if (m == NULL) {
+		const char *fmt =
+			"Expects %s(\'name\') but not module object passed";
+		diag_set(IllegalParams, fmt, method);
+		return luaT_push_nil_and_error(L);
+	}
+
+	const char *func_name = lua_tostring(L, 2);
+	const char *name = tt_sprintf("%s.%s", m->package, func_name);
+	size_t len = strlen(name);
+
+	cf = cmod_func_find(name, len);
+	if (cf == NULL) {
+		cf = cmod_func_new(name, len, strlen(func_name));
+		if (cf == NULL)
+			return luaT_push_nil_and_error(L);
+	}
+
+	if (cmod_func_add(cf) != 0) {
+		cmod_func_free(cf);
+		return luaT_push_nil_and_error(L);
+	}
+
+	cmod_setup_func_handle(L, cf);
+	return 1;
+}
+
+/**
+ * Unload a function.
+ *
+ * This function takes a function object from
+ * the caller stack @a L and unloads it.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: function is not supplied.
+ * - IllegalParams: the function does not exist.
+ *
+ * @returns true on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcmod_func_unload(struct lua_State *L)
+{
+	if (lua_gettop(L) != 1) {
+		const char *fmt = "Expects function:unload()";
+		diag_set(IllegalParams, fmt);
+		return luaT_push_nil_and_error(L);
+	}
+
+	struct cmod_func *cf = cmod_get_func_handle(L, true);
+	if (cf == NULL)
+		return luaT_push_nil_and_error(L);
+
+	cmod_set_func_handle(L, NULL);
+	if (cmod_func_del(cf))
+		cmod_func_free(cf);
+
+	lua_pushboolean(L, true);
+	return 1;
+}
+
+/**
+ * Load a new module.
+ *
+ * This function takes a module patch from the caller
+ * stack @a L and creates a new module object.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: module path is either not supplied
+ *   or not a string.
+ * - SystemError: unable to open a module due to a system error.
+ * - ClientError: a module does not exist.
+ * - OutOfMemory: unable to allocate a module.
+ *
+ * @returns module object on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcmod_module_load(struct lua_State *L)
+{
+	if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+		const char *fmt =
+			"Expects cmod.load(\'name\') but no name passed";
+		diag_set(IllegalParams, fmt);
+		return luaT_push_nil_and_error(L);
+	}
+
+	size_t name_len;
+	const char *name = lua_tolstring(L, 1, &name_len);
+
+	struct module *module = module_load(name, name_len);
+	if (module == NULL)
+		return luaT_push_nil_and_error(L);
+
+	cmod_setup_module_handle(L, module);
+	/*
+	 * Make sure the module won't disappear until
+	 * it is GC'ed or unloaded explicitly.
+	 */
+	module->ref++;
+	return 1;
+}
+
+/**
+ * Unload a module.
+ *
+ * This function takes a module object from
+ * the caller stack @a L and unloads it.
+ *
+ * If there are some active functions left then
+ * module won't be freed internally until last function
+ * from this module is unloaded, this is guaranteed by
+ * module_cache engine.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: a module is not supplied.
+ * - IllegalParams: a module does not exist.
+ *
+ * @returns true on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcmod_module_unload(struct lua_State *L)
+{
+	if (lua_gettop(L) != 1) {
+		const char *fmt = "Expects module:unload()";
+		diag_set(IllegalParams, fmt);
+		return luaT_push_nil_and_error(L);
+	}
+
+	struct module *m = cmod_get_module_handle(L, true);
+	if (m == NULL)
+		return luaT_push_nil_and_error(L);
+	m->ref--;
+	cmod_set_module_handle(L, NULL);
+	lua_pushboolean(L, true);
+	return 1;
+}
+
+/**
+ * Reload a module.
+ *
+ * This function takes a module object from the caller
+ * stack @a L and reloads all functions associated with
+ * it.
+ *
+ * Possible errors:
+ *
+ * - IllegalParams: a module is not supplied.
+ * - IllegalParams: a module does not exist.
+ * - ClientError: a module with the name provided does
+ *   not exist.
+ *
+ * @returns true on success or {nil,error} on error,
+ * the error is set to the diagnostics area.
+ */
+static int
+lcmod_module_reload(struct lua_State *L)
+{
+	if (lua_gettop(L) != 1) {
+		const char *fmt = "Expects module:reload()";
+		diag_set(IllegalParams, fmt);
+		return luaT_push_nil_and_error(L);
+	}
+
+	struct module *m = cmod_get_module_handle(L, true);
+	if (m == NULL)
+		return luaT_push_nil_and_error(L);
+
+	struct module *new = NULL;
+	size_t len = strlen(m->package);
+	if (module_reload(m->package, &m->package[len], &new) == 0) {
+		if (new != NULL) {
+			m->ref--;
+			module_unload(m);
+			new->ref++;
+			cmod_set_module_handle(L, new);
+			lua_pushboolean(L, true);
+			return 1;
+		}
+		diag_set(ClientError, ER_NO_SUCH_MODULE, m->package);
+	}
+	return luaT_push_nil_and_error(L);
+}
+
+/**
+ * Handle __index request for a module object.
+ */
+static int
+lcmod_module_handle_index(struct lua_State *L)
+{
+	/*
+	 * Instead of showing userdata pointer
+	 * lets provide a serialized value.
+	 */
+	lua_getmetatable(L, 1);
+	lua_pushvalue(L, 2);
+	lua_rawget(L, -2);
+	if (!lua_isnil(L, -1))
+		return 1;
+
+	struct module *m = cmod_get_module_handle(L, false);
+	if (m == NULL) {
+		lua_pushnil(L);
+		return 1;
+	}
+
+	const char *key = lua_tostring(L, 2);
+
+	if (lua_type(L, 2) != LUA_TSTRING || key == NULL) {
+		diag_set(IllegalParams,
+			 "Bad params, use __index(obj, <string>)");
+		return luaT_error(L);
+	}
+
+	if (strcmp(key, "path") == 0) {
+		lua_pushstring(L, m->package);
+		return 1;
+	}
+
+	return 0;
+}
+
+/**
+ * Module handle representation for REPL (console).
+ */
+static int
+lcmod_module_handle_serialize(struct lua_State *L)
+{
+	struct module *m = cmod_get_module_handle(L, true);
+	if (m == NULL) {
+		lua_pushnil(L);
+		return 1;
+	}
+
+	lua_createtable(L, 0, 0);
+	lua_pushstring(L, m->package);
+	lua_setfield(L, -2, "path");
+
+	return 1;
+}
+
+/**
+ * Collect a module handle.
+ */
+static int
+lcmod_module_handle_gc(struct lua_State *L)
+{
+	struct module *m = cmod_get_module_handle(L, false);
+	if (m != NULL) {
+		/*
+		 * If there are some referenced functions
+		 * alive then final module GC will happen
+		 * on last function unload (indirectly when
+		 * module symbol get collected by module_sym_unload
+		 * code).
+		 */
+		cmod_set_module_handle(L, NULL);
+		m->ref--;
+		module_unload(m);
+	}
+	return 0;
+}
+
+/**
+ * Function handle representation for REPL (console).
+ */
+static int
+lcmod_func_handle_serialize(struct lua_State *L)
+{
+	struct cmod_func *cf = cmod_get_func_handle(L, true);
+	if (cf == NULL) {
+		lua_pushnil(L);
+		return 1;
+	}
+
+	lua_createtable(L, 0, 0);
+	lua_pushstring(L, cf->name);
+	lua_setfield(L, -2, "name");
+
+	return 1;
+}
+
+/**
+ * Handle __index request for a function object.
+ */
+static int
+lcmod_func_handle_index(struct lua_State *L)
+{
+	/*
+	 * Instead of showing userdata pointer
+	 * lets provide a serialized value.
+	 */
+	lua_getmetatable(L, 1);
+	lua_pushvalue(L, 2);
+	lua_rawget(L, -2);
+	if (!lua_isnil(L, -1))
+		return 1;
+
+	struct cmod_func *cf = cmod_get_func_handle(L, true);
+	if (cf == NULL)
+		return luaT_error(L);
+
+	const char *key = lua_tostring(L, 2);
+
+	if (lua_type(L, 2) != LUA_TSTRING || key == NULL) {
+		diag_set(IllegalParams,
+			 "Bad params, use __index(obj, <string>)");
+		return luaT_error(L);
+	}
+
+	if (strcmp(key, "name") == 0) {
+		lua_pushstring(L, cf->name);
+		return 1;
+	}
+
+	return 0;
+}
+
+/**
+ * Collect function handle if there is no active loads left.
+ */
+static int
+lcmod_func_handle_gc(struct lua_State *L)
+{
+	struct cmod_func *cf = cmod_get_func_handle(L, false);
+	if (cf != NULL) {
+		cmod_set_func_handle(L, NULL);
+		if (cmod_func_del(cf))
+			cmod_func_free(cf);
+	}
+	return 0;
+}
+
+/**
+ * Call a function by its name from the Lua code.
+ */
+static int
+lcmod_func_handle_call(struct lua_State *L)
+{
+	struct cmod_func *cf = cmod_get_func_handle(L, true);
+	if (cf == NULL)
+		return luaT_push_nil_and_error(L);
+
+	/*
+	 * FIXME: We should get rid of luaT_newthread but this
+	 * requires serious modifications. In particular
+	 * port_lua_do_dump uses tarantool_L reference and
+	 * coro_ref must be valid as well.
+	 */
+	lua_State *args_L = luaT_newthread(tarantool_L);
+	if (args_L == NULL)
+		return luaT_push_nil_and_error(L);
+
+	int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX);
+	lua_xmove(L, args_L, lua_gettop(L) - 1);
+
+	struct port args;
+	port_lua_create(&args, args_L);
+	((struct port_lua *)&args)->ref = coro_ref;
+
+	struct port ret;
+	if (module_sym_call(&cf->mod_sym, &args, &ret) != 0) {
+		port_destroy(&args);
+		return luaT_push_nil_and_error(L);
+	}
+
+	int top = lua_gettop(L);
+	lua_pushboolean(L, true);
+	port_dump_lua(&ret, L, true);
+	int cnt = lua_gettop(L) - top;
+
+	port_destroy(&ret);
+	port_destroy(&args);
+
+	return cnt;
+}
+
+/**
+ * Initialize cmod module.
+ */
+void
+box_lua_cmod_init(struct lua_State *L)
+{
+	func_hash = mh_strnptr_new();
+	if (func_hash == NULL) {
+		panic("Can't allocate cmod hash table");
+	}
+
+	static const struct luaL_Reg module_methods[] = {
+		{ "load",		lcmod_module_load	},
+		{ NULL, NULL },
+	};
+	luaL_register_module(L, "cmod", module_methods);
+	lua_pop(L, 1);
+
+	static const struct luaL_Reg module_handle_methods[] = {
+		{ "load",		lcmod_func_load			},
+		{ "reload",		lcmod_module_reload		},
+		{ "unload",		lcmod_module_unload		},
+		{ "__index",		lcmod_module_handle_index	},
+		{ "__serialize",	lcmod_module_handle_serialize	},
+		{ "__gc",		lcmod_module_handle_gc		},
+		{ NULL, NULL },
+	};
+	luaL_register_type(L, cmod_module_handle_uname, module_handle_methods);
+
+	static const struct luaL_Reg func_handle_methods[] = {
+		{ "unload",		lcmod_func_unload		},
+		{ "__index",		lcmod_func_handle_index		},
+		{ "__serialize",	lcmod_func_handle_serialize	},
+		{ "__call",		lcmod_func_handle_call		},
+		{ "__gc",		lcmod_func_handle_gc		},
+		{ NULL, NULL },
+	};
+	luaL_register_type(L, cmod_func_handle_uname, func_handle_methods);
+}
diff --git a/src/box/lua/cmod.h b/src/box/lua/cmod.h
new file mode 100644
index 000000000..f0ea2d34d
--- /dev/null
+++ b/src/box/lua/cmod.h
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+
+#pragma once
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+
+/**
+ * Initialize cmod Lua module.
+ *
+ * @param L Lua state where to register the cmod module.
+ */
+void
+box_lua_cmod_init(struct lua_State *L);
+#if defined(__cplusplus)
+}
+#endif /* defined(__plusplus) */
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index fbcdfb20b..bad2b7ca9 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -60,6 +60,7 @@
 #include "box/lua/cfg.h"
 #include "box/lua/xlog.h"
 #include "box/lua/console.h"
+#include "box/lua/cmod.h"
 #include "box/lua/tuple.h"
 #include "box/lua/execute.h"
 #include "box/lua/key_def.h"
@@ -465,6 +466,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_tuple_init(L);
 	box_lua_call_init(L);
 	box_lua_cfg_init(L);
+	box_lua_cmod_init(L);
 	box_lua_slab_init(L);
 	box_lua_index_init(L);
 	box_lua_space_init(L);
-- 
2.29.2



More information about the Tarantool-patches mailing list