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 6FDD9645EA; Mon, 18 Jan 2021 23:39:36 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 6FDD9645EA DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1611002376; bh=mCWstS9doPyOD/74YBIrRTUzK1lVLl4B2KLjOd9dr/4=; 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=RZ+3PqwCOZhR4jTLvF43f59smdjvzeWZmolnuJelPRacCH5rnZ9yzmwcjbbqH6GYH jzXsJHxxzD+cPwBA1A0WN94sNZ9ByNR5SIHr/sEaybFC1hUggyEGMfs+fQxMNARa8V 77fPfbjXFjRO1VlLECRvSa65rM55uIn741PW0hQc= Received: from mail-lf1-f45.google.com (mail-lf1-f45.google.com [209.85.167.45]) (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 982ADBD3F8 for ; Mon, 18 Jan 2021 23:37:26 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 982ADBD3F8 Received: by mail-lf1-f45.google.com with SMTP id v24so19289629lfr.7 for ; Mon, 18 Jan 2021 12:37:26 -0800 (PST) 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=f0gd0ZptqsUkmuBs0wWMOjmrkfuCCd4AduBdJDpcRdA=; b=YHgAg2B2dTR4r50ulBx0RuJtg90a03HZw43l9bnlGFLbzOiV5AWTF8hXhpQZ6c/y4k 9m3OrXxwMOEBDa/RUCA6NLKUeI7ak+2Q0w8ApdY66hPo0ZOFVBTAuR5cJETOUNS6A5tG JylHSWIvqxIP84IHC4FBDAQ5u1o6Ur2IABn6cNdDGfXOwXyDq8WQZ5eLWTq9+4gm8JPZ igdfSvS0hN1oCWyidTBOZAmpN/cJApq2pq0CK6rFTa8c3e3czc9CvlsXGxefnmBclTQE bE5ND6WR0L9hyW2L9vHobUKMXdj9ZVxU2IVSWf5Fh/bHQdiBL+9lylLBmjMjM7gSXnBb ryEg== X-Gm-Message-State: AOAM5311t6OSsiHmGBpELmbtUL7jtOKrowjiG8o/L7XJMBzkAW60MAf/ RzCRGK/K1bBnVy+jNiuHkvbZaZ64r/I= X-Google-Smtp-Source: ABdhPJy2+bWFf+VKkEX0CVs0yYTasvibUBjO8/6dVC12/w+zt7Lo8ixYpZen1IOlcT7Aag17brh+1w== X-Received: by 2002:a05:6512:1315:: with SMTP id x21mr353604lfu.511.1611002244830; Mon, 18 Jan 2021 12:37:24 -0800 (PST) Received: from grain.localdomain ([5.18.91.94]) by smtp.gmail.com with ESMTPSA id v23sm1781457ljg.97.2021.01.18.12.37.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 18 Jan 2021 12:37:23 -0800 (PST) Received: by grain.localdomain (Postfix, from userid 1000) id 6E1D2560256; Mon, 18 Jan 2021 23:35:59 +0300 (MSK) To: tml Date: Mon, 18 Jan 2021 23:35:55 +0300 Message-Id: <20210118203556.281700-8-gorcunov@gmail.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20210118203556.281700-1-gorcunov@gmail.com> References: <20210118203556.281700-1-gorcunov@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH v12 7/8] box/cmod: implement cmod Lua 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 "cmod" lua module. Fixes #4642 Signed-off-by: Cyrill Gorcunov @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 +#include + +#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, )"); + 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, )"); + 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