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 B0DC38205E; Sun, 24 Jan 2021 19:29:39 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org B0DC38205E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1611505779; bh=gXWuZb1iJ9jjnady1mEvBmI8Nk8cM1Z7jUUxxnZZ0E8=; h=To:References:Date:In-Reply-To:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=sut0BuNXW//Mqd4LExOwtLtfXGNlDm4eUw0xFrgQcYg+mddMcu/vubNzGL1jU+Mko GUiZV62SARhDaotxKwbgBePW0ITUKGrQfXcodYahRwoeG0HkvfBOEvZwuE7Tz5p5yX z2u0vHH88nJZyhZ63YhpLxwbT79eQDDcOkAZf0z4= Received: from smtpng3.m.smailru.net (smtpng3.m.smailru.net [94.100.177.149]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 441BF57E4A for ; Sun, 24 Jan 2021 19:28:29 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 441BF57E4A Received: by smtpng3.m.smailru.net with esmtpa (envelope-from ) id 1l3iFY-0003Oa-Fm; Sun, 24 Jan 2021 19:28:29 +0300 To: Cyrill Gorcunov , tml References: <20210118203556.281700-1-gorcunov@gmail.com> <20210118203556.281700-8-gorcunov@gmail.com> Message-ID: <5703e5a2-47b1-bcde-7cd8-7bca6e9c606f@tarantool.org> Date: Sun, 24 Jan 2021 17:28:27 +0100 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:78.0) Gecko/20100101 Thunderbird/78.6.1 MIME-Version: 1.0 In-Reply-To: <20210118203556.281700-8-gorcunov@gmail.com> Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD9F0E84CC1954AA438427011AB64E0AE2F78B5E6EBC5DB03F600894C459B0CD1B91C1216967B9DBF6B540F846346BAEED1E677AB1C9F20D3C81DFC3049464E9EA7 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE79961E86438F5BDAEEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F790063775FF68B4B43662428638F802B75D45FF5571747095F342E8C7A0BC55FA0FE5FC520051DBA2F12A1037CE707C92EE0B1A7DD238A010E7378A389733CBF5DBD5E913377AFFFEAFD269A417C69337E82CC2CC7F00164DA146DAFE8445B8C89999729449624AB7ADAF37F6B57BC7E64490611E7FA7ABCAF51C92A417C69337E82CC2CC7F00164DA146DA6F5DAA56C3B73B23C77107234E2CFBA567F23339F89546C55F5C1EE8F4F765FC9BFB91CAEB05C77775ECD9A6C639B01BBD4B6F7A4D31EC0BC0CAF46E325F83A522CA9DD8327EE4930A3850AC1BE2E735E5B2F3A2B87CD4C8C4224003CC836476C0CAF46E325F83A50BF2EBBBDD9D6B0FECB2555BB02FD5A93B503F486389A921A5CC5B56E945C8DA X-C1DE0DAB: 0D63561A33F958A5A6E28438CAD4357CEE60E896EF0A0A6DBE4E1F89DC54611ED59269BC5F550898D99A6476B3ADF6B47008B74DF8BB9EF7333BD3B22AA88B938A852937E12ACA75448CF9D3A7B2C848410CA545F18667F91A7EA1CDA0B5A7A0 X-C8649E89: 4E36BF7865823D7055A7F0CF078B5EC49A30900B95165D342C0B628602DFD0BC6EF032FEF6561E67B1826CDD1E919391BFB3B2E4387030AAFB5651465EDF2F2A1D7E09C32AA3244CB7C3430E642AF78AF676704000D1BCA83E8609A02908F271729B2BEF169E0186 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojEcKN7r9rK/2swZZ/q1GCDQ== X-Mailru-Sender: 689FA8AB762F73936BC43F508A06382248CE4113C3478C5BC3F13574917378013841015FED1DE5223CC9A89AB576DD93FB559BB5D741EB963CF37A108A312F5C27E8A8C3839CE0E267EA787935ED9F1B X-Mras: Ok Subject: Re: [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: Vladislav Shpilevoy via Tarantool-patches Reply-To: Vladislav Shpilevoy Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Thanks for the patch! See 21 comments below. On 18.01.2021 21:35, Cyrill Gorcunov wrote: > 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` 1. I don't like it, but I must say: according to one another change in Lua guidelines now we must again throw exceptions from all new functions present on the board instead of returning errors. You can try to raise it in the chat if you want. > ---------------------------------------------- > > 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. 2. What happens to the existing function objects on reload()? How is reload different from unload() + load()? > 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. 3. What if the function is not found? > 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. 4. Is this unloaded automatically, when the function object is GCed? > 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. 5. What happens when I do multireturn? > 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; 6. This member is never used. I removed it and nothing changed. ==================== @@ -31,10 +31,6 @@ struct cmod_func { * 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. */ @@ -231,12 +227,11 @@ cmod_func_free(struct cmod_func *cf) * * @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) +cmod_func_new(const char *name, size_t len) { const ssize_t cf_size = sizeof(struct cmod_func); size_t size = cf_size + len + 1; @@ -250,7 +245,6 @@ cmod_func_new(const char *name, size_t len, size_t func_name_len) 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); @@ -309,7 +303,7 @@ lcmod_func_load(struct lua_State *L) cf = cmod_func_find(name, len); if (cf == NULL) { - cf = cmod_func_new(name, len, strlen(func_name)); + cf = cmod_func_new(name, len); if (cf == NULL) return luaT_push_nil_and_error(L); ==================== > + /** > + * 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"; 7. Please, move global declarations to the beginning of the file so as it would be easier to find them, and to be consistent with other files. > + > +static struct cmod_func * > +cmod_get_func_handle(struct lua_State *L, bool mandatory) 8. According to the code style, flags must have 'is' prefix or a similar one like 'has', 'does', etc. Here it should be 'is_mandatory'. But a better idea - just drop it. The single place where it is not mandatory is GC handler. Another option - introduce separate get() and check(). Get() only returns it without checking for NULL. Check() will ensure it is not NULL and set the error otherwise. Also the function takes a Lua state, so it seems it must have lcmod prefix, not cmod. According to the naming convention you established in this file. > +{ > + 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); > +} 9. Amount of boilerplate code and double checks of the same conditions you need just to push some udata is terrifying, tbh. For instance, take lcmod_func_handle_call(). It calls cmod_get_func_handle() and checks result for NULL. This one calls cmod_get_handle() and also checks result for NULL. This one calls luaL_testudata() and also checks result for NULL. You did 3 NULL checks which could be one, if you would have a set of simpler functions working directly with luaL_testudata(). 10. Why do you have 'handle' word in each function name? For example, how 'handle_call' is any different from just 'call' in scope of code in this file? Or how is 'cmod_setup_func' different from 'cmod_setup_func_handle'? It seems that by 'handle' you try not to clash names with the non-Lua part of cmod. But you should do it via prefixes, not via suffixes. lcmod instead of cmod. > + > +/** > + * 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) 11. The function must be static. > +{ > + 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) 12. This whole cmod and lcmod API looks extremely inconsistent and complex. Here are the functions you have (not counting the boilerplate code above): // Find a function in the cache, ok. cmod_func_find(name) // It is called 'delete'. But it does not do the // delete. Moreover, it also decreases 'load_count', // which is super surprising. What does it have to do // with loads? You don't call module_sym_unload() here, // but decrease load_count. cmod_func_del(f) // Ok, this looks like it should add the function to the // hash. But wait, it also increases load_count, and does // not call module_sym_load()! WTF? cmod_func_add(f) // It should free the memory, right? But it calls // module_sym_unload(). Why!? Also, we have a code style // agreement, that function freeing the memory should // have suffix 'delete', not 'free'. As soon as you have // 'new'. cmod_func_free(f) // Fine, it should allocate a new function object, right? // But it also calls module_sym_load()! And does not increase // load_count! I don't understand. It seems like the names // of these functions has very little to do with what they are // really doing. The only way to make them work is to call them // in some special sequence, in which a next function fixes // leftovers of previous functions. cmod_func_new(name) This must be seriously redesigned. Starting from the function names. Here is an option: // Find the function in the hash. If found, increase load_count // and return. Otherwise allocate a new function object, fill its // members, do the load, and make load_count = 1. Add to the hash. cmod_func_load(name) // Decrease load_count. If became 0, remove from the hash, // unload mod_sym, and free the memory. cmod_func_unload(f) I don't really see why would you need the other functions. These 2 are going to be extra simple anyway. Module_cache API for module objects is literally that simple - load, unload, reload. Why do you need so many for cmod, especially with so unclear behaviour? > +{ > + 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) 13. I already proposed an option how to rework cmod API above, but still will leave some comments alongside. Why the heck 'add' function increases load_count? > + 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; 14. Why so complex? Just add it to the hash when it is created. This function is used in a single place, where you already do the check if it exists. > +} > + > +/** > + * 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); 15. Worth adding load_count == 0 assertion. Or rather do the unload where it belongs - where load_count becomes 0. > + 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) { 16. You load, and don't increase load_count. Does not it look contradictory to you? > + 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); 17. Why couldn't you just inline 'fmt' string? The current version is not any shorter nor easier to read. The same in many other places. > + 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); 18. Or you could pass package name and function name to the constructor and copy them right to the allocated memory, without double copying via the static buffer. > + 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) { 19. From this code it looks like you can fail addition of an already existing function and you just delete it. I know it can't happen, but the code says the opposite, and it is confusing. Another reason why 'add' is a bad name - it seems like it should fail when added second time due to being already added. > + 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++; 20. The fact that you need to touch reference counter of an object from a different subsystem should make you start to question sanity of the API. Why module_load() does not do the increase, and module_unload() - decrease? > + return 1; > +}> + > +/** > + * 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); 21. Since you decided to use createtable instead of newtable, I suggest you to fill the proper parameters, which should be (0, 1) in this case I think. > + lua_pushstring(L, m->package); > + lua_setfield(L, -2, "path"); > + > + return 1; > +}