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