[Tarantool-patches] [PATCH v5 5/6] box/cbox: implement cbox Lua module
Vladislav Shpilevoy
v.shpilevoy at tarantool.org
Sat Oct 10 00:46:20 MSK 2020
Thanks for the patch!
See 10 comments below.
> @TarantoolBot document
> Title: cbox module
>
> Overview
> ========
>
> `cbox` module provides a way to create, delete and execute
> `C` procedures. Unlinke `box.schema.func` functionality this
> functions are not persistent in any space and live purely in
> memory, thus once node get turned off they are vanished. Same
> time such functions are allowed to be created and executed
> when a node is in read-only mode.
>
> Module functions
> ================
>
> `cbox.func.create(name) -> obj | nil, err`
> ------------------------------------------
>
> Create a new function with name `name`.
1. I hope this is a draft. Because in the final version it would be
better to explain what does it mean to 'create' a function (or 'load'
in the next version, I guess). That it will load user's dynamic lib
on the first function call. It does not create anything, just loads
an existing function from a shared file.
> Possible errors:
> - IllegalParams: function name is either not supplied
> or not a string.
> - IllegalParams: a function is already created.
> - OutOfMemory: unable to allocate a function.
>
> On success the new callable object is returned, otherwise
> `nil,error` pair.
>
> Example:
>
> ``` Lua
> f, err = require('cbox').create('func')
> if not f then
> print(err)
> end
> -- call the function
> f(arg1,arg2,...)
> ```
>
> `cbox.func.drop(name) -> true | nil, err`
> -----------------------------------------
>
> Deletes a function with name `name`.
>
> Possible errors:
> - IllegalParams: function name is either not supplied
> or not a string.
> - IllegalParams: the function does not exist.
>
> On success `true` is returned, otherwise `nil,error` pair.
>
> Example:
>
> ``` Lua
> ok, err = require('cbox').func.drop('func')
> if not ok then
> print(err)
> end
> ```
>
> `cbox.module.reload(name) -> true | nil, err`
> ---------------------------------------------
>
> Reloads a module with name `name` and all functions which
> were associated for the module.
2. What happens with the currently working functions? What
happens, if there are still non-deleted refs to the old
functions? What if all refs are deleted, but not on all
functions was called unload()?
> Possible errors:
> - IllegalParams: module name is either not supplied
> or not a string.
> - IllegalParams: the function does not exist.
> - ClientError: a module with the name provided does
> not exist.
>
> On success `true` is returned, otherwise `nil,error` pair.
>
> Example:
>
> ``` Lua
> ok, err = require('cbox').module.reload('func')
> if not ok then
> print(err)
> end
> ```
3. It seems the function invocation itself is not documented?
> diff --git a/src/box/lua/cbox.c b/src/box/lua/cbox.c
> new file mode 100644
> index 000000000..58585b44d
> --- /dev/null
> +++ b/src/box/lua/cbox.c
> @@ -0,0 +1,417 @@
> +/*
> + * SPDX-License-Identifier: BSD-2-Clause
> + *
> + * Copyright 2010-2020, Tarantool AUTHORS, please see AUTHORS file.
> + */
> +
> +#include <string.h>
> +#include <lua.h>
> +
> +#include "tt_static.h"
4. Why do you need tt_static.h? You don't use any of tt_*
functions here.
> +#include "assoc.h"
> +#include "diag.h"
> +#include "port.h"
> +#include "say.h"
5. You don't use say_* functions either.
> +
> +#include "box/error.h"
> +#include "box/func.h"
> +#include "box/call.h"
> +#include "box/port.h"
> +
> +#include "trivia/util.h"
> +#include "lua/utils.h"
> +
> +/** Function names to descriptor map. */
> +static struct mh_strnptr_t *func_hash;
> +
> +/**
> + * Function descriptor.
> + */
> +struct cbox_func {
> + /**
> + * Symbol descriptor for the function in
> + * an associated module.
> + */
> + struct module_sym mod_sym;
> +
> + /** Function name length */
> + size_t name_len;
> +
> + /** Function name */
> + char name[0];
> +};
> +
> +/**
> + * Find function descriptor by its name.
> + */
> +struct cbox_func *
> +cbox_func_find(const char *name, size_t len)
6. I assume the function should be 'static'. It is never
used out of cbox.c.
> +{
> + if (name != NULL && len > 0) {
> + mh_int_t e = mh_strnptr_find_inp(func_hash, name, len);
> + if (e != mh_end(func_hash))
> + return mh_strnptr_node(func_hash, e)->val;
> + }
> + return NULL;
> +}
> +
> +/**
> + * Add function descriptor to the cache.
> + */
> +static int
> +cbox_func_add(struct cbox_func *cfunc)
> +{
> + const struct mh_strnptr_node_t nd = {
> + .str = cfunc->name,
> + .len = cfunc->name_len,
> + .hash = mh_strn_hash(cfunc->name, cfunc->name_len),
> + .val = cfunc,
> + };
> +
> + mh_int_t h = mh_strnptr_put(func_hash, &nd, NULL, NULL);
> + if (h == mh_end(func_hash)) {
> + diag_set(OutOfMemory, sizeof(nd), __func__, "h");
> + return -1;
> + }
> + return 0;
> +}
> +
> +/**
> + * Setup a detailed error description into the diagnostics area.
> + */
> +static void
> +diag_set_param_error(struct lua_State *L, int idx, const char *func_name,
> + const char *param, const char *exp)
> +{
> + const char *typename = idx == 0 ?
> + "<unknown>" : lua_typename(L, lua_type(L, idx));
> + static const char *fmt =
> + "%s: wrong parameter \"%s\": expected %s, got %s";
7. Please, lets omit 'static' when it is not needed. It just raises questions.
> + diag_set(IllegalParams, fmt, func_name, param, exp, typename);
> +}
> +
> +/**
> + * Call function by its name from the Lua code.
> + */
> +static int
> +lcbox_func_call(struct lua_State *L)
> +{
> + int nr_args = lua_gettop(L);
8. It is not really a number of arguments. It includes the function
object itself.
> +
> + /*
> + * A function name is associated with the variable.
> + */
> + lua_getfield(L, -nr_args, "name");
> + size_t name_len;
> + const char *name = lua_tolstring(L, -1, &name_len);
> + struct cbox_func *cf = cbox_func_find(name, name_len);
> + /*
> + * Do not pop the result early, since Lua's GC may
> + * drop the name string found.
> + */
> + lua_pop(L, 1);
> +
> + if (cf == NULL) {
> + /*
> + * This can happen if someone changed the
> + * name of the function manually.
> + */
> + const char *fmt = tnt_errcode_desc(ER_FUNCTION_EXISTS);
> + diag_set(IllegalParams, fmt, name);
> + return luaT_push_nil_and_error(L);
> + }
> +
> + /*
> + * FIXME: We should get rid of luaT_newthread but this
> + * requires serious modifucations. In particular
9. modifucations -> modifications.
> + * 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;
> +}
> +
> +/**
> + * Create a new function.
> + *
> + * This function takes a function name from the caller
> + * stack @a L and creates a new function object.
> + *
> + * Possible errors:
> + *
> + * - IllegalParams: function name is either not supplied
> + * or not a string.
> + * - IllegalParams: a function is already created.
> + * - 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
> +lcbox_func_create(struct lua_State *L)
> +{
> + const char *method = "cbox.func.create";
> + struct cbox_func *cf = NULL;
> +
> + if (lua_gettop(L) != 1) {
> + const char *fmt = "Expects %s(\'name\')";
> + diag_set(IllegalParams, fmt, method);
> + goto out;
> + }
> +
> + if (lua_type(L, 1) != LUA_TSTRING) {
> + diag_set_param_error(L, 1, method,
> + lua_tostring(L, 1),
> + "function name");
> + goto out;
> + }
> +
> + size_t name_len;
> + const char *name = lua_tolstring(L, 1, &name_len);
> +
> + if (cbox_func_find(name, name_len) != NULL) {
> + const char *fmt = tnt_errcode_desc(ER_FUNCTION_EXISTS);
> + diag_set(IllegalParams, fmt, name);
> + goto out;
> + }
> +
> + size_t size = sizeof(*cf) + name_len + 1;
> + cf = malloc(size);
> + if (cf == NULL) {
> + diag_set(OutOfMemory, size, "malloc", "cf");
> + goto out;
> + }
> +
> + cf->mod_sym.addr = NULL;
> + cf->mod_sym.module = NULL;
> + cf->mod_sym.name = cf->name;
> + cf->name_len = name_len;
> +
> + memcpy(cf->name, name, name_len);
> + cf->name[name_len] = '\0';
> +
> + if (cbox_func_add(cf) != 0)
> + goto out;
> +
> + /*
> + * A new variable should be callable for
> + * convenient use in Lua.
> + */
> + lua_newtable(L);
> +
> + lua_pushstring(L, "name");
> + lua_pushstring(L, cf->name);
> + lua_settable(L, -3);
> +
> + lua_newtable(L);
> +
> + lua_pushstring(L, "__call");
> + lua_pushcfunction(L, lcbox_func_call);
> + lua_settable(L, -3);
> +
> + lua_setmetatable(L, -2);
> + return 1;
> +
> +out:
> + free(cf);
> + return luaT_push_nil_and_error(L);
> +}
> +
> +/**
> + * Drop a function.
> + *
> + * This function takes a function name from the caller
> + * stack @a L and drops a function object.
> + *
> + * Possible errors:
> + *
> + * - IllegalParams: function name is either not supplied
> + * or not a string.
> + * - 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
> +lcbox_func_drop(struct lua_State *L)
> +{
> + const char *method = "cbox.func.drop";
> + const char *name = NULL;
> +
> + if (lua_gettop(L) != 1) {
> + const char *fmt = "expects %s(\'name\')";
> + diag_set(IllegalParams, fmt, method);
> + return luaT_push_nil_and_error(L);
> + }
> +
> + if (lua_type(L, 1) != LUA_TSTRING) {
> + diag_set_param_error(L, 1, method,
> + lua_tostring(L, 1),
> + "function name or id");
> + return luaT_push_nil_and_error(L);
> + }
> +
> + size_t name_len;
> + name = lua_tolstring(L, 1, &name_len);
> +
> + mh_int_t e = mh_strnptr_find_inp(func_hash, name, name_len);
> + if (e == mh_end(func_hash)) {
> + const char *fmt = tnt_errcode_desc(ER_NO_SUCH_FUNCTION);
> + diag_set(IllegalParams, fmt, name);
> + return luaT_push_nil_and_error(L);
> + }
> +
> + struct cbox_func *cf = mh_strnptr_node(func_hash, e)->val;
> + mh_strnptr_del(func_hash, e, NULL);
> + free(cf);
10. I would advise to wrap it into cbox_func_delete(). I am 100% sure
we will add more deletion things to the function object soon.
> +
> + lua_pushboolean(L, true);
> + return 1;
> +}
More information about the Tarantool-patches
mailing list