From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 81A756EC5D; Thu, 8 Apr 2021 19:44:29 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 81A756EC5D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1617900269; bh=Hxbj8VGuQtMJ7+vlpuIRIK1UKB2NV3W23SFY+7sNewc=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=rnXrb4p+v2ZPZUn7ZUv3Qq1U8gLEom6K+wRIgi2m2nXo40oqwxVMUteV4xVGYD1r4 PzYjLGcpP8Fm3PVAZgRvu4RRj28bmj26nLj0EZRYWdslLz5bVsGPv5L9htyWagBqL2 JWZKD91BxqQXiQdtc8UkExpGO6i5kqbKsdOwt8Qg= Received: from mail-lj1-f173.google.com (mail-lj1-f173.google.com [209.85.208.173]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 24C016EC5E for ; Thu, 8 Apr 2021 19:42:58 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 24C016EC5E Received: by mail-lj1-f173.google.com with SMTP id 184so3056047ljf.9 for ; Thu, 08 Apr 2021 09:42:58 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=U8NZR+GdVUvMUCXHBlF5/xb0MsvpTxku8hNYZErDG7c=; b=EF2Ng271t40JqXLcebY8KpauhgZkGlUDh9y8993lx8ppPCAZ8fsik1ghpclsGpCO7Y dXuWAXRx3+VPNnaKgStf+F5VZujNDSWPbI1mcYz4EIU5VFfom5aBUAiAYJGdzya5Qh4n 6tmovH2eMORHahDHgkarmacd4WiAcuG59jJoWUhTcz3cmpZAZNKaxE1hmye/ZNQDIxh2 P3TQdPmBy0X85FyMrN3pnKFoANUz8iHzpVAYMy+HWfKuY+lBdgqiCD7ePuies5xZenVS /JYIBiw70I8JLzpvS5VHJwPAPkhRvd+GFNkG41n70GuzkoJXkK561ppT+4cbSqc3l88Z R1CQ== X-Gm-Message-State: AOAM530O/nXP1V5IRFasvB3Q07+AD38jOhJIng+h3sSyZ9Uk9L7FVm/T UTNf9KPNRqURoLi+Giw6T0u4imzXR/M= X-Google-Smtp-Source: ABdhPJytasa1idi+IamVOHNNnViK8LBa159TdIP3HjyV0d7EEBXALhscuj4DW5VIdYmYH8tNrJzzig== X-Received: by 2002:a05:651c:3c3:: with SMTP id f3mr6378648ljp.105.1617900176275; Thu, 08 Apr 2021 09:42:56 -0700 (PDT) Received: from grain.localdomain ([5.18.199.94]) by smtp.gmail.com with ESMTPSA id f3sm2894374ljm.5.2021.04.08.09.42.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 08 Apr 2021 09:42:54 -0700 (PDT) Received: by grain.localdomain (Postfix, from userid 1000) id 865975601FD; Thu, 8 Apr 2021 19:41:52 +0300 (MSK) To: tml Date: Thu, 8 Apr 2021 19:41:50 +0300 Message-Id: <20210408164151.1759348-6-gorcunov@gmail.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210408164151.1759348-1-gorcunov@gmail.com> References: <20210408164151.1759348-1-gorcunov@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v21 5/6] box: implement box.lib module X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Cyrill Gorcunov via Tarantool-patches Reply-To: Cyrill Gorcunov Cc: Vladislav Shpilevoy Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" 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 "box.lib" lua module. Unlike `box.schema.func` interface the `box.lib` does not defer module loading procedure until first call of a function. Instead a module is loaded immediately and if some error happens (say shared library is corrupted or not found) it pops up early. The need of use stored C procedures implies that application is running under serious loads most likely there is modular structure present on Lua level (ie same shared library is loaded in different sources) thus we cache the loaded library and reuse it on next load attempts. To verify that cached library is up to day the module_cache engine test for file attributes (device, inode, size, modification time) on every load attempt. Since both `box.schema.func` and `box.lib` are using caching to minimize module loading procedure the pass-through caching scheme is implemented: - box.lib relies on module_cache engine for caching; - box.schema.func does snoop into box.lib hash table when attempt to load a new module, if module is present in box.lib hash then it simply referenced from there and added into own hash table; in case if module is not present then it loaded from the scratch and put into both hashes; - the module_reload action in box.schema.func invalidates module_cache or fill it if entry is not present. Closes #4642 Signed-off-by: Cyrill Gorcunov @TarantoolBot document Title: box.lib module Overview ======== `box.lib` module provides a way to create, delete and execute `C` procedures from shared libraries. Unlike `box.schema.func` methods the functions created with `box.lib` 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 ================ `box.lib.load(path) -> obj | error` ----------------------------------- Loads a module from `path` and return an object instance associate with the module, otherwise an error is thrown. The `path` should not end up with shared library 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. Example: ``` Lua -- Without error handling m = box.lib.load('path/to/library) -- With error handling m, err = pcall(box.lib.load, 'path/to/library') if err ~= nil then print(err) end ``` `module:unload() -> true | error` --------------------------------- Unloads a module. Returns `true` on success, otherwise an error is thrown. 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 = box.lib.load('path/to/library') -- -- do something with module -- m:unload() ``` If there are functions from this module referenced somewhere in other places of Lua code they still can be executed because the module continue sitting in memory until the last reference to it is closed. If the module become a target to the Lua's garbage collector then unload is called implicitly. module:load(name) -> obj | error` --------------------------------- Loads a new function with name `name` from the previously loaded `module` and return a callable object instance associated with the function. On failure an error is thrown. Possible errors: - IllegalParams: function name is either not supplied or not a string. - IllegalParams: attempt to load a function but module has been unloaded already. - ClientError: no such function in the module. - OutOfMemory: unable to allocate a function. Example: ``` Lua -- Load a module if not been loaded yet. m = box.lib.load('path/to/library') -- Load a function with the `foo` name from the module `m`. func = m:load('foo') ``` In case if there is no need for further loading of other functions from the same module then the module might be unloaded immediately. ``` Lua m = box.lib.load('path/to/library') func = m:load('foo') m:unload() ``` `function:unload() -> true | error` ----------------------------------- Unloads a function. Returns `true` on success, otherwise an error is thrown. Possible errors: - IllegalParams: function name is either not supplied or not a string. - IllegalParams: the function already unloaded. Example: ``` Lua m = box.lib.load('path/to/library') func = m:load('foo') -- -- do something with function and cleanup then -- func:unload() m:unload() ``` If the function become a target to the Lua's garbage collector then unload is called implicitly. Executing a loaded function =========================== Once function is loaded it can be executed as an 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 = box.lib.load('cfunc') cfunc_sum = m:load('cfunc_sum') ``` Once successfully loaded we can execute it. Lets call the `cfunc_sum` with wrong number of arguments ``` Lua cfunc_sum() | --- | - error: invalid argument count ``` We will see 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 sum of arguments will be printed out. ``` Lua cfunc_sum(1, 2) | --- | - 3 ``` The functions may return multiple results. For example a trivial echo function which prints arguments passed in. ``` Lua cfunc_echo(1,2,3) | --- | - 1 | - 2 | - 3 ``` Module and function caches ========================== Loading a module is relatively slow procedure because operating system needs to read the library, resolve its symbols and etc. Thus to speedup this procedure if the module is loaded for a first time we put it into an internal cache. If module is sitting in the cache already and new request to load comes in -- we simply reuse a previous copy. In case if module is updated on a storage device then on new load attempt we detect that file attributes (such as device number, inode, size, modification time) get changed and reload module from the scratch. Note that newly loaded module does not intersect with previously loaded modules, the continue operating with code previously read from cache. Thus if there is a need to update a module then all module instances should be unloaded (together with functions) and loaded again. Similar caching technique applied to functions -- only first function allocation cause symbol resolving, next ones are simply obtained from a function cache. --- changelogs/unreleased/add-box-lib.md | 4 + src/box/CMakeLists.txt | 1 + src/box/lua/init.c | 2 + src/box/lua/lib.c | 606 +++++++++++++++++++++++++++ src/box/lua/lib.h | 25 ++ src/box/lua/load_cfg.lua | 1 + test/box/misc.result | 1 + 7 files changed, 640 insertions(+) create mode 100644 changelogs/unreleased/add-box-lib.md create mode 100644 src/box/lua/lib.c create mode 100644 src/box/lua/lib.h diff --git a/changelogs/unreleased/add-box-lib.md b/changelogs/unreleased/add-box-lib.md new file mode 100644 index 000000000..3fe7a1b01 --- /dev/null +++ b/changelogs/unreleased/add-box-lib.md @@ -0,0 +1,4 @@ +## feature/core + +* Introduce `box.lib` module which allows to load and execute + C stored procedures on read-only nodes (gh-4642). diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index cc2e17e94..09f81afd8 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -196,6 +196,7 @@ add_library(box STATIC lua/call.c lua/cfg.cc lua/console.c + lua/lib.c lua/serialize_lua.c lua/tuple.c lua/slab.c diff --git a/src/box/lua/init.c b/src/box/lua/init.c index fbcdfb20b..3a6d60864 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/lib.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_lib_init(L); box_lua_slab_init(L); box_lua_index_init(L); box_lua_space_init(L); diff --git a/src/box/lua/lib.c b/src/box/lua/lib.c new file mode 100644 index 000000000..78aee37b5 --- /dev/null +++ b/src/box/lua/lib.c @@ -0,0 +1,606 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file. + */ + +#include +#include +#include + +#include "box/error.h" +#include "box/port.h" + +#include "assoc.h" +#include "lib.h" +#include "diag.h" +#include "module_cache.h" + +#include "lua/utils.h" + +/** + * Function descriptor. + */ +struct box_module_func { + /** C function to call. */ + struct module_func base; + /** Number of references. */ + int64_t refs; + /** Length of functon name in @a key. */ + size_t sym_len; + /** Length of @a key. */ + size_t len; + /** Function hash key. */ + char key[0]; +}; + +/** Function name to box_module_func hash. */ +static struct mh_strnptr_t *func_hash = NULL; + +/** A type to find a module from an object. */ +static const char *uname_lib = "tt_uname_box_lib"; + +/** A type to find a function from an object. */ +static const char *uname_func = "tt_uname_box_lib_func"; + +/** Get data associated with an object. */ +static void * +get_udata(struct lua_State *L, const char *uname) +{ + void **pptr = luaL_testudata(L, 1, uname); + return pptr != NULL ? *pptr : NULL; +} + +/** + * Get pointer associated with an object and clear it + * returning previously associated data. + */ +static void * +clear_udata(struct lua_State *L, const char *uname) +{ + void **pptr = luaL_testudata(L, 1, uname); + if (pptr != NULL) { + void *ptr = *pptr; + *pptr = NULL; + return ptr; + } + return NULL; +} + +/** Setup a new data and associate it with an object. */ +static void +new_udata(struct lua_State *L, const char *uname, void *ptr) +{ + *(void **)lua_newuserdata(L, sizeof(void *)) = ptr; + luaL_getmetatable(L, uname); + lua_setmetatable(L, -2); +} + +/** + * Helpers for function cache. + */ +static void * +cache_find(const char *str, size_t len) +{ + mh_int_t e = mh_strnptr_find_inp(func_hash, str, len); + if (e == mh_end(func_hash)) + return NULL; + return mh_strnptr_node(func_hash, e)->val; +} + +static int +cache_put(struct box_module_func *cf) +{ + const struct mh_strnptr_node_t nd = { + .str = cf->key, + .len = cf->len, + .hash = mh_strn_hash(cf->key, 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", + "box.lib: hash node"); + return -1; + } + return 0; +} + +static void +cache_del(struct box_module_func *cf) +{ + mh_int_t e = mh_strnptr_find_inp(func_hash, cf->key, cf->len); + if (e != mh_end(func_hash)) + mh_strnptr_del(func_hash, e, NULL); +} + +/** + * Load a module. + * + * This function takes a module path from the caller + * stack @a L and returns cached module instance or + * 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 throws an error. + */ +static int +lbox_module_load(struct lua_State *L) +{ + const char *msg_noname = "Expects box.lib.load(\'name\') " + "but no name passed"; + + if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) { + diag_set(IllegalParams, msg_noname); + return luaT_error(L); + } + + size_t name_len; + const char *name = lua_tolstring(L, 1, &name_len); + + if (name_len < 1) { + diag_set(IllegalParams, msg_noname); + return luaT_error(L); + } + + struct module *m = module_load(name, name_len); + if (m == NULL) + return luaT_error(L); + + new_udata(L, uname_lib, m); + return 1; +} + +/** + * Unload a module. + * + * Take a module object from the caller stack @a L and unload it. + * + * Possible errors: + * + * - IllegalParams: module is not supplied. + * - IllegalParams: the module is unloaded. + * + * @returns true on success or throwns an error. + */ +static int +lbox_module_unload(struct lua_State *L) +{ + if (lua_gettop(L) != 1) { + diag_set(IllegalParams, "Expects module:unload()"); + return luaT_error(L); + } + + struct module *m = clear_udata(L, uname_lib); + if (m == NULL) { + diag_set(IllegalParams, "The module is unloaded"); + return luaT_error(L); + } + module_unload(m); + + lua_pushboolean(L, true); + return 1; +} + +/** Handle __index request for a module object. */ +static int +lbox_module_index(struct lua_State *L) +{ + /* + * Process metamethods such as "module:load" first. + */ + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) + return 1; + + struct module *m = get_udata(L, uname_lib); + if (m == NULL) { + lua_pushnil(L); + return 1; + } + + const char *key = lua_tostring(L, 2); + if (key == NULL || !lua_isstring(L, 2)) { + diag_set(IllegalParams, + "Bad params, use __index()"); + return luaT_error(L); + } + + if (strcmp(key, "path") == 0) { + lua_pushstring(L, m->package); + return 1; + } + + /* + * Internal keys for debug only, not API. + */ + if (strcmp(key, "debug_refs") == 0) { + lua_pushnumber(L, m->refs); + return 1; + } else if (strcmp(key, "debug_ptr") == 0) { + char s[64]; + snprintf(s, sizeof(s), "%p", m); + lua_pushstring(L, s); + return 1; + } + return 0; +} + +/** Module representation for REPL (console). */ +static int +lbox_module_serialize(struct lua_State *L) +{ + struct module *m = get_udata(L, uname_lib); + if (m == NULL) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, 0, 1); + lua_pushstring(L, m->package); + lua_setfield(L, -2, "path"); + return 1; +} + +/** Collect a module. */ +static int +lbox_module_gc(struct lua_State *L) +{ + struct module *m = clear_udata(L, uname_lib); + if (m != NULL) + module_unload(m); + return 0; +} + +/** Increase reference to a function. */ +static void +box_module_func_ref(struct box_module_func *cf) +{ + assert(cf->refs >= 0); + ++cf->refs; +} + +/** Free function memory. */ +static void +box_module_func_delete(struct box_module_func *cf) +{ + assert(module_func_is_empty(&cf->base)); + TRASH(cf); + free(cf); +} + +/** Unreference a function and free if last one. */ +static void +box_module_func_unref(struct box_module_func *cf) +{ + assert(cf->refs > 0); + if (--cf->refs == 0) { + module_func_unload(&cf->base); + cache_del(cf); + box_module_func_delete(cf); + } +} + +/** Function name from a hash key. */ +static char * +box_module_func_name(struct box_module_func *cf) +{ + return &cf->key[cf->len - cf->sym_len]; +} + +/** + * Allocate a new function instance and resolve its address. + * + * @param m a module the function should be loaded from. + * @param key function hash key, ie "addr.module.foo". + * @param len length of @a key. + * @param sym_len function symbol name length, ie 3 for "foo". + * + * @returns function instance on success, NULL otherwise (diag is set). + */ +static struct box_module_func * +box_module_func_new(struct module *m, const char *key, size_t len, size_t sym_len) +{ + size_t size = sizeof(struct box_module_func) + len + 1; + struct box_module_func *cf = malloc(size); + if (cf == NULL) { + diag_set(OutOfMemory, size, "malloc", "cf"); + return NULL; + } + + cf->len = len; + cf->sym_len = sym_len; + cf->refs = 0; + + module_func_create(&cf->base); + memcpy(cf->key, key, len); + cf->key[len] = '\0'; + + if (module_func_load(m, box_module_func_name(cf), &cf->base) != 0) { + box_module_func_delete(cf); + return NULL; + } + + if (cache_put(cf) != 0) { + module_func_unload(&cf->base); + box_module_func_delete(cf); + return NULL; + } + + /* + * Each new function depends on module presence. + * Module will reside even if been unload + * explicitly after the function creation. + */ + box_module_func_ref(cf); + return cf; +} + +/** + * Load a function. + * + * This function takes a function name from the caller + * stack @a L and either returns a cached function or + * creates a new function object. + * + * Possible errors: + * + * - IllegalParams: function name 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 throws an error. + */ +static int +lbox_module_load_func(struct lua_State *L) +{ + const char *method = "function = module:load"; + const char *fmt_noname = "Expects %s(\'name\') but no name passed"; + + if (lua_gettop(L) != 2 || !lua_isstring(L, 2)) { + diag_set(IllegalParams, fmt_noname, method); + return luaT_error(L); + } + + struct module *m = get_udata(L, uname_lib); + if (m == NULL) { + const char *fmt = + "Expects %s(\'name\') but not module object passed"; + diag_set(IllegalParams, fmt, method); + return luaT_error(L); + } + + size_t sym_len; + const char *sym = lua_tolstring(L, 2, &sym_len); + const size_t max_sym_len = 512; + + if (sym_len < 1) { + diag_set(IllegalParams, fmt_noname, method); + return luaT_error(L); + } else if (sym_len > max_sym_len) { + diag_set(IllegalParams, "Symbol \'%s\' is too long (max %zd)", + sym, max_sym_len); + return luaT_error(L); + } + + /* + * Functions are bound to a module symbols, thus + * since the hash is global it should be unique + * per module. The symbol (function name) is the + * last part of the hash key. + * + * The key's buffer should be big enough to keep + * the longest package path plus symbol name and + * the pointer. + */ + char key[PATH_MAX + 2 * max_sym_len]; + snprintf(key, sizeof(key), "%p.%s.%s", + (void *)m, m->package, sym); + size_t len = strlen(key); + + struct box_module_func *cf = cache_find(key, len); + if (cf == NULL) { + cf = box_module_func_new(m, key, len, sym_len); + if (cf == NULL) + return luaT_error(L); + } else { + box_module_func_ref(cf); + } + + new_udata(L, uname_func, cf); + return 1; +} + +/** + * Unload a function. + * + * Take a function object from the caller stack @a L and unload it. + * + * Possible errors: + * + * - IllegalParams: the function is not supplied. + * - IllegalParams: the function already unloaded. + * + * @returns true on success or throwns an error. + */ +static int +lbox_func_unload(struct lua_State *L) +{ + if (lua_gettop(L) != 1) { + diag_set(IllegalParams, "Expects function:unload()"); + return luaT_error(L); + } + + struct box_module_func *cf = clear_udata(L, uname_func); + if (cf == NULL) { + diag_set(IllegalParams, "The function is unloaded"); + return luaT_error(L); + } + box_module_func_unref(cf); + + lua_pushboolean(L, true); + return 1; +} + +/** Handle __index request for a function object. */ +static int +lbox_func_index(struct lua_State *L) +{ + /* + * Process metamethods such as "func:unload" first. + */ + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) + return 1; + + struct box_module_func *cf = get_udata(L, uname_func); + if (cf == NULL) { + lua_pushnil(L); + return 1; + } + + const char *key = lua_tostring(L, 2); + if (key == NULL || !lua_isstring(L, 2)) { + diag_set(IllegalParams, + "Bad params, use __index()"); + return luaT_error(L); + } + + if (strcmp(key, "name") == 0) { + lua_pushstring(L, box_module_func_name(cf)); + return 1; + } + + /* + * Internal keys for debug only, not API. + */ + if (strcmp(key, "debug_refs") == 0) { + lua_pushnumber(L, cf->refs); + return 1; + } else if (strcmp(key, "debug_key") == 0) { + lua_pushstring(L, cf->key); + return 1; + } else if (strcmp(key, "debug_module_ptr") == 0) { + char s[64]; + snprintf(s, sizeof(s), "%p", cf->base.module); + lua_pushstring(L, s); + return 1; + } else if (strcmp(key, "debug_module_refs") == 0) { + lua_pushnumber(L, cf->base.module->refs); + return 1; + } + return 0; +} + +/** Function representation for REPL (console). */ +static int +lbox_func_serialize(struct lua_State *L) +{ + struct box_module_func *cf = get_udata(L, uname_func); + if (cf == NULL) { + lua_pushnil(L); + return 1; + } + + lua_createtable(L, 0, 1); + lua_pushstring(L, box_module_func_name(cf)); + lua_setfield(L, -2, "name"); + return 1; +} + +/** Collect a function. */ +static int +lbox_func_gc(struct lua_State *L) +{ + struct box_module_func *cf = clear_udata(L, uname_func); + if (cf != NULL) + box_module_func_unref(cf); + return 0; +} + + +/** Call a function by its name from the Lua code. */ +static int +lbox_func_call(struct lua_State *L) +{ + struct box_module_func *cf = get_udata(L, uname_func); + if (cf == NULL) { + diag_set(IllegalParams, "The function is unloaded"); + return luaT_error(L); + } + + lua_State *args_L = luaT_newthread(tarantool_L); + if (args_L == NULL) + return luaT_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_func_call(&cf->base, &args, &ret) != 0) { + port_destroy(&args); + return luaT_error(L); + } + + int top = lua_gettop(L); + port_dump_lua(&ret, L, true); + int cnt = lua_gettop(L) - top; + + port_destroy(&ret); + port_destroy(&args); + + return cnt; +} + +void +box_lua_lib_init(struct lua_State *L) +{ + func_hash = mh_strnptr_new(); + if (func_hash == NULL) + panic("box.lib: Can't allocate func hash table"); + + static const struct luaL_Reg top_methods[] = { + { "load", lbox_module_load }, + { NULL, NULL }, + }; + luaL_register(L, "box.lib", top_methods); + lua_pop(L, 1); + + static const struct luaL_Reg lbox_module_methods[] = { + { "unload", lbox_module_unload }, + { "load", lbox_module_load_func }, + { "__index", lbox_module_index }, + { "__serialize", lbox_module_serialize }, + { "__gc", lbox_module_gc }, + { NULL, NULL }, + }; + luaL_register_type(L, uname_lib, lbox_module_methods); + + static const struct luaL_Reg lbox_func_methods[] = { + { "unload", lbox_func_unload }, + { "__index", lbox_func_index }, + { "__serialize", lbox_func_serialize }, + { "__gc", lbox_func_gc }, + { "__call", lbox_func_call }, + { NULL, NULL }, + }; + luaL_register_type(L, uname_func, lbox_func_methods); +} diff --git a/src/box/lua/lib.h b/src/box/lua/lib.h new file mode 100644 index 000000000..a86691b42 --- /dev/null +++ b/src/box/lua/lib.h @@ -0,0 +1,25 @@ +/* + * 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 Lua box.lib. + * + * @param L Lua state where to register. + */ +void +box_lua_lib_init(struct lua_State *L); + +#if defined(__cplusplus) +} +#endif /* defined(__plusplus) */ diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index 44bb95ed1..374524973 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -597,6 +597,7 @@ local box_cfg_guard_whitelist = { error = true; internal = true; index = true; + lib = true; session = true; tuple = true; runtime = true; diff --git a/test/box/misc.result b/test/box/misc.result index e18a46e02..59fc60a22 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -85,6 +85,7 @@ t - info - internal - is_in_txn + - lib - on_commit - on_rollback - once -- 2.30.2