From 2dd7633e987183f9389cceb53f2698c058828586 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 16 Jan 2025 16:29:33 -0800
Subject: [PATCH v9 4/6] tidstore.c: support shared iteration on TidStore.

Reviewed-by:
Discussion: https://postgr.es/m/
---
 src/backend/access/common/tidstore.c          | 59 ++++++++++++++++++
 src/include/access/tidstore.h                 |  3 +
 .../modules/test_tidstore/test_tidstore.c     | 62 ++++++++++++++-----
 3 files changed, 110 insertions(+), 14 deletions(-)

diff --git a/src/backend/access/common/tidstore.c b/src/backend/access/common/tidstore.c
index 5bd75fb499c..720bc86c266 100644
--- a/src/backend/access/common/tidstore.c
+++ b/src/backend/access/common/tidstore.c
@@ -475,6 +475,7 @@ TidStoreBeginIterate(TidStore *ts)
 	iter = palloc0(sizeof(TidStoreIter));
 	iter->ts = ts;
 
+	/* begin iteration on the radix tree */
 	if (TidStoreIsShared(ts))
 		iter->tree_iter.shared = shared_ts_begin_iterate(ts->tree.shared);
 	else
@@ -525,6 +526,56 @@ TidStoreEndIterate(TidStoreIter *iter)
 	pfree(iter);
 }
 
+/*
+ * Prepare to iterate through a shared TidStore in shared mode. This function
+ * is aimed to start the iteration on the given TidStore with parallel workers.
+ *
+ * The TidStoreIter struct is created in the caller's memory context, and it
+ * will be freed in TidStoreEndIterate.
+ *
+ * The caller is responsible for locking TidStore until the iteration is
+ * finished.
+ */
+TidStoreIter *
+TidStoreBeginIterateShared(TidStore *ts)
+{
+	TidStoreIter *iter;
+
+	if (!TidStoreIsShared(ts))
+		elog(ERROR, "cannot begin shared iteration on local TidStore");
+
+	iter = palloc0(sizeof(TidStoreIter));
+	iter->ts = ts;
+
+	/* begin the shared iteration on radix tree */
+	iter->tree_iter.shared =
+		(shared_ts_iter *) shared_ts_begin_iterate_shared(ts->tree.shared);
+
+	return iter;
+}
+
+/*
+ * Attach to the shared TidStore iterator. 'iter_handle' is the dsa_pointer
+ * returned by TidStoreGetSharedIterHandle(). The returned object is allocated
+ * in backend-local memory using CurrentMemoryContext.
+ */
+TidStoreIter *
+TidStoreAttachIterateShared(TidStore *ts, dsa_pointer iter_handle)
+{
+	TidStoreIter *iter;
+
+	Assert(TidStoreIsShared(ts));
+
+	iter = palloc0(sizeof(TidStoreIter));
+	iter->ts = ts;
+
+	/* Attach to the shared iterator */
+	iter->tree_iter.shared = shared_ts_attach_iterate_shared(ts->tree.shared,
+															 iter_handle);
+
+	return iter;
+}
+
 /*
  * Return the memory usage of TidStore.
  */
@@ -556,6 +607,14 @@ TidStoreGetHandle(TidStore *ts)
 	return (dsa_pointer) shared_ts_get_handle(ts->tree.shared);
 }
 
+dsa_pointer
+TidStoreGetSharedIterHandle(TidStoreIter *iter)
+{
+	Assert(TidStoreIsShared(iter->ts));
+
+	return (dsa_pointer) shared_ts_get_iter_handle(iter->tree_iter.shared);
+}
+
 /*
  * Given a TidStoreIterResult returned by TidStoreIterateNext(), extract the
  * offset numbers.  Returns the number of offsets filled in, if <=
diff --git a/src/include/access/tidstore.h b/src/include/access/tidstore.h
index 041091df278..c886cef0f7d 100644
--- a/src/include/access/tidstore.h
+++ b/src/include/access/tidstore.h
@@ -37,6 +37,9 @@ extern void TidStoreDetach(TidStore *ts);
 extern void TidStoreLockExclusive(TidStore *ts);
 extern void TidStoreLockShare(TidStore *ts);
 extern void TidStoreUnlock(TidStore *ts);
+extern TidStoreIter *TidStoreBeginIterateShared(TidStore *ts);
+extern TidStoreIter *TidStoreAttachIterateShared(TidStore *ts, dsa_pointer iter_handle);
+extern dsa_pointer TidStoreGetSharedIterHandle(TidStoreIter *iter);
 extern void TidStoreDestroy(TidStore *ts);
 extern void TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets,
 									int num_offsets);
diff --git a/src/test/modules/test_tidstore/test_tidstore.c b/src/test/modules/test_tidstore/test_tidstore.c
index eb16e0fbfa6..36654cf0110 100644
--- a/src/test/modules/test_tidstore/test_tidstore.c
+++ b/src/test/modules/test_tidstore/test_tidstore.c
@@ -33,6 +33,7 @@ PG_FUNCTION_INFO_V1(test_is_full);
 PG_FUNCTION_INFO_V1(test_destroy);
 
 static TidStore *tidstore = NULL;
+static bool tidstore_is_shared;
 static size_t tidstore_empty_size;
 
 /* array for verification of some tests */
@@ -107,6 +108,7 @@ test_create(PG_FUNCTION_ARGS)
 		LWLockRegisterTranche(tranche_id, "test_tidstore");
 
 		tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
+		tidstore_is_shared = true;
 
 		/*
 		 * Remain attached until end of backend or explicitly detached so that
@@ -115,8 +117,11 @@ test_create(PG_FUNCTION_ARGS)
 		dsa_pin_mapping(TidStoreGetDSA(tidstore));
 	}
 	else
+	{
 		/* VACUUM uses insert only, so we test the other option. */
 		tidstore = TidStoreCreateLocal(tidstore_max_size, false);
+		tidstore_is_shared = false;
+	}
 
 	tidstore_empty_size = TidStoreMemoryUsage(tidstore);
 
@@ -212,14 +217,42 @@ do_set_block_offsets(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(blkno);
 }
 
+/* Collect TIDs stored in the tidstore, in order */
+static void
+check_iteration(TidStore *tidstore, int *num_iter_tids, bool shared_iter)
+{
+	TidStoreIter *iter;
+	TidStoreIterResult *iter_result;
+
+	TidStoreLockShare(tidstore);
+
+	if (shared_iter)
+		iter = TidStoreBeginIterateShared(tidstore);
+	else
+		iter = TidStoreBeginIterate(tidstore);
+
+	while ((iter_result = TidStoreIterateNext(iter)) != NULL)
+	{
+		OffsetNumber offsets[MaxOffsetNumber];
+		int			num_offsets;
+
+		num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
+		Assert(num_offsets <= lengthof(offsets));
+		for (int i = 0; i < num_offsets; i++)
+			ItemPointerSet(&(items.iter_tids[(*num_iter_tids)++]), iter_result->blkno,
+						   offsets[i]);
+	}
+
+	TidStoreEndIterate(iter);
+	TidStoreUnlock(tidstore);
+}
+
 /*
  * Verify TIDs in store against the array.
  */
 Datum
 check_set_block_offsets(PG_FUNCTION_ARGS)
 {
-	TidStoreIter *iter;
-	TidStoreIterResult *iter_result;
 	int			num_iter_tids = 0;
 	int			num_lookup_tids = 0;
 	BlockNumber prevblkno = 0;
@@ -261,22 +294,23 @@ check_set_block_offsets(PG_FUNCTION_ARGS)
 	}
 
 	/* Collect TIDs stored in the tidstore, in order */
+	check_iteration(tidstore, &num_iter_tids, false);
 
-	TidStoreLockShare(tidstore);
-	iter = TidStoreBeginIterate(tidstore);
-	while ((iter_result = TidStoreIterateNext(iter)) != NULL)
+	/* If the tidstore is shared, check the shared-iteration as well */
+	if (tidstore_is_shared)
 	{
-		OffsetNumber offsets[MaxOffsetNumber];
-		int			num_offsets;
+		int			num_iter_tids_shared = 0;
 
-		num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
-		Assert(num_offsets <= lengthof(offsets));
-		for (int i = 0; i < num_offsets; i++)
-			ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
-						   offsets[i]);
+		check_iteration(tidstore, &num_iter_tids_shared, true);
+
+		/*
+		 * verify that normal iteration and shared iteration returned the
+		 * number of TIDs.
+		 */
+		if (num_lookup_tids != num_iter_tids_shared)
+			elog(ERROR, "shared-iteration should have %d TIDs, have %d aaa",
+				 items.num_tids, num_iter_tids_shared);
 	}
-	TidStoreEndIterate(iter);
-	TidStoreUnlock(tidstore);
 
 	/*
 	 * Sort verification and lookup arrays and test that all arrays are the
-- 
2.43.5

