From b00a4579ec9186cff286f93eac3cf99347f0514a Mon Sep 17 00:00:00 2001 From: Aleksandr Lyapunov <alyapunov@tarantool.org> Date: Mon, 24 Jan 2022 07:44:25 +0300 Subject: [PATCH] box: add pin/unping infrastructure for spaces There are cases when we need to be sure that a space by given id or name is not deleted; and if it is replaced (in space cache), there's need to track pointer to new space. Like ib constraints: they must hold a pointer to struct space while it's very hard to determine whether there'a constraint that points to given space. Implement space pin/unpin for this purpose. You can pin a space to declare that the space is require to exist. To have to unpin it when the space is not needed anymore. NO_DOC=refactoring NO_CHANGELOG=refactoring NO_TEST=refactoring --- src/box/alter.cc | 25 +++++++++++++ src/box/space.c | 2 ++ src/box/space.h | 2 ++ src/box/space_cache.c | 81 +++++++++++++++++++++++++++++++++++++++++++ src/box/space_cache.h | 74 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+) diff --git a/src/box/alter.cc b/src/box/alter.cc index 774d5a9a25..bca20b3000 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -2380,6 +2380,17 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) "the space has check constraints"); return -1; } + /* Check whether old_space is used somewhere. */ + enum space_cache_holder_type pinned_type; + if (space_cache_is_pinned(old_space, &pinned_type)) { + const char *type_str = + space_cache_holder_type_strs[pinned_type]; + diag_set(ClientError, ER_DROP_SPACE, + space_name(old_space), + tt_sprintf("space is referenced by %s", + type_str)); + return -1; + } /** * The space must be deleted from the space * cache right away to achieve linearisable @@ -2653,6 +2664,20 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) "space sequence exists"); return -1; } + + /* + * Must not truncate pinned space. + */ + enum space_cache_holder_type pinned_type; + if (space_cache_is_pinned(old_space, &pinned_type)) { + const char *type_str = + space_cache_holder_type_strs[pinned_type]; + diag_set(ClientError, ER_ALTER_SPACE, + space_name(old_space), + tt_sprintf("space is referenced by %s", + type_str)); + return -1; + } } if (iid != 0 && space_index(old_space, 0) == NULL) { diff --git a/src/box/space.c b/src/box/space.c index 4d2c704a10..04e0f03f5b 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -271,6 +271,7 @@ space_create(struct space *space, struct engine *engine, } } space->constraint_ids = mh_strnptr_new(); + rlist_create(&space->space_cache_pin_list); rlist_create(&space->memtx_stories); return 0; @@ -350,6 +351,7 @@ space_delete(struct space *space) assert(rlist_empty(&space->parent_fk_constraint)); assert(rlist_empty(&space->child_fk_constraint)); assert(rlist_empty(&space->ck_constraint)); + assert(rlist_empty(&space->space_cache_pin_list)); space->vtab->destroy(space); } diff --git a/src/box/space.h b/src/box/space.h index 5d476d4ac5..e25f5a0b38 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -239,6 +239,8 @@ struct space { * Hash table with constraint identifiers hashed by name. */ struct mh_strnptr_t *constraint_ids; + /** List of space holders. This member is a property of space cache. */ + struct rlist space_cache_pin_list; /** * List of all tx stories in the space. */ diff --git a/src/box/space_cache.c b/src/box/space_cache.c index ffa02051bb..a4cd55c16a 100644 --- a/src/box/space_cache.c +++ b/src/box/space_cache.c @@ -21,6 +21,10 @@ static struct mh_strnptr_t *spaces_by_name; */ uint32_t space_cache_version; +const char *space_cache_holder_type_strs[SPACE_HOLDER_MAX] = { + "foreign key", +}; + void space_cache_init(void) { @@ -123,6 +127,29 @@ space_foreach(int (*func)(struct space *sp, void *udata), void *udata) return 0; } +/** + * If the @a old_space space is pinned, relink holders of that space to + * the @a new_space. + */ +static void +space_cache_repin_pinned(struct space *old_space, struct space *new_space) +{ + assert(new_space != NULL); + assert(rlist_empty(&new_space->space_cache_pin_list)); + if (old_space == NULL) + return; + + rlist_swap(&new_space->space_cache_pin_list, + &old_space->space_cache_pin_list); + + struct space_cache_holder *h; + rlist_foreach_entry(h, &new_space->space_cache_pin_list, link) { + assert(h->space == old_space); + h->space = new_space; + h->on_replace(h, old_space); + } +} + void space_cache_replace(struct space *old_space, struct space *new_space) { @@ -172,6 +199,9 @@ space_cache_replace(struct space *old_space, struct space *new_space) old_space_by_name = (struct space *)p_old_s->val; assert(old_space_by_name == old_space); (void)old_space_by_name; + + /* If old space is pinned, we have to pin the new space. */ + space_cache_repin_pinned(old_space, new_space); } else { /* * Delete @old_space from @spaces cache. @@ -194,6 +224,7 @@ space_cache_replace(struct space *old_space, struct space *new_space) assert(old_space_by_name == old_space); (void)old_space_by_name; mh_strnptr_del(spaces_by_name, k, NULL); + assert(rlist_empty(&old_space->space_cache_pin_list)); } space_cache_version++; @@ -206,3 +237,53 @@ space_cache_replace(struct space *old_space, struct space *new_space) if (old_space != NULL) space_invalidate(old_space); } + +void +space_cache_on_replace_noop(struct space_cache_holder *holder, + struct space *old_space) +{ + (void)holder; + (void)old_space; +} + +void +space_cache_pin(struct space *space, struct space_cache_holder *holder, + space_cache_on_replace on_replace, + enum space_cache_holder_type type) +{ + assert(mh_i32ptr_find(spaces, space->def->id, NULL) != mh_end(spaces)); + holder->on_replace = on_replace; + holder->type = type; + rlist_add_tail(&space->space_cache_pin_list, &holder->link); + holder->space = space; +} + +void +space_cache_unpin(struct space_cache_holder *holder) +{ + struct space *space = holder->space; (void)space; + assert(mh_i32ptr_find(spaces, space->def->id, NULL) != mh_end(spaces)); +#ifndef NDEBUG + /* Paranoid check that the holder in space's pin list. */ + bool is_in_list = false; + struct rlist *tmp; + rlist_foreach(tmp, &space->space_cache_pin_list) + is_in_list = is_in_list || tmp == &holder->link; + assert(is_in_list); +#endif + rlist_del(&holder->link); + holder->space = NULL; +} + +bool +space_cache_is_pinned(struct space *space, enum space_cache_holder_type *type) +{ + assert(mh_i32ptr_find(spaces, space->def->id, NULL) != mh_end(spaces)); + if (rlist_empty(&space->space_cache_pin_list)) + return false; + struct space_cache_holder *h = + rlist_first_entry(&space->space_cache_pin_list, + struct space_cache_holder, link); + *type = h->type; + return true; +} diff --git a/src/box/space_cache.h b/src/box/space_cache.h index 5d73b0ba7e..5808fbb051 100644 --- a/src/box/space_cache.h +++ b/src/box/space_cache.h @@ -27,6 +27,45 @@ extern uint32_t space_cache_version; */ extern struct rlist on_alter_space; +/** + * Type of a holder that can pin a space. @sa struct space_cache_holder. + */ +enum space_cache_holder_type { + SPACE_HOLDER_FOREIGN_KEY, + SPACE_HOLDER_MAX, +}; + +/** + * Lowercase name of each type. + */ +extern const char *space_cache_holder_type_strs[SPACE_HOLDER_MAX]; + +struct space_cache_holder; +typedef void +(*space_cache_on_replace)(struct space_cache_holder *holder, + struct space *old_space); + +/** + * Definition of a holder that pinned some space. Pinning of a space is + * a mechanism that is designed for preventing of deletion of some space from + * space cache by storing links to holders that prevented that. On the other + * hand it is allowed to replace a space with another - the new space becomes + * pinned after this point. + */ +struct space_cache_holder { + /** Holders of the same func are linked into ring list by this link. */ + struct rlist link; + /** Actual pointer to space. */ + struct space *space; + /** Callback that is called when the space is replaced in cache. */ + space_cache_on_replace on_replace; + /** + * Type of holder, mostly for better error generation, but also can be + * used for proper container_of application. + */ + enum space_cache_holder_type type; +}; + /** * Initialize space cache storage. */ @@ -108,6 +147,41 @@ space_foreach(int (*func)(struct space *sp, void *udata), void *udata); void space_cache_replace(struct space *old_space, struct space *new_space); +/** No-op callback for space_cache_pin */ +void +space_cache_on_replace_noop(struct space_cache_holder *holder, + struct space *old_space); + +/** + * Register that there is a @a holder of type @a type that is dependent + * on @a space. + * The space must be in cache (asserted). + * If a space has holders, it must not be deleted (asserted). It can be + * replaced though, the holder will hold the new space in that case and + * @a on_replace callback is called. + */ +void +space_cache_pin(struct space *space, struct space_cache_holder *holder, + space_cache_on_replace on_replace, + enum space_cache_holder_type type); + +/** + * Notify that a @a holder does not depend anymore on @a space. + * The space must be in cache (asserted). + * If a space has no holders, it can be deleted. + */ +void +space_cache_unpin(struct space_cache_holder *holder); + +/** + * Check whether the @a space 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 space has holders, it must not be deleted (asserted). + */ +bool +space_cache_is_pinned(struct space *space, enum space_cache_holder_type *type); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ -- GitLab