From ffc9cee400cd3f067b25f1797027eebacaa57c65 Mon Sep 17 00:00:00 2001 From: Aleksandr Lyapunov <alyapunov@tarantool.org> Date: Mon, 22 Nov 2021 17:31:16 +0300 Subject: [PATCH] box: add pin/unping infrastructure for func cache There are cases when we need to be sure that a function is not deleted and/or removed from func cache. For example constraints: they must hold a pointer to struct func while it's very hard to determine whether there'a constraint that points to given func. Implement func pin/unpin for this purpose. You can pin a func to declare that the func must not be deleted. To have to unpin it when the func is not needed anymore. NO_DOC=refactoring NO_CHANGELOG=refactoring --- src/box/alter.cc | 11 ++ src/box/func.c | 2 + src/box/func.h | 2 + src/box/func_cache.c | 47 ++++++++- src/box/func_cache.h | 60 +++++++++++ src/box/sql/func.c | 1 + test/unit/CMakeLists.txt | 2 + test/unit/func_cache.c | 203 ++++++++++++++++++++++++++++++++++++ test/unit/func_cache.result | 53 ++++++++++ 9 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 test/unit/func_cache.c create mode 100644 test/unit/func_cache.result diff --git a/src/box/alter.cc b/src/box/alter.cc index bfd70dc2b3..774d5a9a25 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -3635,6 +3635,17 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) "function has references"); return -1; } + /* Check whether old_func is used somewhere. */ + enum func_holder_type pinned_type; + if (func_is_pinned(old_func, &pinned_type)) { + const char *type_str = + func_cache_holder_type_strs[pinned_type]; + diag_set(ClientError, ER_DROP_FUNCTION, + (unsigned int)old_func->def->uid, + tt_sprintf("function is referenced by %s", + type_str)); + return -1; + } struct trigger *on_commit = txn_alter_trigger_new(on_drop_func_commit, old_func); struct trigger *on_rollback = diff --git a/src/box/func.c b/src/box/func.c index e7a368b2f2..a43add36c8 100644 --- a/src/box/func.c +++ b/src/box/func.c @@ -399,6 +399,7 @@ func_new(struct func_def *def) if (func == NULL) return NULL; func->def = def; + rlist_create(&func->func_cache_pin_list); /** Nobody has access to the function but the owner. */ memset(func->access, 0, sizeof(func->access)); /* @@ -500,6 +501,7 @@ static struct func_vtab func_c_vtab = { void func_delete(struct func *func) { + assert(rlist_empty(&func->func_cache_pin_list)); struct func_def *def = func->def; credentials_destroy(&func->owner_credentials); func->vtab->destroy(func); diff --git a/src/box/func.h b/src/box/func.h index a8a6932689..b0e5dafe60 100644 --- a/src/box/func.h +++ b/src/box/func.h @@ -60,6 +60,8 @@ struct func { struct func_def *def; /** Virtual method table. */ const struct func_vtab *vtab; + /** List of func holders. This member is a property of func cache. */ + struct rlist func_cache_pin_list; /** * Authentication id of the owner of the function, * used for set-user-id functions. diff --git a/src/box/func_cache.c b/src/box/func_cache.c index 38f09e0cc1..6657bfe9fd 100644 --- a/src/box/func_cache.c +++ b/src/box/func_cache.c @@ -13,6 +13,10 @@ static struct mh_i32ptr_t *funcs; /** Name -> func dictionary. */ static struct mh_strnptr_t *funcs_by_name; +const char *func_cache_holder_type_strs[FUNC_HOLDER_MAX] = { + "constraint", +}; + void func_cache_init(void) { @@ -55,8 +59,8 @@ func_cache_delete(uint32_t fid) mh_int_t k = mh_i32ptr_find(funcs, fid, NULL); if (k == mh_end(funcs)) return; - struct func *func = (struct func *) - mh_i32ptr_node(funcs, k)->val; + struct func *func = (struct func *)mh_i32ptr_node(funcs, k)->val; + assert(rlist_empty(&func->func_cache_pin_list)); mh_i32ptr_del(funcs, k, NULL); k = mh_strnptr_find_str(funcs_by_name, func->def->name, strlen(func->def->name)); @@ -81,3 +85,42 @@ func_by_name(const char *name, uint32_t name_len) return NULL; return (struct func *)mh_strnptr_node(funcs_by_name, func)->val; } + +void +func_pin(struct func *func, struct func_cache_holder *holder, + enum func_holder_type type) +{ + assert(func_by_id(func->def->fid) != NULL); + holder->func = func; + holder->type = type; + rlist_add_tail(&func->func_cache_pin_list, &holder->link); +} + +void +func_unpin(struct func_cache_holder *holder) +{ + assert(func_by_id(holder->func->def->fid) != NULL); +#ifndef NDEBUG + /* Paranoid check that the func is pinned by holder. */ + bool is_in_list = false; + struct rlist *tmp; + rlist_foreach(tmp, &holder->func->func_cache_pin_list) + is_in_list = is_in_list || tmp == &holder->link; + assert(is_in_list); +#endif + rlist_del(&holder->link); + holder->func = NULL; +} + +bool +func_is_pinned(struct func *func, enum func_holder_type *type) +{ + assert(func_by_id(func->def->fid) != NULL); + if (rlist_empty(&func->func_cache_pin_list)) + return false; + struct func_cache_holder *h = + rlist_first_entry(&func->func_cache_pin_list, + struct func_cache_holder, link); + *type = h->type; + return true; +} diff --git a/src/box/func_cache.h b/src/box/func_cache.h index 72fcca09ac..b022c10e54 100644 --- a/src/box/func_cache.h +++ b/src/box/func_cache.h @@ -14,6 +14,36 @@ extern "C" { struct func; +/** + * Type of a holder that can pin a func. @sa struct func_cache_holder. + */ +enum func_holder_type { + FUNC_HOLDER_CONSTRAINT, + FUNC_HOLDER_MAX, +}; + +/** + * Lowercase name of each type. + */ +extern const char *func_cache_holder_type_strs[FUNC_HOLDER_MAX]; + +/** + * Definition of a holder that pinned some func. Pinning of a func is + * a mechanism that is designed for preventing of deletion of some func from + * func cache by storing links to holders that prevented that. + */ +struct func_cache_holder { + /** Holders of the same func are linked into ring list by this link. */ + struct rlist link; + /** Actual pointer to func. */ + struct func *func; + /** + * Type of holder, mostly for better error generation, but also can be + * used for proper container_of application. + */ + enum func_holder_type type; +}; + /** * Initialize function cache storage. */ @@ -35,6 +65,9 @@ func_cache_insert(struct func *func); /** * Delete a function object from the function cache. + * The function must not have any keepers (assert, @sa func_cache_is_kept), + * so if there is no assurance that there are no pins, @sa func_cache_is_pinned + * must be called before. * If the function is not found by five ID - do nothing. * @param fid ID of function object. */ @@ -56,6 +89,33 @@ func_by_id(uint32_t fid); struct func * func_by_name(const char *name, uint32_t name_len); +/** + * Register that there is a @a holder of type @a type that is dependent + * on function @a func. + * The function must be in cache (asserted). + * If a function has holders, it must not be deleted (asserted). + */ +void +func_pin(struct func *func, struct func_cache_holder *holder, + enum func_holder_type type); + +/** + * Notify that a @a holder does not depend anymore on function. + * The function must be in cache (asserted). + * If a function has no holders, it can be deleted. + */ +void +func_unpin(struct func_cache_holder *holder); + +/** + * Check whether the function @a func has holders or not. + * If it has, @a type argument is set to the first holder's type. + * The function must be in cache (asserted). + * If a function has holders, it must not be deleted (asserted). + */ +bool +func_is_pinned(struct func *func, enum func_holder_type *type); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/box/sql/func.c b/src/box/sql/func.c index df1fcd9795..2d701d2824 100644 --- a/src/box/sql/func.c +++ b/src/box/sql/func.c @@ -2149,6 +2149,7 @@ sql_built_in_functions_cache_init(void) panic("Out of memory on creating SQL built-in"); func->base.def = def; + rlist_create(&func->base.func_cache_pin_list); func->base.vtab = &func_sql_builtin_vtab; credentials_create_empty(&func->base.owner_credentials); memset(func->base.access, 0, sizeof(func->base.access)); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 16e1c4d0e1..1f0eee4f71 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -92,6 +92,8 @@ add_executable(fiber.test fiber.cc core_test_utils.c) target_link_libraries(fiber.test core unit) add_executable(fiber_stack.test fiber_stack.c core_test_utils.c) target_link_libraries(fiber_stack.test core unit) +add_executable(func_cache.test func_cache.c box_test_utils.c) +target_link_libraries(func_cache.test box unit) if (NOT ENABLE_GCOV) # This test is known to be broken with GCOV diff --git a/test/unit/func_cache.c b/test/unit/func_cache.c new file mode 100644 index 0000000000..3b9892cf18 --- /dev/null +++ b/test/unit/func_cache.c @@ -0,0 +1,203 @@ +#include "func_cache.h" +#include "func.h" +#include "trivia/util.h" +#include "unit.h" + +int status; + +struct func * +test_func_new(uint32_t id, const char *name) +{ + uint32_t name_len = strlen(name); + struct func_def *def = xmalloc(offsetof(struct func_def, + name[name_len + 1])); + def->fid = id; + strcpy(def->name, name); + def->name_len = strlen(name); + struct func *f = xmalloc(sizeof(*f)); + f->def = def; + rlist_create(&f->func_cache_pin_list); + return f; +} + +static void +test_func_delete(struct func *f) +{ + free(f->def); + free(f); +} + +/** + * Test that pin/is_pinned/unpin works fine with one func and one holder. + */ +static void +func_cache_pin_test_one_holder(void) +{ + header(); + plan(7); + + func_cache_init(); + struct func *f1 = test_func_new(1, "func1"); + enum func_holder_type type; + struct func_cache_holder h1; + + func_cache_insert(f1); + ok(!func_is_pinned(f1, &type), "ok"); + func_pin(f1, &h1, 1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1, "ok"); + func_unpin(&h1); + ok(!func_is_pinned(f1, &type), "ok"); + func_pin(f1, &h1, 1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1, "ok"); + func_unpin(&h1); + ok(!func_is_pinned(f1, &type), "ok"); + func_cache_delete(f1->def->fid); + + test_func_delete(f1); + func_cache_destroy(); + + footer(); + status |= check_plan(); +} + +/** + * Test several holders that pin/unpins one func in FIFO order. + */ +static void +func_cache_pin_test_fifo(void) +{ + header(); + plan(8); + + func_cache_init(); + struct func *f1 = test_func_new(1, "func1"); + enum func_holder_type type; + struct func_cache_holder h1, h2; + + func_cache_insert(f1); + ok(!func_is_pinned(f1, &type), "ok"); + func_pin(f1, &h1, 1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1, "ok"); + func_pin(f1, &h2, 2); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1 || type == 2, "ok"); + func_unpin(&h1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 2, "ok"); + func_unpin(&h2); + ok(!func_is_pinned(f1, &type), "ok"); + func_cache_delete(f1->def->fid); + + test_func_delete(f1); + func_cache_destroy(); + + footer(); + status |= check_plan(); +} + +/** + * Test several holders that pin/unpins one func in LIFO order. + */ +static void +func_cache_pin_test_lifo(void) +{ + header(); + plan(8); + + func_cache_init(); + struct func *f1 = test_func_new(1, "func1"); + enum func_holder_type type; + struct func_cache_holder h1, h2; + + func_cache_insert(f1); + ok(!func_is_pinned(f1, &type), "ok"); + func_pin(f1, &h1, 1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1, "ok"); + func_pin(f1, &h2, 2); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1 || type == 2, "ok"); + func_unpin(&h2); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1, "ok"); + func_unpin(&h1); + ok(!func_is_pinned(f1, &type), "ok"); + func_cache_delete(f1->def->fid); + + test_func_delete(f1); + func_cache_destroy(); + + footer(); + status |= check_plan(); +} + +/** + * Test several holders with several funcs. + */ +static void +func_cache_pin_test_several(void) +{ + header(); + plan(18); + + func_cache_init(); + struct func *f1 = test_func_new(1, "func1"); + struct func *f2 = test_func_new(2, "func2"); + enum func_holder_type type; + struct func_cache_holder h1, h2, h3; + + func_cache_insert(f1); + ok(!func_is_pinned(f1, &type), "ok"); + func_pin(f1, &h1, 1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1, "ok"); + + func_cache_insert(f2); + ok(func_is_pinned(f1, &type), "ok"); + ok(!func_is_pinned(f2, &type), "ok"); + + func_pin(f1, &h2, 2); + ok(func_is_pinned(f1, &type), "ok"); + ok(!func_is_pinned(f2, &type), "ok"); + + func_pin(f2, &h3, 3); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 1 || type == 2, "ok"); + ok(func_is_pinned(f2, &type), "ok"); + ok(type == 3, "ok"); + + func_unpin(&h1); + ok(func_is_pinned(f1, &type), "ok"); + ok(type == 2, "ok"); + ok(func_is_pinned(f2, &type), "ok"); + ok(type == 3, "ok"); + + func_unpin(&h3); + ok(func_is_pinned(f1, &type), "ok"); + ok(!func_is_pinned(f2, &type), "ok"); + func_cache_delete(f2->def->fid); + + func_unpin(&h2); + ok(!func_is_pinned(f1, &type), "ok"); + func_cache_delete(f1->def->fid); + + test_func_delete(f1); + test_func_delete(f2); + func_cache_destroy(); + + footer(); + status |= check_plan(); +} + +int +main(void) +{ + func_cache_pin_test_one_holder(); + func_cache_pin_test_fifo(); + func_cache_pin_test_lifo(); + func_cache_pin_test_several(); + return status; +} diff --git a/test/unit/func_cache.result b/test/unit/func_cache.result new file mode 100644 index 0000000000..f038c5578d --- /dev/null +++ b/test/unit/func_cache.result @@ -0,0 +1,53 @@ + *** func_cache_pin_test_one_holder *** +1..7 +ok 1 - ok +ok 2 - ok +ok 3 - ok +ok 4 - ok +ok 5 - ok +ok 6 - ok +ok 7 - ok + *** func_cache_pin_test_one_holder: done *** + *** func_cache_pin_test_fifo *** +1..8 +ok 1 - ok +ok 2 - ok +ok 3 - ok +ok 4 - ok +ok 5 - ok +ok 6 - ok +ok 7 - ok +ok 8 - ok + *** func_cache_pin_test_fifo: done *** + *** func_cache_pin_test_lifo *** +1..8 +ok 1 - ok +ok 2 - ok +ok 3 - ok +ok 4 - ok +ok 5 - ok +ok 6 - ok +ok 7 - ok +ok 8 - ok + *** func_cache_pin_test_lifo: done *** + *** func_cache_pin_test_several *** +1..18 +ok 1 - ok +ok 2 - ok +ok 3 - ok +ok 4 - ok +ok 5 - ok +ok 6 - ok +ok 7 - ok +ok 8 - ok +ok 9 - ok +ok 10 - ok +ok 11 - ok +ok 12 - ok +ok 13 - ok +ok 14 - ok +ok 15 - ok +ok 16 - ok +ok 17 - ok +ok 18 - ok + *** func_cache_pin_test_several: done *** -- GitLab