diff --git a/src/box/box.cc b/src/box/box.cc index dc83b513aeebad7b9f6e3ad5a563a84429b41d4d..784f20e33ec4e3451e3f362781186d00a487a2bc 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -68,6 +68,7 @@ #include "checkpoint.h" #include "systemd.h" #include "call.h" +#include "func.h" static char status[64] = "unknown"; @@ -1214,6 +1215,7 @@ box_free(void) replication_free(); user_cache_free(); schema_free(); + module_free(); tuple_free(); port_free(); #endif @@ -1403,6 +1405,8 @@ box_cfg_xc(void) gc_init(); engine_init(); + if (module_init() != 0) + diag_raise(); schema_init(); replication_init(); port_init(); diff --git a/src/box/call.cc b/src/box/call.cc index c8fbf3d33c05c100722ef07a67859d4d37f13944..b2706c1e2c75b34d5c680bf2e0aa2ccb03db0f38 100644 --- a/src/box/call.cc +++ b/src/box/call.cc @@ -85,6 +85,7 @@ box_c_call(struct func *func, struct call_request *request, struct obuf *out) /* Call function from the shared library */ int rc = func_call(func, &ctx, request->args, request->args_end); + func = NULL; /* May be deleted by DDL */ if (rc != 0) { if (diag_last_error(&fiber()->diag) == NULL) { /* Stored procedure forget to set diag */ @@ -128,6 +129,18 @@ box_c_call(struct func *func, struct call_request *request, struct obuf *out) return -1; } +int +box_func_reload(const char *name) +{ + size_t name_len = strlen(name); + struct func *func = access_check_func(name, name_len); + if (func->def->language != FUNC_LANGUAGE_C || func->func == NULL) + return 0; /* Nothing to do */ + if (func_reload(func) == 0) + return 0; + return -1; +} + void box_process_call(struct call_request *request, struct obuf *out) { diff --git a/src/box/call.h b/src/box/call.h index b44a696f1c1d235f219d4210b9ebfbdbfccb0278..9e105087a983c46a208fe7d9e23a58f840c3fb8c 100644 --- a/src/box/call.h +++ b/src/box/call.h @@ -33,6 +33,16 @@ #include <stdint.h> +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ +int +box_func_reload(const char *name); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ + struct obuf; struct box_function_ctx { diff --git a/src/box/func.c b/src/box/func.c index b17a00583ea942e35846f8e4d4270ac04a148ef4..2cc276f9cdb8e993cf78ea363cfea6e57dcd6c2e 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -32,6 +32,8 @@ #include <dlfcn.h> +#include "assoc.h" + #include "lua/utils.h" /** @@ -102,7 +104,14 @@ luaT_module_find(lua_State *L) if (lua_isnil(L, -1)) return luaL_error(L, "module not found in package.cpath"); - snprintf(ctx->path, ctx->path_len, "%s", lua_tostring(L, -1)); + /* Convert path to absolute */ + char resolved[PATH_MAX]; + if (realpath(lua_tostring(L, -1), resolved) == NULL) { + diag_set(SystemError, "realpath"); + return luaT_error(L); + } + + snprintf(ctx->path, ctx->path_len, "%s", resolved); return 0; } @@ -133,6 +142,224 @@ module_find(const char *package, const char *package_end, char *path, return 0; } +static struct mh_strnptr_t *modules = NULL; + +static void +module_gc(struct module *module); + +int +module_init(void) +{ + modules = mh_strnptr_new(); + if (modules == NULL) { + diag_set(OutOfMemory, sizeof(*modules), "malloc", + "modules hash table"); + return -1; + } + return 0; +} + +void +module_free(void) +{ + while (mh_size(modules) > 0) { + mh_int_t i = mh_first(modules); + struct module *module = + (struct module *) mh_strnptr_node(modules, i)->val; + /* Can't delete modules if they have active calls */ + module_gc(module); + } + mh_strnptr_delete(modules); +} + +/** + * Look up a module in the modules cache. + */ +static struct module * +module_cache_find(const char *name, const char *name_end) +{ + mh_int_t i = mh_strnptr_find_inp(modules, name, name_end - name); + if (i == mh_end(modules)) + return NULL; + return (struct module *)mh_strnptr_node(modules, i)->val; +} + +/** + * Save module to the module cache. + */ +static inline int +module_cache_put(const char *name, const char *name_end, struct module *module) +{ + size_t name_len = name_end - name; + uint32_t name_hash = mh_strn_hash(name, name_len); + const struct mh_strnptr_node_t strnode = { + name, name_len, name_hash, module}; + + if (mh_strnptr_put(modules, &strnode, NULL, NULL) == mh_end(modules)) { + diag_set(OutOfMemory, sizeof(strnode), "malloc", "modules"); + return -1; + } + return 0; +} + +/** + * Delete a module from the module cache + */ +static void +module_cache_del(const char *name, const char *name_end) +{ + mh_int_t i = mh_strnptr_find_inp(modules, name, name_end - name); + if (i == mh_end(modules)) + return; + mh_strnptr_del(modules, i, NULL); +} + +/* + * Load a dso. + * Create a new symlink based on temporary directory and try to + * load via this symink to load a dso twice for cases of a function + * reload. + */ +static struct module * +module_load(const char *package, const char *package_end) +{ + char path[PATH_MAX]; + if (module_find(package, package_end, path, sizeof(path)) != 0) + return NULL; + + struct module *module = (struct module *) malloc(sizeof(*module)); + if (module == NULL) { + diag_set(OutOfMemory, sizeof(struct module), "malloc", + "struct module"); + return NULL; + } + rlist_create(&module->funcs); + module->calls = 0; + module->is_unloading = false; + char dir_name[] = "/tmp/tntXXXXXX"; + if (mkdtemp(dir_name) == NULL) { + diag_set(SystemError, "failed to create unique dir name"); + goto error; + } + char load_name[PATH_MAX + 1]; + snprintf(load_name, sizeof(load_name), "%s/%*s", dir_name, + (int)(package_end - package), package); + if (symlink(path, load_name) < 0) { + diag_set(SystemError, "failed to create dso link"); + goto error; + } + module->handle = dlopen(load_name, RTLD_NOW | RTLD_LOCAL); + if (unlink(load_name) != 0) + say_warn("failed to unlink dso link %s", load_name); + if (rmdir(dir_name) != 0) + say_warn("failed to delete temporary dir %s", dir_name); + if (module->handle == NULL) { + int package_len = (int) (package_end - package_end); + diag_set(ClientError, ER_LOAD_MODULE, package_len, + package, dlerror()); + goto error; + } + + return module; +error: + free(module); + return NULL; +} + +static void +module_delete(struct module *module) +{ + dlclose(module->handle); + TRASH(module); + free(module); +} + +/* + * Check if a dso is unused and can be closed. + */ +static void +module_gc(struct module *module) +{ + if (!module->is_unloading || !rlist_empty(&module->funcs) || + module->calls != 0) + return; + module_delete(module); +} + +/* + * Import a function from the module. + */ +static box_function_f +module_sym(struct module *module, const char *name) +{ + box_function_f f = (box_function_f)dlsym(module->handle, name); + if (f == NULL) { + diag_set(ClientError, ER_LOAD_FUNCTION, name, dlerror()); + return NULL; + } + return f; +} + +/* + * Reload a dso. + */ +int +module_reload(const char *package, const char *package_end, struct module **module) +{ + struct module *old_module = module_cache_find(package, package_end); + if (old_module == NULL) { + /* Module wasn't loaded - do nothing. */ + *module = NULL; + return 0; + } + + struct module *new_module = module_load(package, package_end); + if (new_module == NULL) + return -1; + + struct func *func, *tmp_func; + rlist_foreach_entry_safe(func, &old_module->funcs, item, tmp_func) { + struct func_name name; + func_split_name(func->def->name, &name); + func->func = module_sym(new_module, name.sym); + if (func->func == NULL) + goto restore; + func->module = new_module; + rlist_move(&new_module->funcs, &func->item); + } + module_cache_del(package, package_end); + if (module_cache_put(package, package_end, new_module) != 0) + goto restore; + old_module->is_unloading = true; + module_gc(old_module); + *module = new_module; + return 0; +restore: + /* + * Some old-dso func can't be load from new module, restore old + * functions. + */ + do { + struct func_name name; + func_split_name(func->def->name, &name); + func->func = module_sym(old_module, name.sym); + if (func->func == NULL) { + /* + * Something strange was happen, an early loaden + * function was not found in an old dso. + */ + panic("Can't restore module function, " + "server state is inconsistent"); + } + func->module = old_module; + rlist_move(&old_module->funcs, &func->item); + } while (func != rlist_first_entry(&old_module->funcs, + struct func, item)); + assert(rlist_empty(&new_module->funcs)); + module_delete(new_module); + return -1; +} + struct func * func_new(struct func_def *def) { @@ -157,16 +384,23 @@ func_new(struct func_def *def) */ func->owner_credentials.auth_token = BOX_USER_MAX; /* invalid value */ func->func = NULL; - func->dlhandle = NULL; + func->module = NULL; return func; } static void func_unload(struct func *func) { - if (func->dlhandle) - dlclose(func->dlhandle); - func->dlhandle = NULL; + if (func->module) { + rlist_del(&func->item); + if (rlist_empty(&func->module->funcs)) { + struct func_name name; + func_split_name(func->def->name, &name); + module_cache_del(name.package, name.package_end); + } + module_gc(func->module); + } + func->module = NULL; func->func = NULL; } @@ -182,22 +416,34 @@ func_load(struct func *func) struct func_name name; func_split_name(func->def->name, &name); - char path[PATH_MAX]; - if (module_find(name.package, name.package_end, path, sizeof(path))) - return -1; + struct module *module = module_cache_find(name.package, + name.package_end); + if (module == NULL) { + /* Try to find loaded module in the cache */ + module = module_load(name.package, name.package_end); + if (module == NULL) + diag_raise(); + if (module_cache_put(name.package, name.package_end, module)) { + module_delete(module); + diag_raise(); + } + } - func->dlhandle = dlopen(path, RTLD_NOW | RTLD_LOCAL); - if (func->dlhandle == NULL) { - int package_len = (int) (name.package_end - name.package_end); - diag_set(ClientError, ER_LOAD_MODULE, package_len, - name.package, dlerror()); - diag_log(); + func->func = module_sym(module, name.sym); + if (func->func == NULL) return -1; - } - func->func = (box_function_f) dlsym(func->dlhandle, name.sym); - if (func->func == NULL) { - diag_set(ClientError, ER_LOAD_FUNCTION, func->def->name, - dlerror()); + func->module = module; + rlist_add(&module->funcs, &func->item); + return 0; +} + +int +func_reload(struct func *func) +{ + struct func_name name; + func_split_name(func->def->name, &name); + struct module *module = NULL; + if (module_reload(name.package, name.package_end, &module) != 0) { diag_log(); return -1; } @@ -213,7 +459,15 @@ func_call(struct func *func, box_function_ctx_t *ctx, const char *args, return -1; } - return func->func(ctx, args, args_end); + /* Module can be changed after function reload. */ + struct module *module = func->module; + if (module != NULL) + ++module->calls; + int rc = func->func(ctx, args, args_end); + if (module != NULL) + --module->calls; + module_gc(module); + return rc; } void diff --git a/src/box/func.h b/src/box/func.h index 144b031f67feca2bccb297e87e6f67610e0a796a..b152a2285b15ef7b419de60ccc59b54a45ba2716 100644 --- a/src/box/func.h +++ b/src/box/func.h @@ -30,17 +30,42 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include <small/rlist.h> + #include "key_def.h" #if defined(__cplusplus) extern "C" { #endif /* defined(__cplusplus) */ +/** + * Dynamic shared module. + */ +struct module { + /** Module dlhandle. */ + void *handle; + /** List of imported functions. */ + struct rlist funcs; + /** Count of active calls. */ + size_t calls; + /** True if module is being unloaded. */ + bool is_unloading; +}; + /** * Stored function. */ struct func { struct func_def *def; + /** + * Anchor for module membership. + */ + struct rlist item; /** * For C functions, the body of the function. */ @@ -49,7 +74,7 @@ struct func { * Each stored function keeps a handle to the * dynamic library for the C callback. */ - void *dlhandle; + struct module *module; /** * Authentication id of the owner of the function, * used for set-user-id functions. @@ -61,6 +86,18 @@ struct func { struct access access[BOX_USER_MAX]; }; +/** + * Initialize modules subsystem. + */ +int +module_init(void); + +/** + * Cleanup modules subsystem. + */ +void +module_free(void); + struct func * func_new(struct func_def *def); @@ -77,9 +114,11 @@ int func_call(struct func *func, box_function_ctx_t *ctx, const char *args, const char *args_end); +int +func_reload(struct func *func); + #if defined(__cplusplus) } /* extern "C" */ - #endif /* defined(__cplusplus) */ #endif /* TARANTOOL_BOX_FUNC_H_INCLUDED */ diff --git a/src/box/lua/call.c b/src/box/lua/call.c index f0bc7863deb056f2ec3a6580ebaa17b9058ccf02..0063707e2bfaa82c8f5e80a9ad01dc94315f6d6b 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -414,8 +414,18 @@ box_lua_eval(struct call_request *request, struct obuf *out) return box_process_lua(request, out, execute_lua_eval); } +static int +lbox_func_reload(lua_State *L) +{ + const char *name = luaL_optstring(L, 1, "function name"); + if (box_func_reload(name) != 0) + return luaT_error(L); + return 0; +} + static const struct luaL_Reg boxlib_internal[] = { {"call_loadproc", lbox_call_loadproc}, + {"func_reload", lbox_func_reload}, {NULL, NULL} }; diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 62964ee3469a2ffce04049d27774d8ff97a0de37..92c29ea7c21f15eaa6f39ee8c228feaa6fbeb736 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1280,6 +1280,8 @@ function box.schema.func.exists(name_or_id) return tuple ~= nil end +box.schema.func.reload = internal.func_reload + box.schema.user = {} box.schema.user.password = function(password) diff --git a/test/box/CMakeLists.txt b/test/box/CMakeLists.txt index ebacc96adb27476b905d0fccd8b5c024868fcc0a..4f859d1ce795bc7834cb5c65312a93fa0fbd6011 100644 --- a/test/box/CMakeLists.txt +++ b/test/box/CMakeLists.txt @@ -1,4 +1,6 @@ include_directories(${MSGPUCK_INCLUDE_DIRS}) build_module(function1 function1.c) -target_link_libraries(function1 ${MSGPUCK_LIBRARIES}) +build_module(reload1 reload1.c) +build_module(reload2 reload2.c) +target_link_libraries(function1 reload1 reload2 ${MSGPUCK_LIBRARIES}) build_module(tuple_bench tuple_bench.c) diff --git a/test/box/func_reload.result b/test/box/func_reload.result new file mode 100644 index 0000000000000000000000000000000000000000..00cde1d3bd06a980865d2dcaf607385b15193942 --- /dev/null +++ b/test/box/func_reload.result @@ -0,0 +1,250 @@ +fio = require('fio') +--- +... +net = require('net.box') +--- +... +fiber = require('fiber') +--- +... +ext = (jit.os == "OSX" and "dylib" or "so") +--- +... +build_path = os.getenv("BUILDDIR") +--- +... +reload1_path = build_path..'/test/box/reload1.'..ext +--- +... +reload2_path = build_path..'/test/box/reload2.'..ext +--- +... +reload_path = "reload."..ext +--- +... +_ = fio.unlink(reload_path) +--- +... +c = net.connect(os.getenv("LISTEN")) +--- +... +box.schema.func.create('reload.foo', {language = "C"}) +--- +... +box.schema.user.grant('guest', 'execute', 'function', 'reload.foo') +--- +... +_ = box.schema.space.create('test') +--- +... +_ = box.space.test:create_index('primary', {parts = {1, "integer"}}) +--- +... +box.schema.user.grant('guest', 'read,write', 'space', 'test') +--- +... +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload1_path, reload_path) +--- +- true +... +--check not fail on non-load func +box.schema.func.reload("reload.foo") +--- +... +-- test of usual case reload. No hanging calls +box.space.test:insert{0} +--- +- [0] +... +c:call("reload.foo", {1}) +--- +- [] +... +box.space.test:delete{0} +--- +- [0] +... +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload2_path, reload_path) +--- +- true +... +box.schema.func.reload("reload.foo") +--- +... +c:call("reload.foo") +--- +- [] +... +box.space.test:select{} +--- +- - [-1] + - [0] + - [1] +... +box.space.test:truncate() +--- +... +-- test case with hanging calls +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload1_path, reload_path) +--- +- true +... +box.schema.func.reload("reload.foo") +--- +... +fibers = 10 +--- +... +for i = 1, fibers do fiber.create(function() c:call("reload.foo", {i}) end) end +--- +... +while box.space.test:count() < fibers do fiber.sleep(0.001) end +--- +... +-- double reload doesn't fail waiting functions +box.schema.func.reload("reload.foo") +--- +... +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload2_path, reload_path) +--- +- true +... +box.schema.func.reload("reload.foo") +--- +... +c:call("reload.foo") +--- +- [] +... +while box.space.test:count() < 2 * fibers + 1 do fiber.sleep(0.001) end +--- +... +box.space.test:select{} +--- +- - [-10] + - [-9] + - [-8] + - [-7] + - [-6] + - [-5] + - [-4] + - [-3] + - [-2] + - [-1] + - [0] + - [1] + - [2] + - [3] + - [4] + - [5] + - [6] + - [7] + - [8] + - [9] + - [10] +... +box.schema.func.drop("reload.foo") +--- +... +box.space.test:drop() +--- +... +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload1_path, reload_path) +--- +- true +... +box.schema.func.create('reload.test_reload', {language = "C"}) +--- +... +box.schema.user.grant('guest', 'execute', 'function', 'reload.test_reload') +--- +... +ch = fiber.channel(2) +--- +... +-- call first time to load function +c:call("reload.test_reload") +--- +- [[1]] +... +_ = fiber.create(function() ch:put(c:call("reload.test_reload")) end) +--- +... +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload2_path, reload_path) +--- +- true +... +box.schema.func.reload("reload.test_reload") +--- +... +_ = fiber.create(function() ch:put(c:call("reload.test_reload")) end) +--- +... +ch:get() +--- +- [[1]] +... +ch:get() +--- +- [[2]] +... +box.schema.func.create('reload.test_reload_fail', {language = "C"}) +--- +... +box.schema.user.grant('guest', 'execute', 'function', 'reload.test_reload_fail') +--- +... +c:call("reload.test_reload_fail") +--- +- [[2]] +... +_ = fio.unlink(reload_path) +--- +... +fio.symlink(reload1_path, reload_path) +--- +- true +... +s, e = pcall(box.schema.func.reload, "reload.test_reload") +--- +... +s, string.find(tostring(e), 'test_reload_fail') ~= nil +--- +- false +- true +... +c:call("reload.test_reload") +--- +- [[2]] +... +c:call("reload.test_reload_fail") +--- +- [[2]] +... +box.schema.func.drop("reload.test_reload") +--- +... +box.schema.func.drop("reload.test_reload_fail") +--- +... +_ = fio.unlink(reload_path) +--- +... diff --git a/test/box/func_reload.test.lua b/test/box/func_reload.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..728ee470f1089550dd753301efdd1ecb675657d0 --- /dev/null +++ b/test/box/func_reload.test.lua @@ -0,0 +1,86 @@ +fio = require('fio') +net = require('net.box') +fiber = require('fiber') + +ext = (jit.os == "OSX" and "dylib" or "so") +build_path = os.getenv("BUILDDIR") +reload1_path = build_path..'/test/box/reload1.'..ext +reload2_path = build_path..'/test/box/reload2.'..ext +reload_path = "reload."..ext +_ = fio.unlink(reload_path) + +c = net.connect(os.getenv("LISTEN")) + +box.schema.func.create('reload.foo', {language = "C"}) +box.schema.user.grant('guest', 'execute', 'function', 'reload.foo') +_ = box.schema.space.create('test') +_ = box.space.test:create_index('primary', {parts = {1, "integer"}}) +box.schema.user.grant('guest', 'read,write', 'space', 'test') +_ = fio.unlink(reload_path) +fio.symlink(reload1_path, reload_path) + +--check not fail on non-load func +box.schema.func.reload("reload.foo") + +-- test of usual case reload. No hanging calls +box.space.test:insert{0} +c:call("reload.foo", {1}) +box.space.test:delete{0} +_ = fio.unlink(reload_path) +fio.symlink(reload2_path, reload_path) + +box.schema.func.reload("reload.foo") +c:call("reload.foo") +box.space.test:select{} +box.space.test:truncate() + +-- test case with hanging calls +_ = fio.unlink(reload_path) +fio.symlink(reload1_path, reload_path) +box.schema.func.reload("reload.foo") + +fibers = 10 +for i = 1, fibers do fiber.create(function() c:call("reload.foo", {i}) end) end + +while box.space.test:count() < fibers do fiber.sleep(0.001) end +-- double reload doesn't fail waiting functions +box.schema.func.reload("reload.foo") + +_ = fio.unlink(reload_path) +fio.symlink(reload2_path, reload_path) +box.schema.func.reload("reload.foo") +c:call("reload.foo") + +while box.space.test:count() < 2 * fibers + 1 do fiber.sleep(0.001) end +box.space.test:select{} +box.schema.func.drop("reload.foo") +box.space.test:drop() +_ = fio.unlink(reload_path) + +fio.symlink(reload1_path, reload_path) +box.schema.func.create('reload.test_reload', {language = "C"}) +box.schema.user.grant('guest', 'execute', 'function', 'reload.test_reload') +ch = fiber.channel(2) +-- call first time to load function +c:call("reload.test_reload") +_ = fiber.create(function() ch:put(c:call("reload.test_reload")) end) +_ = fio.unlink(reload_path) +fio.symlink(reload2_path, reload_path) +box.schema.func.reload("reload.test_reload") +_ = fiber.create(function() ch:put(c:call("reload.test_reload")) end) +ch:get() +ch:get() + +box.schema.func.create('reload.test_reload_fail', {language = "C"}) +box.schema.user.grant('guest', 'execute', 'function', 'reload.test_reload_fail') +c:call("reload.test_reload_fail") +_ = fio.unlink(reload_path) +fio.symlink(reload1_path, reload_path) +s, e = pcall(box.schema.func.reload, "reload.test_reload") +s, string.find(tostring(e), 'test_reload_fail') ~= nil +c:call("reload.test_reload") +c:call("reload.test_reload_fail") + +box.schema.func.drop("reload.test_reload") +box.schema.func.drop("reload.test_reload_fail") +_ = fio.unlink(reload_path) diff --git a/test/box/function1.c b/test/box/function1.c index a72381b28b1b8162c2159453751c898c95dd6caa..8b9ea144c8c53feceb9e0c31f1cb8e9138360902 100644 --- a/test/box/function1.c +++ b/test/box/function1.c @@ -143,3 +143,12 @@ errors(box_function_ctx_t *ctx, const char *args, const char *args_end) return -1; /* raises "Unknown procedure error" */ } + +int +test_yield(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + say_info("-- yield - called --"); + fiber_sleep(0.001); + printf("ok - yield\n"); + return 0; +} diff --git a/test/box/function1.result b/test/box/function1.result index b7927397997e45f44f4988ffdac3361e87f2a922..a70e601a0a1533dbfef6e4b78d329fe4c6731e21 100644 --- a/test/box/function1.result +++ b/test/box/function1.result @@ -214,6 +214,29 @@ c:call(name) box.schema.func.drop(name) --- ... +-- Drop function while executing gh-910 +box.schema.func.create('function1.test_yield', {language = "C"}) +--- +... +box.schema.user.grant('guest', 'execute', 'function', 'function1.test_yield') +--- +... +fiber = require('fiber') +--- +... +ch = fiber.channel(1) +--- +... +_ = fiber.create(function() c:call('function1.test_yield') ch:put(true) end) +--- +... +box.schema.func.drop('function1.test_yield') +--- +... +ch:get() +--- +- true +... c:close() --- ... diff --git a/test/box/function1.test.lua b/test/box/function1.test.lua index 746b11a59ff1b49fa7e650b20babb78ce75da690..30b4190da770ba6b52ebe3a20e672638860a04a9 100644 --- a/test/box/function1.test.lua +++ b/test/box/function1.test.lua @@ -69,4 +69,13 @@ box.schema.user.grant('guest', 'execute', 'function', name) c:call(name) box.schema.func.drop(name) +-- Drop function while executing gh-910 +box.schema.func.create('function1.test_yield', {language = "C"}) +box.schema.user.grant('guest', 'execute', 'function', 'function1.test_yield') +fiber = require('fiber') +ch = fiber.channel(1) +_ = fiber.create(function() c:call('function1.test_yield') ch:put(true) end) +box.schema.func.drop('function1.test_yield') +ch:get() + c:close() diff --git a/test/box/reload1.c b/test/box/reload1.c new file mode 100644 index 0000000000000000000000000000000000000000..06baf3962178ed4dc5bf145ee55d6590faadc4fe --- /dev/null +++ b/test/box/reload1.c @@ -0,0 +1,57 @@ +#include "module.h" + +#include <stdio.h> +#include <msgpuck.h> + +int +foo(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + static const char *SPACE_TEST_NAME = "test"; + static const char *INDEX_NAME = "primary"; + uint32_t space_test_id = box_space_id_by_name(SPACE_TEST_NAME, + strlen(SPACE_TEST_NAME)); + uint32_t index_id = box_index_id_by_name(space_test_id, INDEX_NAME, + strlen(INDEX_NAME)); + if (space_test_id == BOX_ID_NIL || index_id == BOX_ID_NIL) { + return box_error_set(__FILE__, __LINE__, ER_PROC_C, + "Can't find index %s in space %s", + INDEX_NAME, SPACE_TEST_NAME); + } + mp_decode_array(&args); + uint32_t num = mp_decode_uint(&args); + + char buf[16]; + char *end = buf; + end = mp_encode_array(end, 1); + end = mp_encode_uint(end, num); + if (box_insert(space_test_id, buf, end, NULL) < 0) { + return box_error_set(__FILE__, __LINE__, ER_PROC_C, + "Can't insert in space %s", SPACE_TEST_NAME); + } + end = buf; + end = mp_encode_array(end, 1); + end = mp_encode_uint(end, 0); + while (box_index_count(space_test_id, index_id, ITER_EQ, buf, end) <= 0) { + fiber_sleep(0.001); + } + end = buf; + end = mp_encode_array(end, 1); + end = mp_encode_int(end, -((int)num)); + if (box_insert(space_test_id, buf, end, NULL) < 0) { + return box_error_set(__FILE__, __LINE__, ER_PROC_C, + "Can't insert in space %s", SPACE_TEST_NAME); + } + return 0; +} + +int +test_reload(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + fiber_sleep(0.001); + char tuple_buf[64]; + char *tuple_end = tuple_buf; + tuple_end = mp_encode_array(tuple_end, 1); + tuple_end = mp_encode_uint(tuple_end, 1); + struct tuple *tuple = box_tuple_new(box_tuple_format_default(), tuple_buf, tuple_end); + return box_return_tuple(ctx, tuple); +} diff --git a/test/box/reload2.c b/test/box/reload2.c new file mode 100644 index 0000000000000000000000000000000000000000..30a359171dde79a17c7ba875c509a20fe9383650 --- /dev/null +++ b/test/box/reload2.c @@ -0,0 +1,49 @@ +#include "module.h" + +#include <stdio.h> +#include <msgpuck.h> + +int +foo(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + static const char *SPACE_TEST_NAME = "test"; + uint32_t space_test_id = box_space_id_by_name(SPACE_TEST_NAME, + strlen(SPACE_TEST_NAME)); + if (space_test_id == BOX_ID_NIL) { + return box_error_set(__FILE__, __LINE__, ER_PROC_C, + "Can't find space %s", SPACE_TEST_NAME); + } + char buf[16]; + char *end = buf; + end = mp_encode_array(end, 1); + end = mp_encode_uint(end, 0); + if (box_insert(space_test_id, buf, end, NULL) < 0) { + return box_error_set(__FILE__, __LINE__, ER_PROC_C, + "Can't insert in space %s", SPACE_TEST_NAME); + } + return 0; +} + +int +test_reload(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + fiber_sleep(0.001); + char tuple_buf[64]; + char *tuple_end = tuple_buf; + tuple_end = mp_encode_array(tuple_end, 1); + tuple_end = mp_encode_uint(tuple_end, 2); + struct tuple *tuple = box_tuple_new(box_tuple_format_default(), tuple_buf, tuple_end); + return box_return_tuple(ctx, tuple); +} + +int +test_reload_fail(box_function_ctx_t *ctx, const char *args, const char *args_end) +{ + char tuple_buf[64]; + char *tuple_end = tuple_buf; + tuple_end = mp_encode_array(tuple_end, 1); + tuple_end = mp_encode_uint(tuple_end, 2); + struct tuple *tuple = box_tuple_new(box_tuple_format_default(), tuple_buf, tuple_end); + return box_return_tuple(ctx, tuple); + +}