Regression test failed because I didn't add an extra new line in the
expected file.

2025년 3월 17일 (월) 오전 9:55, Sungwoo Chang <swchang...@gmail.com>님이 작성:
>
> There was a minor typo in the test module. I built and ran the
> test_dsm_registry extension before submitting the patch but perhaps it
> got mixed up with an older version.
From ef58c9f26a73f8b3714ad02053749d2799b9fd4f Mon Sep 17 00:00:00 2001
From: swchangdev <swchang...@gmail.com>
Date: Fri, 14 Mar 2025 10:00:12 +0900
Subject: [PATCH] Add detach and destroy feature to dsm_registry

---
 src/backend/storage/ipc/dsm_registry.c        | 167 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   8 +
 .../expected/test_dsm_registry.out            |  12 ++
 .../sql/test_dsm_registry.sql                 |   2 +
 .../test_dsm_registry--1.0.sql                |   6 +
 .../test_dsm_registry/test_dsm_registry.c     |  28 +++
 6 files changed, 222 insertions(+), 1 deletion(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..7915b70e47d 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -47,6 +47,12 @@ typedef struct DSMRegistryEntry
 	size_t		size;
 } DSMRegistryEntry;
 
+typedef struct DSMDetachCallbackContext
+{
+	void (*on_detach_callback) (void *);
+	void *arg;
+} DSMDetachCallbackContext;
+
 static const dshash_parameters dsh_params = {
 	offsetof(DSMRegistryEntry, handle),
 	sizeof(DSMRegistryEntry),
@@ -121,6 +127,36 @@ init_dsm_registry(void)
 	LWLockRelease(DSMRegistryLock);
 }
 
+static void
+call_on_detach_callback(dsm_segment *seg, Datum arg)
+{
+	char *ptr = DatumGetPointer(arg);
+	DSMDetachCallbackContext *cb_ctx = (DSMDetachCallbackContext *)ptr;
+	cb_ctx->on_detach_callback(cb_ctx->arg);
+}
+
+static void
+detach_dsm_segment(dsm_handle handle, void (*detach_cb) (void *), void *arg)
+{
+	dsm_segment *seg = dsm_find_mapping(handle);
+
+	/* Detach the segment only if it is already attached */
+	if (seg != NULL)
+	{
+		if (detach_cb != NULL)
+		{
+			DSMDetachCallbackContext *cb_ctx = palloc(sizeof(DSMDetachCallbackContext));
+			cb_ctx->on_detach_callback = detach_cb;
+			cb_ctx->arg = arg;
+
+			on_dsm_detach(seg, call_on_detach_callback, PointerGetDatum(cb_ctx));
+		}
+
+		dsm_unpin_mapping(seg);
+		dsm_detach(seg);
+	}
+}
+
 /*
  * Initialize or attach a named DSM segment.
  *
@@ -172,6 +208,8 @@ GetNamedDSMSegment(const char *name, size_t size,
 	}
 	else if (entry->size != size)
 	{
+		dshash_release_lock(dsm_registry_table, entry);
+		MemoryContextSwitchTo(oldcontext);
 		ereport(ERROR,
 				(errmsg("requested DSM segment size does not match size of "
 						"existing segment")));
@@ -185,8 +223,11 @@ GetNamedDSMSegment(const char *name, size_t size,
 		{
 			seg = dsm_attach(entry->handle);
 			if (seg == NULL)
+			{
+				dshash_release_lock(dsm_registry_table, entry);
+				MemoryContextSwitchTo(oldcontext);
 				elog(ERROR, "could not map dynamic shared memory segment");
-
+			}
 			dsm_pin_mapping(seg);
 		}
 
@@ -198,3 +239,127 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Detach a named DSM segment
+ *
+ * This routine detaches the DSM segment. If the DSM segment was not attached
+ * by this process, then the routine just returns. on_detach_callback is passed
+ * on to dsm_segment by calling on_dsm_detach for the corresponding dsm_segment
+ * before actually detaching.
+ */
+void
+DetachNamedDSMSegment(const char *name, size_t size,
+					  void (*on_detach_callback) (void *), void *arg)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSM segment name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+		ereport(ERROR,
+				(errmsg("DSM segment name too long")));
+
+	if (size == 0)
+		ereport(ERROR,
+				(errmsg("DSM segment size must be nonzero")));
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	/* Connect to the registry. */
+	init_dsm_registry();
+
+	entry = dshash_find(dsm_registry_table, name, true);
+
+	if (entry == NULL)
+	{
+		MemoryContextSwitchTo(oldcontext);
+		ereport(ERROR,
+				(errmsg("cannot detach a DSM segment that does not exist")));
+	}
+
+	if (entry->size != size)
+	{
+		dshash_release_lock(dsm_registry_table, entry);
+		MemoryContextSwitchTo(oldcontext);
+		ereport(ERROR,
+				(errmsg("requested DSM segment size does not match size of "
+						"existing segment")));
+	}
+
+	detach_dsm_segment(entry->handle, on_detach_callback, arg);
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * Attempt to destroy a named DSM segment
+ *
+ * This routine attempts to destroy the DSM segment. We unpin the dsm_segment
+ * and delete the entry from dsm_registry_table. This may not destroy the
+ * dsm_segment instantly, but it would die out once all the other processes
+ * attached to this dsm_segment either exit or manually detach from the
+ * dsm_segment.
+ *
+ * Because we deleted the key from dsm_registry_table, calling
+ * GetNamedDSMSegment with the same key would result into creating a new
+ * dsm_segment instead of retrieving the old unpinned dsm_segment.
+ */
+void
+DestroyNamedDSMSegment(const char *name, size_t size,
+					   void (*on_detach_callback) (void *), void *arg)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSM segment name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+		ereport(ERROR,
+				(errmsg("DSM segment name too long")));
+
+	if (size == 0)
+		ereport(ERROR,
+				(errmsg("DSM segment size must be nonzero")));
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	/* Connect to the registry. */
+	init_dsm_registry();
+
+	entry = dshash_find(dsm_registry_table, name, true);
+
+	if (entry == NULL)
+	{
+		MemoryContextSwitchTo(oldcontext);
+		ereport(ERROR,
+				(errmsg("cannot destroy a DSM segment that does not exist")));
+	}
+
+	if (entry->size != size)
+	{
+		dshash_release_lock(dsm_registry_table, entry);
+		MemoryContextSwitchTo(oldcontext);
+		ereport(ERROR,
+				(errmsg("requested DSM segment size does not match size of "
+						"existing segment")));
+	}
+
+	detach_dsm_segment(entry->handle, on_detach_callback, arg);
+	dsm_unpin_segment(entry->handle);
+
+	/* dshash_delete_entry calls LWLockRelease internally. We shouldn't
+	 * release lock twice */
+	dshash_delete_entry(dsm_registry_table, entry);
+	dshash_delete_key(dsm_registry_table, name);
+
+	MemoryContextSwitchTo(oldcontext);
+}
\ No newline at end of file
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..2bd20b82ffd 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -17,6 +17,14 @@ extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
 
+extern void DetachNamedDSMSegment(const char *name, size_t size,
+								  void (*on_detach_callback) (void *),
+								  void *arg);
+
+extern void DestroyNamedDSMSegment(const char *name, size_t size,
+								   void (*on_detach_callback) (void *),
+								   void *arg);
+
 extern Size DSMRegistryShmemSize(void);
 extern void DSMRegistryShmemInit(void);
 
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
index 8ffbd343a05..3764aa31bf6 100644
--- a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -12,3 +12,15 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT detach_from_tdr_segment();
+ detach_from_tdr_segment 
+-------------------------
+ t
+(1 row)
+
+SELECT destroy_tdr_segment();
+ destroy_tdr_segment 
+---------------------
+ 
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
index b3351be0a16..24f90658f83 100644
--- a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -2,3 +2,5 @@ CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
 \c
 SELECT get_val_in_shmem();
+SELECT detach_from_tdr_segment();
+SELECT destroy_tdr_segment();
\ No newline at end of file
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
index 8c55b0919b1..74d8dc50d8a 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -8,3 +8,9 @@ CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
 
 CREATE FUNCTION get_val_in_shmem() RETURNS INT
 	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION detach_from_tdr_segment() RETURNS BOOL
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION destroy_tdr_segment() RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
\ No newline at end of file
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
index 462a80f8790..50a38c5fcd2 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -35,6 +35,12 @@ tdr_init_shmem(void *ptr)
 	state->val = 0;
 }
 
+static void
+reset_tdr_state_to_null(void *arg)
+{
+	tdr_state = NULL;
+}
+
 static void
 tdr_attach_shmem(void)
 {
@@ -74,3 +80,25 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_UINT32(ret);
 }
+
+PG_FUNCTION_INFO_V1(detach_from_tdr_segment);
+Datum
+detach_from_tdr_segment(PG_FUNCTION_ARGS)
+{
+	DetachNamedDSMSegment("test_dsm_registry",
+						  sizeof(TestDSMRegistryStruct),
+						  reset_tdr_state_to_null, NULL);
+
+	PG_RETURN_BOOL(tdr_state == NULL);
+}
+
+PG_FUNCTION_INFO_V1(destroy_tdr_segment);
+Datum
+destroy_tdr_segment(PG_FUNCTION_ARGS)
+{
+	DestroyNamedDSMSegment("test_dsm_registry",
+						   sizeof(TestDSMRegistryStruct),
+						   NULL, NULL);
+
+	PG_RETURN_VOID();
+}
\ No newline at end of file
-- 
2.48.1

Reply via email to