add function for creating/attaching hash table in DSM registry

Started by Nathan Bossart7 months ago41 messages
#1Nathan Bossart
nathandbossart@gmail.com
1 attachment(s)

Libraries commonly use shared memory to store hash tables. While it's
possible to set up a dshash table using the DSM registry today, doing so is
complicated; you need to set up two LWLock tranches, a DSA, and finally the
dshash table. The attached patch adds a new function called
GetNamedDSMHash() that makes creating/attaching a hash table in the DSM
registry much easier.

--
nathan

Attachments:

v1-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From cf29232fbc7e6702f15dbc4f0c238fd69ec648a9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v1 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 89 +++++++++++++++++++
 src/include/storage/dsm_registry.h            |  6 +-
 .../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     | 65 ++++++++++++++
 src/tools/pgindent/typedefs.list              |  2 +
 7 files changed, 181 insertions(+), 1 deletion(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..383f160ba92 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,13 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function gurantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -56,6 +63,16 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSMHashState
+{
+	dsa_handle	dsah;
+	dshash_table_handle dshh;
+	int			dsa_tranche;
+	char		dsa_tranche_name[68];	/* name + "_dsa" */
+	int			dsh_tranche;
+	char		dsh_tranche_name[68];	/* name + "_dsh" */
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -198,3 +215,75 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; a new tranche ID will be generated if needed.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState), NULL, found);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranches for the DSA and dshash table. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		state->dsh_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa_tranche_name, "%s_dsa", name);
+		sprintf(state->dsh_tranche_name, "%s_dsh", name);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..be9f2338417 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,14 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..aab1b2a90da 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
@@ -5,6 +5,12 @@ SELECT set_val_in_shmem(1236);
  
 (1 row)
 
+SELECT set_val_in_hash('test', 1414);
+ set_val_in_hash 
+-----------------
+ 
+(1 row)
+
 \c
 SELECT get_val_in_shmem();
  get_val_in_shmem 
@@ -12,3 +18,9 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+            1414
+(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..741c77098e0 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
+SELECT set_val_in_hash('test', 1414);
 \c
 SELECT get_val_in_shmem();
+SELECT get_val_in_hash('test');
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..95fd6586bf5 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 set_val_in_hash(key TEXT, val INT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
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..5449dd8c22c 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,8 +25,24 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	int			val;
+} TestDSMRegistryHashEntry;
+
 static TestDSMRegistryStruct *tdr_state;
 
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
+
+static dshash_table *tdr_hash;
+
 static void
 tdr_init_shmem(void *ptr)
 {
@@ -74,3 +91,51 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_UINT32(ret);
 }
+
+static void
+tdr_attach_hash(void)
+{
+	bool		found;
+
+	if (tdr_hash)
+		return;
+
+	tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = PG_GETARG_INT32(1);
+	bool		found;
+
+	tdr_attach_hash();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	entry->val = val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = -1;
+
+	tdr_attach_hash();
+
+	entry = dshash_find(tdr_hash, key, false);
+	val = entry->val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_INT32(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..fc7c04f2dac 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,7 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +2999,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

In reply to: Nathan Bossart (#1)
Re: add function for creating/attaching hash table in DSM registry

Nathan Bossart <nathandbossart@gmail.com> writes:

+typedef struct NamedDSMHashState
+{
+	dsa_handle	dsah;
+	dshash_table_handle dshh;
+	int			dsa_tranche;
+	char		dsa_tranche_name[68];	/* name + "_dsa" */
+	int			dsh_tranche;
+	char		dsh_tranche_name[68];	/* name + "_dsh" */
+} NamedDSMHashState;

I don't have enough knowledge to review the rest of the patch, but
shouldn't this use NAMEDATALEN, rather than hard-coding the default
length?

- ilmari

#3Sami Imseih
samimseih@gmail.com
In reply to: Dagfinn Ilmari Mannsåker (#2)
Re: add function for creating/attaching hash table in DSM registry

Thanks for this patch! I have implemented this pattern several times,
so this is really helpful.

I have a few early comments, but I plan on trying this out next.

1/

+typedef struct NamedDSMHashState
+{
+     dsa_handle      dsah;
+     dshash_table_handle dshh;
+     int                     dsa_tranche;
+     char            dsa_tranche_name[68];   /* name + "_dsa" */
+     int                     dsh_tranche;
+     char            dsh_tranche_name[68];   /* name + "_dsh" */
+} NamedDSMHashState;

I don't have enough knowledge to review the rest of the patch, but
shouldn't this use NAMEDATALEN, rather than hard-coding the default
length?

NamedLWLockTrancheRequest uses NAMEDATALEN = 64 bytes for the
tranche_name

typedef struct NamedLWLockTrancheRequest
{
char tranche_name[NAMEDATALEN];
int num_lwlocks;
} NamedLWLockTrancheRequest;

but in the case here, "_dsa" or "_dsh" will occupy another 4 bytes.
I think instead of hardcoding, we should #define a length,

i.e. #define NAMEDDSMTRANCHELEN (NAMEDATALEN + 4)

2/ Can you group the dsa and dsh separately. I felt this was a bit
difficult to read?

+               /* Initialize LWLock tranches for the DSA and dshash table. */
+               state->dsa_tranche = LWLockNewTrancheId();
+               state->dsh_tranche = LWLockNewTrancheId();
+               sprintf(state->dsa_tranche_name, "%s_dsa", name);
+               sprintf(state->dsh_tranche_name, "%s_dsh", name);
+               LWLockRegisterTranche(state->dsa_tranche,
state->dsa_tranche_name);
+               LWLockRegisterTranche(state->dsh_tranche,
state->dsh_tranche_name);

3/ It will be good to "Assert(dsh)" before "return dsh;" for safety?

MemoryContextSwitchTo(oldcontext);
LWLockRelease(DSMRegistryLock);

return dsh;
}

--
Sami Imseih
Amazon Web Services (AWS)

#4Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#3)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Thu, Jun 05, 2025 at 01:38:25PM -0500, Sami Imseih wrote:

I have a few early comments, but I plan on trying this out next.

Thanks for reviewing.

+typedef struct NamedDSMHashState
+{
+     dsa_handle      dsah;
+     dshash_table_handle dshh;
+     int                     dsa_tranche;
+     char            dsa_tranche_name[68];   /* name + "_dsa" */
+     int                     dsh_tranche;
+     char            dsh_tranche_name[68];   /* name + "_dsh" */
+} NamedDSMHashState;

I don't have enough knowledge to review the rest of the patch, but
shouldn't this use NAMEDATALEN, rather than hard-coding the default
length?

I straightened this out in v2. I've resisted using NAMEDATALEN because
this stuff is unrelated to the name type. But I have moved all the lengths
and suffixes to macros.

NamedLWLockTrancheRequest uses NAMEDATALEN = 64 bytes for the
tranche_name

typedef struct NamedLWLockTrancheRequest
{
char tranche_name[NAMEDATALEN];
int num_lwlocks;
} NamedLWLockTrancheRequest;

I think the NAMEDATALEN limit only applies to tranches requested at startup
time. LWLockRegisterTranche() just saves whatever pointer you give it, so
AFAICT there's no real limit there.

2/ Can you group the dsa and dsh separately. I felt this was a bit
difficult to read?

+               /* Initialize LWLock tranches for the DSA and dshash table. */
+               state->dsa_tranche = LWLockNewTrancheId();
+               state->dsh_tranche = LWLockNewTrancheId();
+               sprintf(state->dsa_tranche_name, "%s_dsa", name);
+               sprintf(state->dsh_tranche_name, "%s_dsh", name);
+               LWLockRegisterTranche(state->dsa_tranche,
state->dsa_tranche_name);
+               LWLockRegisterTranche(state->dsh_tranche,
state->dsh_tranche_name);

Done.

3/ It will be good to "Assert(dsh)" before "return dsh;" for safety?

MemoryContextSwitchTo(oldcontext);
LWLockRelease(DSMRegistryLock);

return dsh;

Eh, I would expect the tests to start failing horribly if I managed to mess
that up.

--
nathan

Attachments:

v2-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From 201509e26d72f0ac0d817cce54d1ff6fa755f0ba Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v2 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 103 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   6 +-
 .../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     |  65 +++++++++++
 src/tools/pgindent/typedefs.list              |   2 +
 7 files changed, 194 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..f44848ce847 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,13 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function gurantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +39,16 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				64
+
+#define DSMR_DSA_TRANCHE_SUFFIX		"_dsa"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
+#define DSMR_DSH_TRANCHE_SUFFIX		"_dsh"
+#define DSMR_DSH_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSH_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSH_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSH_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +59,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +73,16 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSMHashState
+{
+	dsa_handle	dsah;
+	dshash_table_handle dshh;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_DSH_TRANCHE_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -198,3 +225,77 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; a new tranche ID will be generated if needed.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState), NULL, found);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		sprintf(state->dsh_tranche_name, "%s%s", name, DSMR_DSH_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..be9f2338417 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,14 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..aab1b2a90da 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
@@ -5,6 +5,12 @@ SELECT set_val_in_shmem(1236);
  
 (1 row)
 
+SELECT set_val_in_hash('test', 1414);
+ set_val_in_hash 
+-----------------
+ 
+(1 row)
+
 \c
 SELECT get_val_in_shmem();
  get_val_in_shmem 
@@ -12,3 +18,9 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+            1414
+(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..741c77098e0 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
+SELECT set_val_in_hash('test', 1414);
 \c
 SELECT get_val_in_shmem();
+SELECT get_val_in_hash('test');
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..95fd6586bf5 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 set_val_in_hash(key TEXT, val INT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
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..5449dd8c22c 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,8 +25,24 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	int			val;
+} TestDSMRegistryHashEntry;
+
 static TestDSMRegistryStruct *tdr_state;
 
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
+
+static dshash_table *tdr_hash;
+
 static void
 tdr_init_shmem(void *ptr)
 {
@@ -74,3 +91,51 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_UINT32(ret);
 }
+
+static void
+tdr_attach_hash(void)
+{
+	bool		found;
+
+	if (tdr_hash)
+		return;
+
+	tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = PG_GETARG_INT32(1);
+	bool		found;
+
+	tdr_attach_hash();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	entry->val = val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = -1;
+
+	tdr_attach_hash();
+
+	entry = dshash_find(tdr_hash, key, false);
+	val = entry->val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_INT32(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..fc7c04f2dac 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,7 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +2999,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#5Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#4)
Re: add function for creating/attaching hash table in DSM registry

Thanks, I tested v2 a bit more and did a quick hack of pg_stat_statements just
to get a feel for what it would take to use the new API to move the hash table
from static to dynamic.

One thing I noticed, and I’m not too fond of, is that the wait_event name shows
up with the _dsh suffix:

```
postgres=# select query, wait_event, wait_event_type from pg_stat_activity ;
query | wait_event
| wait_event_type
-------------------------------------------------------------------+------------------------
+-----------------
select 1; | pg_stat_statements_dsh
| LWLock
```

So the suffix isn’t just an internal name, it’s user-facing now. If we really
need to keep this behavior, I think it’s important to document it clearly in
the code.

A few nits also:

1/
+
+static dshash_table *tdr_hash;
+

Isn't it better to initialize this to NULL?

2/

The comment:

```
params is ignored; a new tranche ID will be generated if needed.
```

The "if needed" part isn't necessary here. A new tranche ID will always be
generated, right?

3/ GetNamedDSMSegment is called with "found" as the last argument:

```
state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState), NULL, found);
```

I think it should use a different bool here instead of "found", since that
value isn’t really needed from GetNamedDSMSegment. Also, it refers to
whether the dynamic hash was found, and is set later in the routine.

--
Sami

#6Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#5)
Re: add function for creating/attaching hash table in DSM registry

On Mon, Jun 09, 2025 at 03:10:30PM -0500, Sami Imseih wrote:

One thing I noticed, and I�m not too fond of, is that the wait_event name shows
up with the _dsh suffix:

```
postgres=# select query, wait_event, wait_event_type from pg_stat_activity ;
query | wait_event
| wait_event_type
-------------------------------------------------------------------+------------------------
+-----------------
select 1; | pg_stat_statements_dsh
| LWLock
```

So the suffix isn�t just an internal name, it�s user-facing now. If we really
need to keep this behavior, I think it�s important to document it clearly in
the code.

Okay. FWIW we do use suffixes like "DSA" and "Hash" for the built-in
tranche names (e.g., DSMRegistryDSA and DSMRegistryHash).

+
+static dshash_table *tdr_hash;
+

Isn't it better to initialize this to NULL?

It might be better notationally, but it'll be initialized to NULL either
way.

```
params is ignored; a new tranche ID will be generated if needed.
```

The "if needed" part isn't necessary here. A new tranche ID will always be
generated, right?

Not if the dshash table already exists.

3/ GetNamedDSMSegment is called with "found" as the last argument:

```
state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState), NULL, found);
```

I think it should use a different bool here instead of "found", since that
value isn�t really needed from GetNamedDSMSegment. Also, it refers to
whether the dynamic hash was found, and is set later in the routine.

Yeah, I might as well add another boolean variable called "unused" or
something for clarity here.

--
nathan

#7Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#6)
Re: add function for creating/attaching hash table in DSM registry

On Mon, Jun 09, 2025 at 03:10:30PM -0500, Sami Imseih wrote:

One thing I noticed, and I´m not too fond of, is that the wait_event name shows
up with the _dsh suffix:

```
postgres=# select query, wait_event, wait_event_type from pg_stat_activity ;
query | wait_event
| wait_event_type
-------------------------------------------------------------------+------------------------
+-----------------
select 1; | pg_stat_statements_dsh
| LWLock
```

So the suffix isn´t just an internal name, it´s user-facing now. If we really
need to keep this behavior, I think it´s important to document it clearly in
the code.

Okay. FWIW we do use suffixes like "DSA" and "Hash" for the built-in
tranche names (e.g., DSMRegistryDSA and DSMRegistryHash).

I was surprised that I didn’t get the "extension" wait event based on the logic
in GetLWTrancheName, specifically this portion:

/*
It's an extension tranche, so look in LWLockTrancheNames[]. However,
it's possible that the tranche has never been registered in the current
process, in which case give up and return "extension".
*/

In my test setup, I had two backends with pg_stat_statements enabled and
actively counting queries, so they both registered a dynamic hash. The backend
running pg_stat_activity, however, had pg_stat_statements disabled and did not
register a dynamic hash table.

After enabling pg_stat_statements in the pg_stat_activity backend as well,
thus registering a dynamic hash, I then saw an "extension" wait event appear.

It is not expected behavior IMO, and I still need to debug this a bit more,
but it may be something outside the scope of this patch that the patch just
surfaced.

--
Sami

#8Sami Imseih
samimseih@gmail.com
In reply to: Sami Imseih (#7)
Re: add function for creating/attaching hash table in DSM registry

It is not expected behavior IMO, and I still need to debug this a bit more,
but it may be something outside the scope of this patch that the patch just
surfaced.

It seems I got it backward. If the tranch is registered, then the wait event
name is the one of the tranche, in that case we will see the name of the
tranch suffixed with "_dsh". If the tranche is not registered, then the
wait event name is "extension". We can end up instrumenting with 2 different
wait event names, "extension" or the tranche name, for the same code path.
This looks broken to me, but I am not sure what could be done yet.
It should be taken up for discussion in a separate thread, as it's not
the fault of this patch.

Going back to the original point, DSMRegistryHash and DSMRegistryHash
are built-in, and those names are well-defined and actually refer to
waits related to the mechanism of registering a DSA or a HASH.
I think it will be odd to append "_dsh", but we should at minimum add
a comment in the GetNamedDSMHash explaining this.

--
Sami

#9Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#8)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Mon, Jun 09, 2025 at 07:14:28PM -0500, Sami Imseih wrote:

Going back to the original point, DSMRegistryHash and DSMRegistryHash
are built-in, and those names are well-defined and actually refer to
waits related to the mechanism of registering a DSA or a HASH.
I think it will be odd to append "_dsh", but we should at minimum add
a comment in the GetNamedDSMHash explaining this.

This should be addressed in v3.

I'm not quite following your uneasiness with the tranche names. For the
dshash table, we'll need a tranche for the DSA and one for the hash table,
so presumably any wait events for those locks should be named accordingly,
right?

--
nathan

Attachments:

v3-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From c04bcb37eb623375d334120dbc897b0237c78bf1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v3 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 106 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   6 +-
 .../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     |  65 +++++++++++
 src/tools/pgindent/typedefs.list              |   2 +
 7 files changed, 197 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..c4aa95f5fd1 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,13 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function gurantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +39,16 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				64
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
+#define DSMR_DSH_TRANCHE_SUFFIX		" Hash"
+#define DSMR_DSH_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSH_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSH_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSH_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +59,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +73,16 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSMHashState
+{
+	dsa_handle	dsah;
+	dshash_table_handle dshh;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_DSH_TRANCHE_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -198,3 +225,80 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the lock tranches will be registered with the provided name with " DSA" or
+ * " Hash" appended accordingly.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState), NULL, &unused);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		sprintf(state->dsh_tranche_name, "%s%s", name, DSMR_DSH_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..be9f2338417 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,14 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..aab1b2a90da 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
@@ -5,6 +5,12 @@ SELECT set_val_in_shmem(1236);
  
 (1 row)
 
+SELECT set_val_in_hash('test', 1414);
+ set_val_in_hash 
+-----------------
+ 
+(1 row)
+
 \c
 SELECT get_val_in_shmem();
  get_val_in_shmem 
@@ -12,3 +18,9 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+            1414
+(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..741c77098e0 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
+SELECT set_val_in_hash('test', 1414);
 \c
 SELECT get_val_in_shmem();
+SELECT get_val_in_hash('test');
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..95fd6586bf5 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 set_val_in_hash(key TEXT, val INT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..2321577d04f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,8 +25,24 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	int			val;
+} TestDSMRegistryHashEntry;
+
 static TestDSMRegistryStruct *tdr_state;
 
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
+
+static dshash_table *tdr_hash;
+
 static void
 tdr_init_shmem(void *ptr)
 {
@@ -74,3 +91,51 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(ret);
 }
+
+static void
+tdr_attach_hash(void)
+{
+	bool		found;
+
+	if (tdr_hash)
+		return;
+
+	tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = PG_GETARG_INT32(1);
+	bool		found;
+
+	tdr_attach_hash();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	entry->val = val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = -1;
+
+	tdr_attach_hash();
+
+	entry = dshash_find(tdr_hash, key, false);
+	val = entry->val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_INT32(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..fc7c04f2dac 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,7 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +2999,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#10Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#9)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Tue, Jun 10, 2025 at 10:38:29AM -0500, Nathan Bossart wrote:

On Mon, Jun 09, 2025 at 07:14:28PM -0500, Sami Imseih wrote:

Going back to the original point, DSMRegistryHash and DSMRegistryHash
are built-in, and those names are well-defined and actually refer to
waits related to the mechanism of registering a DSA or a HASH.
I think it will be odd to append "_dsh", but we should at minimum add
a comment in the GetNamedDSMHash explaining this.

This should be addressed in v3.

I'm not quite following your uneasiness with the tranche names. For the
dshash table, we'll need a tranche for the DSA and one for the hash table,
so presumably any wait events for those locks should be named accordingly,
right?

Unrelated, but it'd probably be a good idea to make sure the segment is
initialized instead of assuming it'll be zeroed out (and further assuming
that DSHASH_HANDLE_INVALID is 0)...

--
nathan

Attachments:

v4-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From 4391a017b819be84767e9ba4b42443dd47d420f0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v4 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 122 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   6 +-
 .../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     |  65 ++++++++++
 src/tools/pgindent/typedefs.list              |   2 +
 7 files changed, 213 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..446ca60e43f 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,13 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function gurantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +39,16 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				64
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
+#define DSMR_DSH_TRANCHE_SUFFIX		" Hash"
+#define DSMR_DSH_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSH_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSH_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSH_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +59,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +73,16 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSMHashState
+{
+	dsa_handle	dsah;
+	dshash_table_handle dshh;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_DSH_TRANCHE_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -167,6 +194,8 @@ GetNamedDSMSegment(const char *name, size_t size,
 		entry->size = size;
 		ret = dsm_segment_address(seg);
 
+		memset(ret, 0, size);
+
 		if (init_callback)
 			(*init_callback) (ret);
 	}
@@ -198,3 +227,94 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+static void
+init_named_hash_state(void *ptr)
+{
+	NamedDSMHashState *state = (NamedDSMHashState *) ptr;
+
+	state->dsah = DSA_HANDLE_INVALID;
+	state->dshh = DSHASH_HANDLE_INVALID;
+	state->dsa_tranche = -1;
+	state->dsa_tranche_name[0] = '\0';
+	state->dsh_tranche = -1;
+	state->dsh_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the lock tranches will be registered with the provided name with " DSA" or
+ * " Hash" appended accordingly.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState),
+							   init_named_hash_state, &unused);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		sprintf(state->dsh_tranche_name, "%s%s", name, DSMR_DSH_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..be9f2338417 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,14 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..aab1b2a90da 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
@@ -5,6 +5,12 @@ SELECT set_val_in_shmem(1236);
  
 (1 row)
 
+SELECT set_val_in_hash('test', 1414);
+ set_val_in_hash 
+-----------------
+ 
+(1 row)
+
 \c
 SELECT get_val_in_shmem();
  get_val_in_shmem 
@@ -12,3 +18,9 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+            1414
+(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..741c77098e0 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
+SELECT set_val_in_hash('test', 1414);
 \c
 SELECT get_val_in_shmem();
+SELECT get_val_in_hash('test');
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..95fd6586bf5 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 set_val_in_hash(key TEXT, val INT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..2321577d04f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,8 +25,24 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	int			val;
+} TestDSMRegistryHashEntry;
+
 static TestDSMRegistryStruct *tdr_state;
 
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
+
+static dshash_table *tdr_hash;
+
 static void
 tdr_init_shmem(void *ptr)
 {
@@ -74,3 +91,51 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(ret);
 }
+
+static void
+tdr_attach_hash(void)
+{
+	bool		found;
+
+	if (tdr_hash)
+		return;
+
+	tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = PG_GETARG_INT32(1);
+	bool		found;
+
+	tdr_attach_hash();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	entry->val = val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = -1;
+
+	tdr_attach_hash();
+
+	entry = dshash_find(tdr_hash, key, false);
+	val = entry->val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_INT32(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..fc7c04f2dac 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,7 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +2999,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#11Florents Tselai
florents.tselai@gmail.com
In reply to: Nathan Bossart (#10)
Re: add function for creating/attaching hash table in DSM registry

On 10 Jun 2025, at 7:21 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Tue, Jun 10, 2025 at 10:38:29AM -0500, Nathan Bossart wrote:

On Mon, Jun 09, 2025 at 07:14:28PM -0500, Sami Imseih wrote:

Going back to the original point, DSMRegistryHash and DSMRegistryHash
are built-in, and those names are well-defined and actually refer to
waits related to the mechanism of registering a DSA or a HASH.
I think it will be odd to append "_dsh", but we should at minimum add
a comment in the GetNamedDSMHash explaining this.

This should be addressed in v3.

I'm not quite following your uneasiness with the tranche names. For the
dshash table, we'll need a tranche for the DSA and one for the hash table,
so presumably any wait events for those locks should be named accordingly,
right?

Unrelated, but it'd probably be a good idea to make sure the segment is
initialized instead of assuming it'll be zeroed out (and further assuming
that DSHASH_HANDLE_INVALID is 0)...

--
nathan
<v4-0001-simplify-creating-hash-table-in-dsm-registry.patch>

Love this new API.

Two minor things

a minor typo here
+ * current backend. This function gurantees that only one backend

Since you made the first step towards decoupling DSMR_NAME_LEN from NAMEDATALEN;
is it worth considering increasing this to 128 maybe?

I’ve used DSMR extensively for namespacing keys etc, and I’ve come close to 50-60 chars at times.

I’m not too fixed on that though.

#12Nathan Bossart
nathandbossart@gmail.com
In reply to: Florents Tselai (#11)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Tue, Jun 10, 2025 at 07:47:02PM +0300, Florents Tselai wrote:

Love this new API.

Thanks!

a minor typo here
+ * current backend. This function gurantees that only one backend

Fixed.

Since you made the first step towards decoupling DSMR_NAME_LEN from NAMEDATALEN;
is it worth considering increasing this to 128 maybe?

I�ve used DSMR extensively for namespacing keys etc, and I�ve come close to 50-60 chars at times.

I�m not too fixed on that though.

Seems fine to me.

--
nathan

Attachments:

v5-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From c5dc6135b5fe04257debe5ceb80601a8a11dd720 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v5 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 120 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   6 +-
 .../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     |  65 ++++++++++
 src/tools/pgindent/typedefs.list              |   2 +
 7 files changed, 211 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..8694a448c97 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,13 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +39,16 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
+#define DSMR_DSH_TRANCHE_SUFFIX		" Hash"
+#define DSMR_DSH_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSH_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSH_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSH_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +59,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +73,16 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSMHashState
+{
+	dsa_handle	dsah;
+	dshash_table_handle dshh;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_DSH_TRANCHE_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -198,3 +225,94 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+static void
+init_named_hash_state(void *ptr)
+{
+	NamedDSMHashState *state = (NamedDSMHashState *) ptr;
+
+	state->dsah = DSA_HANDLE_INVALID;
+	state->dshh = DSHASH_HANDLE_INVALID;
+	state->dsa_tranche = -1;
+	state->dsa_tranche_name[0] = '\0';
+	state->dsh_tranche = -1;
+	state->dsh_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the lock tranches will be registered with the provided name with " DSA" or
+ * " Hash" appended accordingly.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState),
+							   init_named_hash_state, &unused);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		sprintf(state->dsh_tranche_name, "%s%s", name, DSMR_DSH_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..be9f2338417 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,14 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..aab1b2a90da 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
@@ -5,6 +5,12 @@ SELECT set_val_in_shmem(1236);
  
 (1 row)
 
+SELECT set_val_in_hash('test', 1414);
+ set_val_in_hash 
+-----------------
+ 
+(1 row)
+
 \c
 SELECT get_val_in_shmem();
  get_val_in_shmem 
@@ -12,3 +18,9 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+            1414
+(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..741c77098e0 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
+SELECT set_val_in_hash('test', 1414);
 \c
 SELECT get_val_in_shmem();
+SELECT get_val_in_hash('test');
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..95fd6586bf5 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 set_val_in_hash(key TEXT, val INT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..2321577d04f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,8 +25,24 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	int			val;
+} TestDSMRegistryHashEntry;
+
 static TestDSMRegistryStruct *tdr_state;
 
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
+
+static dshash_table *tdr_hash;
+
 static void
 tdr_init_shmem(void *ptr)
 {
@@ -74,3 +91,51 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(ret);
 }
+
+static void
+tdr_attach_hash(void)
+{
+	bool		found;
+
+	if (tdr_hash)
+		return;
+
+	tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = PG_GETARG_INT32(1);
+	bool		found;
+
+	tdr_attach_hash();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	entry->val = val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	int			val = -1;
+
+	tdr_attach_hash();
+
+	entry = dshash_find(tdr_hash, key, false);
+	val = entry->val;
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_INT32(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..fc7c04f2dac 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,7 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +2999,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#13Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#12)
Re: add function for creating/attaching hash table in DSM registry

I'm not quite following your uneasiness with the tranche names. For the
dshash table, we'll need a tranche for the DSA and one for the hash table,
so presumably any wait events for those locks should be named accordingly,
right?

I may be alone in this opinion, but I prefer the suffixless tranche name for
the primary LWLock (the hash table), as this is the lock users will encounter
most frequently in wait events, like when adding or looking up entries.

Adding a suffix (e.g., "Hash") may be confusing to the extension. In the tests
that I did with pg_stat_statements, I’d rather see "pg_stat_statements" remain
as-is, rather than "pg_stat_statements Hash".

On the other hand, adding a suffix to the DSA tranche name (e.g.,
"pg_stat_statements DSA") is necessary to differentiate it from the Hash
tranche name, and that is OK because it's not likely to be a user-visible wait
event.

--
Sami

#14Sami Imseih
samimseih@gmail.com
In reply to: Sami Imseih (#13)
Re: add function for creating/attaching hash table in DSM registry

There is also that dynamic tranche named are stored in local backend
look-up table, so if you have some backends that attached some dynamic
hash table
and others that did not, only the ones that registered would be able to
resolve the tranche id to its name.

This is the case which I encountered yesterday, in which I tested 2
backends competing for a LWLock on the dshash table, but a third backend
that did not attach the hashtable reported the wait_event as "extension"
rather than the extension-specified tranche name.

If that third backend attaches the hash table, then it's able to report
the wait_event as the tranche name specified by the extension. So that
could be confusing as 2 wait events could be reported for the same
code path. right?

One way I see around this is for extensions to be able
to register tranches when they are loaded, so every backend knows about
it.Then GetNamedDSMHash could optionally allow you to specify the trancheId
for either DSA or Hash, or both. Otherwise, it would default to the
way this patch
has it now.

--
Sami

#15Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#14)
Re: add function for creating/attaching hash table in DSM registry

On Tue, Jun 10, 2025 at 02:05:16PM -0500, Sami Imseih wrote:

There is also that dynamic tranche named are stored in local backend
look-up table, so if you have some backends that attached some dynamic
hash table
and others that did not, only the ones that registered would be able to
resolve the tranche id to its name.

This is the case which I encountered yesterday, in which I tested 2
backends competing for a LWLock on the dshash table, but a third backend
that did not attach the hashtable reported the wait_event as "extension"
rather than the extension-specified tranche name.

If that third backend attaches the hash table, then it's able to report
the wait_event as the tranche name specified by the extension. So that
could be confusing as 2 wait events could be reported for the same
code path. right?

My initial reaction to this was "well yeah, that's how it's designed." But
after some more research, I see that LWLockRegisterTranche() (commit
ea9df81) predates both the removal of dynamic_shared_memory_type=none
(commit bcbd940) and the introduction of DSAs (commit 13df76a). lwlock.h
even still has this (arguably outdated) comment:

* It may seem strange that each process using the tranche must register it
* separately, but dynamic shared memory segments aren't guaranteed to be
* mapped at the same address in all coordinating backends, so storing the
* registration in the main shared memory segment wouldn't work for that case.

So, if we were adding named LWLocks today, I suspect we might do it
differently. The first thing that comes to mind is that we could store a
shared LWLockTrancheNames table and stop requiring each backend to register
them individually. For a concrete example, the autoprewarm shared memory
initialization code would become something like:

static void
apw_init_state(void *ptr)
{
AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;

LWLockInitialize(&state->lock, LWLockNewTrancheId("autoprewarm"));
...
}

static bool
apw_init_shmem(void)
{
bool found;

apw_state = GetNamedDSMSegment("autoprewarm",
sizeof(AutoPrewarmSharedState),
apw_init_state,
&found);
return found;
}

In short, LWLockNewTrancheId() would gain a new name argument, and
LWLockRegisterTranche() would disappear. We would probably need to be
smart to avoid contention on the name table, but that feels avoidable to
me.

--
nathan

#16Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#15)
Re: add function for creating/attaching hash table in DSM registry

So, if we were adding named LWLocks today, I suspect we might do it
differently. The first thing that comes to mind is that we could store a
shared LWLockTrancheNames table.

+1

and stop requiring each backend to register them individually.

which will prevent odd behavior when a backend does not register
a tranche.

In short, LWLockNewTrancheId() would gain a new name argument, and
LWLockRegisterTranche() would disappear.

That looks sane to me. The only reason LWLockNewTrancheId and
LWLockRegisterTranche are currently separate is because each
backend has to register, so having separate routines is necessary.

We would probably need to be
smart to avoid contention on the name table, but that feels avoidable to

Most of the time, we would be reading and not updating the table, so
contention may not be a big problem.

--
Sami

#17Florents Tselai
florents.tselai@gmail.com
In reply to: Nathan Bossart (#12)
Re: add function for creating/attaching hash table in DSM registry

On 10 Jun 2025, at 8:25 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Tue, Jun 10, 2025 at 07:47:02PM +0300, Florents Tselai wrote:

Love this new API.

Thanks!

a minor typo here
+ * current backend. This function gurantees that only one backend

Fixed.

Since you made the first step towards decoupling DSMR_NAME_LEN from NAMEDATALEN;
is it worth considering increasing this to 128 maybe?

I´ve used DSMR extensively for namespacing keys etc, and I´ve come close to 50-60 chars at times.

I´m not too fixed on that though.

Seems fine to me.

--
nathan
<v5-0001-simplify-creating-hash-table-in-dsm-registry.patch>

While trying to port some existing DSMR code, I came across this limitation:

How can one dsa_allocate in the same area as the returned dshash_table ?
in other words: shouldn't the state->dsa_handle be returned somehow ?

#18Rahila Syed
rahilasyed90@gmail.com
In reply to: Florents Tselai (#17)
Re: add function for creating/attaching hash table in DSM registry

Hi,

Thank you for proposing this enhancement to the DSM registry. It will make
it easier
to use dshash functionality.

While trying to port some existing DSMR code, I came across this
limitation:

How can one dsa_allocate in the same area as the returned dshash_table ?
in other words: shouldn't the state->dsa_handle be returned somehow ?

+1. FWIW, Having used the DSA apis in my code, I think having the registry
return
the mapped dsa address or dsa handle will benefit users who use dsa_allocate
to allocate smaller chunks within the dsa.

#19Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#18)
Re: add function for creating/attaching hash table in DSM registry

On Wed, Jun 11, 2025 at 07:15:56PM +0530, Rahila Syed wrote:

How can one dsa_allocate in the same area as the returned dshash_table ?
in other words: shouldn't the state->dsa_handle be returned somehow ?

+1. FWIW, Having used the DSA apis in my code, I think having the registry
return
the mapped dsa address or dsa handle will benefit users who use dsa_allocate
to allocate smaller chunks within the dsa.

I considered adding another function that would create/attach a DSA in the
DSM registry, since that's already an intermediate step of dshash creation.
We could then use that function to generate the DSA in GetNamedDSMHash().
Would that work for your use-cases, or do you really need to use the same
DSA as the dshash table for some reason?

--
nathan

#20Florents Tselai
florents.tselai@gmail.com
In reply to: Nathan Bossart (#19)
Re: add function for creating/attaching hash table in DSM registry

On 11 Jun 2025, at 4:57 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Wed, Jun 11, 2025 at 07:15:56PM +0530, Rahila Syed wrote:

How can one dsa_allocate in the same area as the returned dshash_table ?
in other words: shouldn't the state->dsa_handle be returned somehow ?

+1. FWIW, Having used the DSA apis in my code, I think having the registry
return
the mapped dsa address or dsa handle will benefit users who use dsa_allocate
to allocate smaller chunks within the dsa.

I considered adding another function that would create/attach a DSA in the
DSM registry, since that's already an intermediate step of dshash creation.
We could then use that function to generate the DSA in GetNamedDSMHash().
Would that work for your use-cases, or do you really need to use the same
DSA as the dshash table for some reason?

In my case the hashtable itself stores dsa_pointers (obviously stuff allocated in the dsa as the hash table itself)
so I think I can’t avoid the necessity of having it.

Unless, you see a good reason not to expose it ?

#21Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#19)
Re: add function for creating/attaching hash table in DSM registry

On Wed, Jun 11, 2025 at 07:15:56PM +0530, Rahila Syed wrote:

How can one dsa_allocate in the same area as the returned dshash_table ?
in other words: shouldn't the state->dsa_handle be returned somehow ?

+1. FWIW, Having used the DSA apis in my code, I think having the

registry

return
the mapped dsa address or dsa handle will benefit users who use

dsa_allocate

to allocate smaller chunks within the dsa.

I considered adding another function that would create/attach a DSA in the
DSM registry, since that's already an intermediate step of dshash creation.
We could then use that function to generate the DSA in GetNamedDSMHash().
Would that work for your use-cases, or do you really need to use the same
DSA as the dshash table for some reason?

This will work for me. Thank you for considering it.

#22Nathan Bossart
nathandbossart@gmail.com
In reply to: Florents Tselai (#20)
Re: add function for creating/attaching hash table in DSM registry

On Wed, Jun 11, 2025 at 05:11:54PM +0300, Florents Tselai wrote:

On 11 Jun 2025, at 4:57 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:
I considered adding another function that would create/attach a DSA in the
DSM registry, since that's already an intermediate step of dshash creation.
We could then use that function to generate the DSA in GetNamedDSMHash().
Would that work for your use-cases, or do you really need to use the same
DSA as the dshash table for some reason?

In my case the hashtable itself stores dsa_pointers (obviously stuff
allocated in the dsa as the hash table itself) so I think I can’t avoid
the necessity of having it.

Is there any reason these DSA pointers can't be for a separate DSA than
what the dshash table is using?

Unless, you see a good reason not to expose it ?

I'm not sure there's any real technical reason, but I guess it feels
cleaner to me to keep the DSM-registry-managed dshash DSAs internal to the
implementation. Presumably messing with that DSA introduces some risk of
breakage, and it could make it more difficult to change implementation
details for the named dshash code in the future.

--
nathan

#23Florents Tselai
florents.tselai@gmail.com
In reply to: Nathan Bossart (#22)
Re: add function for creating/attaching hash table in DSM registry

On 11 Jun 2025, at 5:23 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Wed, Jun 11, 2025 at 05:11:54PM +0300, Florents Tselai wrote:

On 11 Jun 2025, at 4:57 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:
I considered adding another function that would create/attach a DSA in the
DSM registry, since that's already an intermediate step of dshash creation.
We could then use that function to generate the DSA in GetNamedDSMHash().
Would that work for your use-cases, or do you really need to use the same
DSA as the dshash table for some reason?

In my case the hashtable itself stores dsa_pointers (obviously stuff
allocated in the dsa as the hash table itself) so I think I can’t avoid
the necessity of having it.

Is there any reason these DSA pointers can't be for a separate DSA than
what the dshash table is using?

I guess not. You’re right I can decouple them.

Unless, you see a good reason not to expose it ?

I'm not sure there's any real technical reason, but I guess it feels
cleaner to me to keep the DSM-registry-managed dshash DSAs internal to the
implementation. Presumably messing with that DSA introduces some risk of
breakage, and it could make it more difficult to change implementation
details for the named dshash code in the future.

You convinced me there :)

#24Nathan Bossart
nathandbossart@gmail.com
In reply to: Florents Tselai (#23)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

Here is a new patch with GetNamedDSA() added. A couple notes:

* I originally wanted to use GetNamedDSA() within GetNamedDSMHash(), but
that would probably lead to two registry entries per dshash table, and it
didn't really save all that much code, anyway. So, I didn't do that.

* Using a DSA from the registry is cumbersome. You essentially need
another batch of shared memory to keep track of the pointers and do
locking, so it might not be tremendously useful on its own. AFAICT the
easiest thing to do is to store the DSA pointers in a dshash table, which
is what I've done in the test.

--
nathan

Attachments:

v6-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From f08bfe5945ff2661cb9710dedff04237a215dc6b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v6 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 200 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   7 +-
 .../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     |  78 +++++++
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 306 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..42de5e8d604 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,20 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A DSA can be created in or retrieved from the registry by calling
+ * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
+ * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
+ * ensures the DSA is attached to the current backend.  This function
+ * guarantees that only one backend initializes the DSA and that all other
+ * backends just attach it.
+ *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +46,16 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
+#define DSMR_DSH_TRANCHE_SUFFIX		" Hash"
+#define DSMR_DSH_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSH_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSH_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSH_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +66,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +80,21 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSAState
+{
+	dsa_handle	dsah;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+} NamedDSAState;
+
+typedef struct NamedDSMHashState
+{
+	NamedDSAState dsa;
+	dshash_table_handle dshh;
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_DSH_TRANCHE_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -198,3 +237,162 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+static void
+init_named_dsa_state(void *ptr)
+{
+	NamedDSAState *state = (NamedDSAState *) ptr;
+
+	state->dsah = DSA_HANDLE_INVALID;
+	state->dsa_tranche = -1;
+	state->dsa_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named DSA.
+ *
+ * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
+ * generated if needed.  Note that the lock tranche will be registered with the
+ * provided name.
+ */
+dsa_area *
+GetNamedDSA(const char *name, bool *found)
+{
+	NamedDSAState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSAState),
+							   init_named_dsa_state, &unused);
+
+	/* Use a lock to ensure only one process creates the DSA. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dsah == DSA_HANDLE_INVALID)
+	{
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		strcpy(state->dsa_tranche_name, name);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize DSA. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Store handle in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranche for the DSA. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Attach to existing DSA. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsa;
+}
+
+static void
+init_named_hash_state(void *ptr)
+{
+	NamedDSMHashState *state = (NamedDSMHashState *) ptr;
+
+	init_named_dsa_state(&state->dsa);
+
+	state->dshh = DSHASH_HANDLE_INVALID;
+	state->dsh_tranche = -1;
+	state->dsh_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the lock tranches will be registered with the provided name with " DSA" or
+ * " Hash" appended accordingly.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState),
+							   init_named_hash_state, &unused);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa.dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa.dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa.dsa_tranche, state->dsa.dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		sprintf(state->dsh_tranche_name, "%s%s", name, DSMR_DSH_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa.dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsa.dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa.dsa_tranche, state->dsa.dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsa.dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..841bc37af82 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,15 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dsa_area *GetNamedDSA(const char *name, bool *found);
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..8ded82e59d6 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
@@ -5,6 +5,12 @@ SELECT set_val_in_shmem(1236);
  
 (1 row)
 
+SELECT set_val_in_hash('test', '1414');
+ set_val_in_hash 
+-----------------
+ 
+(1 row)
+
 \c
 SELECT get_val_in_shmem();
  get_val_in_shmem 
@@ -12,3 +18,9 @@ SELECT get_val_in_shmem();
              1236
 (1 row)
 
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+ 1414
+(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..c2e25cddaae 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
 SELECT set_val_in_shmem(1236);
+SELECT set_val_in_hash('test', '1414');
 \c
 SELECT get_val_in_shmem();
+SELECT get_val_in_hash('test');
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..5da45155be9 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 set_val_in_hash(key TEXT, val TEXT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS TEXT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..5940a7e7258 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,8 +25,25 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	dsa_handle	val;
+} TestDSMRegistryHashEntry;
+
 static TestDSMRegistryStruct *tdr_state;
 
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
+
+static dshash_table *tdr_hash;
+static dsa_area *tdr_dsa;
+
 static void
 tdr_init_shmem(void *ptr)
 {
@@ -74,3 +92,63 @@ get_val_in_shmem(PG_FUNCTION_ARGS)
 
 	PG_RETURN_INT32(ret);
 }
+
+static void
+tdr_attach_hash(void)
+{
+	bool		found;
+
+	if (tdr_hash)
+		return;
+
+	tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
+	tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	char	   *val = TextDatumGetCString(PG_GETARG_DATUM(1));
+	bool		found;
+
+	if (strlen(key) >= offsetof(TestDSMRegistryHashEntry, val))
+		ereport(ERROR,
+				(errmsg("key too long")));
+
+	tdr_attach_hash();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	if (found)
+		dsa_free(tdr_dsa, entry->val);
+
+	entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);
+	strcpy(dsa_get_address(tdr_dsa, entry->val), val);
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	text	   *val = NULL;
+
+	tdr_attach_hash();
+
+	entry = dshash_find(tdr_hash, key, false);
+	if (entry == NULL)
+		PG_RETURN_NULL();
+
+	val = cstring_to_text(dsa_get_address(tdr_dsa, entry->val));
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_TEXT_P(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..245ab978ecb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,8 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSAState
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +3000,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#25Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#24)
Re: add function for creating/attaching hash table in DSM registry

I tested v6 and I think GetNamedDSA is a good addition. I did
not find any issues with the code. However, I am still convinced
that GetNamedDSMHash should not append " Hash" to the tranche
name of the dshash [0]/messages/by-id/CAA5RZ0sRkH8PfbwFPpYiqQWmSYmbH+Bd0Vro+YZFvwxzed_6eQ@mail.gmail.com. I am ok with " DSA" because the DSA tranche
is created implicitly by the API.

Also, with GetNamedDSA not appending any suffixes, it will be
strange to have some extensions that use both GetNamedDSA
and GetNamedDSMHash finding that one API appends a suffix
and the other does not. but, maybe that's only my view.

[0]: /messages/by-id/CAA5RZ0sRkH8PfbwFPpYiqQWmSYmbH+Bd0Vro+YZFvwxzed_6eQ@mail.gmail.com

--
Sami

#26Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#25)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Wed, Jun 11, 2025 at 05:15:36PM -0500, Sami Imseih wrote:

I tested v6 and I think GetNamedDSA is a good addition. I did
not find any issues with the code. However, I am still convinced
that GetNamedDSMHash should not append " Hash" to the tranche
name of the dshash [0]. I am ok with " DSA" because the DSA tranche
is created implicitly by the API.

Okay, I've done this in the attached patch.

--
nathan

Attachments:

v7-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From 345fa06de44c836c90ce26866a6b7f6b87d1ec6e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v7 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 202 +++++++++++++++++-
 src/include/storage/dsm_registry.h            |   7 +-
 .../expected/test_dsm_registry.out            |  26 ++-
 .../sql/test_dsm_registry.sql                 |   6 +-
 .../test_dsm_registry--1.0.sql                |  10 +-
 .../test_dsm_registry/test_dsm_registry.c     | 114 ++++++++--
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 334 insertions(+), 34 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..962efee5644 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,20 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A DSA can be created in or retrieved from the registry by calling
+ * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
+ * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
+ * ensures the DSA is attached to the current backend.  This function
+ * guarantees that only one backend initializes the DSA and that all other
+ * backends just attach it.
+ *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +46,12 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +62,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +76,21 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSAState
+{
+	dsa_handle	dsah;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+} NamedDSAState;
+
+typedef struct NamedDSMHashState
+{
+	NamedDSAState dsa;
+	dshash_table_handle dshh;
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -125,7 +160,8 @@ init_dsm_registry(void)
  * Initialize or attach a named DSM segment.
  *
  * This routine returns the address of the segment.  init_callback is called to
- * initialize the segment when it is first created.
+ * initialize the segment when it is first created.  Note that should be called
+ * at most once for a given segment in each backend.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
@@ -198,3 +234,165 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+static void
+init_named_dsa_state(void *ptr)
+{
+	NamedDSAState *state = (NamedDSAState *) ptr;
+
+	state->dsah = DSA_HANDLE_INVALID;
+	state->dsa_tranche = -1;
+	state->dsa_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named DSA.
+ *
+ * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
+ * generated if needed.  Note that the lock tranche will be registered with the
+ * provided name.  Also note that this should be called at most once for a
+ * given DSA in each backend.
+ */
+dsa_area *
+GetNamedDSA(const char *name, bool *found)
+{
+	NamedDSAState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSAState),
+							   init_named_dsa_state, &unused);
+
+	/* Use a lock to ensure only one process creates the DSA. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dsah == DSA_HANDLE_INVALID)
+	{
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		strcpy(state->dsa_tranche_name, name);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize DSA. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Store handle in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranche for the DSA. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Attach to existing DSA. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsa;
+}
+
+static void
+init_named_hash_state(void *ptr)
+{
+	NamedDSMHashState *state = (NamedDSMHashState *) ptr;
+
+	init_named_dsa_state(&state->dsa);
+
+	state->dshh = DSHASH_HANDLE_INVALID;
+	state->dsh_tranche = -1;
+	state->dsh_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the DSA lock tranche will be registered with the provided name with " DSA"
+ * appended.  The dshash lock tranche will be registered with the provided
+ * name.  Also note that this should be called at most once for a given table
+ * in each backend.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState),
+							   init_named_hash_state, &unused);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa.dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa.dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa.dsa_tranche, state->dsa.dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		strcpy(state->dsh_tranche_name, name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa.dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsa.dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa.dsa_tranche, state->dsa.dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsa.dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..841bc37af82 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,15 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dsa_area *GetNamedDSA(const char *name, bool *found);
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 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..7ee02bb51e3 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
@@ -1,14 +1,26 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
- set_val_in_shmem 
-------------------
+SELECT set_val_in_dsm(1236);
+ set_val_in_dsm 
+----------------
+ 
+(1 row)
+
+SELECT set_val_in_hash('test', '1414');
+ set_val_in_hash 
+-----------------
  
 (1 row)
 
 \c
-SELECT get_val_in_shmem();
- get_val_in_shmem 
-------------------
-             1236
+SELECT get_val_in_dsm();
+ get_val_in_dsm 
+----------------
+           1236
+(1 row)
+
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+ 1414
 (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..7076f825260 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
+SELECT set_val_in_dsm(1236);
+SELECT set_val_in_hash('test', '1414');
 \c
-SELECT get_val_in_shmem();
+SELECT get_val_in_dsm();
+SELECT get_val_in_hash('test');
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..74ceeccfd3b 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
@@ -3,8 +3,14 @@
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
 
-CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+CREATE FUNCTION set_val_in_dsm(val INT) RETURNS VOID
 	AS 'MODULE_PATHNAME' LANGUAGE C;
 
-CREATE FUNCTION get_val_in_shmem() RETURNS INT
+CREATE FUNCTION get_val_in_dsm() RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION set_val_in_hash(key TEXT, val TEXT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS TEXT
 	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..6be142d00d8 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,15 +25,31 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	dsa_handle	val;
+} TestDSMRegistryHashEntry;
+
+static TestDSMRegistryStruct *tdr_dsm;
+static dsa_area *tdr_dsa;
+static dshash_table *tdr_hash;
+
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
 
 static void
-tdr_init_shmem(void *ptr)
+init_tdr_dsm(void *ptr)
 {
-	TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&state->lck, LWLockNewTrancheId());
-	state->val = 0;
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId());
+	dsm->val = 0;
 }
 
 static void
@@ -40,37 +57,94 @@ tdr_attach_shmem(void)
 {
 	bool		found;
 
-	tdr_state = GetNamedDSMSegment("test_dsm_registry",
-								   sizeof(TestDSMRegistryStruct),
-								   tdr_init_shmem,
-								   &found);
-	LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+	if (tdr_dsm == NULL)
+	{
+		tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
+									 sizeof(TestDSMRegistryStruct),
+									 init_tdr_dsm,
+									 &found);
+		LWLockRegisterTranche(tdr_dsm->lck.tranche, "test_dsm_registry");
+	}
+
+	if (tdr_dsa == NULL)
+		tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
+
+	if (tdr_hash == NULL)
+		tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
 }
 
-PG_FUNCTION_INFO_V1(set_val_in_shmem);
+PG_FUNCTION_INFO_V1(set_val_in_dsm);
 Datum
-set_val_in_shmem(PG_FUNCTION_ARGS)
+set_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
-	tdr_state->val = PG_GETARG_INT32(0);
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_EXCLUSIVE);
+	tdr_dsm->val = PG_GETARG_INT32(0);
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_VOID();
 }
 
-PG_FUNCTION_INFO_V1(get_val_in_shmem);
+PG_FUNCTION_INFO_V1(get_val_in_dsm);
 Datum
-get_val_in_shmem(PG_FUNCTION_ARGS)
+get_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	int			ret;
 
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_SHARED);
-	ret = tdr_state->val;
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_SHARED);
+	ret = tdr_dsm->val;
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_INT32(ret);
 }
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	char	   *val = TextDatumGetCString(PG_GETARG_DATUM(1));
+	bool		found;
+
+	if (strlen(key) >= offsetof(TestDSMRegistryHashEntry, val))
+		ereport(ERROR,
+				(errmsg("key too long")));
+
+	tdr_attach_shmem();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	if (found)
+		dsa_free(tdr_dsa, entry->val);
+
+	entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);
+	strcpy(dsa_get_address(tdr_dsa, entry->val), val);
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	text	   *val = NULL;
+
+	tdr_attach_shmem();
+
+	entry = dshash_find(tdr_hash, key, false);
+	if (entry == NULL)
+		PG_RETURN_NULL();
+
+	val = cstring_to_text(dsa_get_address(tdr_dsa, entry->val));
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_TEXT_P(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..245ab978ecb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,8 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSAState
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +3000,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#27Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#26)
Re: add function for creating/attaching hash table in DSM registry

Okay, I've done this in the attached patch.

Thanks! v7 LGTM.

--
Sami

#28Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#24)
Re: add function for creating/attaching hash table in DSM registry

Hi,

* Using a DSA from the registry is cumbersome. You essentially need
another batch of shared memory to keep track of the pointers and do
locking, so it might not be tremendously useful on its own. AFAICT the
easiest thing to do is to store the DSA pointers in a dshash table, which
is what I've done in the test.

I am considering whether it would be better to avoid creating another DSM
segment to
track the DSA handle.
Would it make more sense to track the DSAs in a separate dshash registry
similar
to DSM segments?

+               /* Attach to existing DSA. */
+               dsa = dsa_attach(state->dsah);
+               dsa_pin_mapping(dsa);
+
+               *found = true;
+       }

Should this also consider the case where dsa is already mapped,
to avoid the error on attaching to the DSA twice? IIUC,
that would require calling dsa equivalent of dsm_find_mapping().

Thank you,
Rahila Syed

#29Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#28)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Fri, Jun 13, 2025 at 08:31:22PM +0530, Rahila Syed wrote:

I am considering whether it would be better to avoid creating another DSM
segment to track the DSA handle. Would it make more sense to track the
DSAs in a separate dshash registry similar to DSM segments?

I don't know if it's better to manage 3 dshash tables than to manage 1 with
special entries for DSAs/dshash tables. There might be some small
trade-offs with each approach, but I haven't thought of anything too
worrisome...

+               /* Attach to existing DSA. */
+               dsa = dsa_attach(state->dsah);
+               dsa_pin_mapping(dsa);
+
+               *found = true;
+       }

Should this also consider the case where dsa is already mapped, to avoid
the error on attaching to the DSA twice? IIUC, that would require calling
dsa equivalent of dsm_find_mapping().

I wanted to find a way to do this, but AFAICT you can't. DSAs and dshash
tables are returned in backend-local memory, so if you lose that pointer, I
don't think there's a totally safe way to recover it. For now, I've
documented that GetNamedDSA()/GetNamedDSMHash() should only be called for a
given DSA/dshash once in each backend.

One other thing we could do is add a dsa_is_attached() function and then
ERROR if you try to reattach an already-attached DSA/dshash. I've done
this in the attached patch.

--
nathan

Attachments:

v8-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From 3ce7d9b9cd3845605d020acdc65cf01d9e61ff8b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v8 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 205 +++++++++++++++++-
 src/backend/utils/mmgr/dsa.c                  |  15 ++
 src/include/storage/dsm_registry.h            |   7 +-
 src/include/utils/dsa.h                       |   1 +
 .../expected/test_dsm_registry.out            |  26 ++-
 .../sql/test_dsm_registry.sql                 |   6 +-
 .../test_dsm_registry--1.0.sql                |  10 +-
 .../test_dsm_registry/test_dsm_registry.c     | 111 ++++++++--
 src/tools/pgindent/typedefs.list              |   3 +
 9 files changed, 351 insertions(+), 33 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..735df82ae77 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,20 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A DSA can be created in or retrieved from the registry by calling
+ * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
+ * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
+ * ensures the DSA is attached to the current backend.  This function
+ * guarantees that only one backend initializes the DSA and that all other
+ * backends just attach it.
+ *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSMHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSMHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +46,12 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -42,7 +62,7 @@ static DSMRegistryCtxStruct *DSMRegistryCtx;
 
 typedef struct DSMRegistryEntry
 {
-	char		name[64];
+	char		name[DSMR_NAME_LEN];
 	dsm_handle	handle;
 	size_t		size;
 } DSMRegistryEntry;
@@ -56,6 +76,21 @@ static const dshash_parameters dsh_params = {
 	LWTRANCHE_DSM_REGISTRY_HASH
 };
 
+typedef struct NamedDSAState
+{
+	dsa_handle	dsah;
+	int			dsa_tranche;
+	char		dsa_tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+} NamedDSAState;
+
+typedef struct NamedDSMHashState
+{
+	NamedDSAState dsa;
+	dshash_table_handle dshh;
+	int			dsh_tranche;
+	char		dsh_tranche_name[DSMR_NAME_LEN];
+} NamedDSMHashState;
+
 static dsa_area *dsm_registry_dsa;
 static dshash_table *dsm_registry_table;
 
@@ -198,3 +233,171 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+static void
+init_named_dsa_state(void *ptr)
+{
+	NamedDSAState *state = (NamedDSAState *) ptr;
+
+	state->dsah = DSA_HANDLE_INVALID;
+	state->dsa_tranche = -1;
+	state->dsa_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named DSA.
+ *
+ * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
+ * generated if needed.  Note that the lock tranche will be registered with the
+ * provided name.  Also note that this should be called at most once for a
+ * given DSA in each backend.
+ */
+dsa_area *
+GetNamedDSA(const char *name, bool *found)
+{
+	NamedDSAState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSAState),
+							   init_named_dsa_state, &unused);
+
+	/* Use a lock to ensure only one process creates the DSA. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dsah == DSA_HANDLE_INVALID)
+	{
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa_tranche = LWLockNewTrancheId();
+		strcpy(state->dsa_tranche_name, name);
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Initialize DSA. */
+		dsa = dsa_create(state->dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Store handle in the DSM segment for other backends to use. */
+		state->dsah = dsa_get_handle(dsa);
+
+		*found = false;
+	}
+	else if (dsa_is_attached(state->dsah))
+		ereport(ERROR,
+				(errmsg("requested DSA already attached to current process")));
+	else
+	{
+		/* Initialize existing LWLock tranche for the DSA. */
+		LWLockRegisterTranche(state->dsa_tranche, state->dsa_tranche_name);
+
+		/* Attach to existing DSA. */
+		dsa = dsa_attach(state->dsah);
+		dsa_pin_mapping(dsa);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsa;
+}
+
+static void
+init_named_hash_state(void *ptr)
+{
+	NamedDSMHashState *state = (NamedDSMHashState *) ptr;
+
+	init_named_dsa_state(&state->dsa);
+
+	state->dshh = DSHASH_HANDLE_INVALID;
+	state->dsh_tranche = -1;
+	state->dsh_tranche_name[0] = '\0';
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the DSA lock tranche will be registered with the provided name with " DSA"
+ * appended.  The dshash lock tranche will be registered with the provided
+ * name.  Also note that this should be called at most once for a given table
+ * in each backend.
+ */
+dshash_table *
+GetNamedDSMHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	NamedDSMHashState *state;
+	MemoryContext oldcontext;
+	dsa_area   *dsa;
+	dshash_table *dsh;
+	bool		unused;
+
+	state = GetNamedDSMSegment(name, sizeof(NamedDSMHashState),
+							   init_named_hash_state, &unused);
+
+	/* Use a lock to ensure only one process creates the table. */
+	LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+	/* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+	oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+	if (state->dshh == DSHASH_HANDLE_INVALID)
+	{
+		dshash_parameters params_copy;
+
+		/* Initialize LWLock tranche for the DSA. */
+		state->dsa.dsa_tranche = LWLockNewTrancheId();
+		sprintf(state->dsa.dsa_tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(state->dsa.dsa_tranche, state->dsa.dsa_tranche_name);
+
+		/* Initialize LWLock tranche for the dshash table. */
+		state->dsh_tranche = LWLockNewTrancheId();
+		strcpy(state->dsh_tranche_name, name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Initialize DSA for the hash table. */
+		dsa = dsa_create(state->dsa.dsa_tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = state->dsh_tranche;
+		dsh = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		state->dsa.dsah = dsa_get_handle(dsa);
+		state->dshh = dshash_get_hash_table_handle(dsh);
+
+		*found = false;
+	}
+	else if (dsa_is_attached(state->dsa.dsah))
+		ereport(ERROR,
+				(errmsg("requested dshash already attached to current process")));
+	else
+	{
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(state->dsa.dsa_tranche, state->dsa.dsa_tranche_name);
+		LWLockRegisterTranche(state->dsh_tranche, state->dsh_tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(state->dsa.dsah);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		dsh = dshash_attach(dsa, params, state->dshh, NULL);
+
+		*found = true;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+	LWLockRelease(DSMRegistryLock);
+
+	return dsh;
+}
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 17d4f7a7a06..be43e9351c3 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -531,6 +531,21 @@ dsa_attach(dsa_handle handle)
 	return area;
 }
 
+/*
+ * Returns whether the area with the given handle was already attached by the
+ * current process.  The area must have been created with dsa_create (not
+ * dsa_create_in_place).
+ */
+bool
+dsa_is_attached(dsa_handle handle)
+{
+	/*
+	 * An area handle is really a DSM segment handle for the first segment, so
+	 * we can just search for that.
+	 */
+	return dsm_find_mapping(handle) != NULL;
+}
+
 /*
  * Attach to an area that was created with dsa_create_in_place.  The caller
  * must somehow know the location in memory that was used when the area was
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..841bc37af82 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,15 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dsa_area *GetNamedDSA(const char *name, bool *found);
+extern dshash_table *GetNamedDSMHash(const char *name,
+									 const dshash_parameters *params,
+									 bool *found);
 extern Size DSMRegistryShmemSize(void);
 extern void DSMRegistryShmemInit(void);
 
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index 9eca8788908..0a6067be628 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -145,6 +145,7 @@ extern dsa_area *dsa_create_in_place_ext(void *place, size_t size,
 										 size_t init_segment_size,
 										 size_t max_segment_size);
 extern dsa_area *dsa_attach(dsa_handle handle);
+extern bool dsa_is_attached(dsa_handle handle);
 extern dsa_area *dsa_attach_in_place(void *place, dsm_segment *segment);
 extern void dsa_release_in_place(void *place);
 extern void dsa_on_dsm_detach_release_in_place(dsm_segment *, Datum);
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..7ee02bb51e3 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
@@ -1,14 +1,26 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
- set_val_in_shmem 
-------------------
+SELECT set_val_in_dsm(1236);
+ set_val_in_dsm 
+----------------
+ 
+(1 row)
+
+SELECT set_val_in_hash('test', '1414');
+ set_val_in_hash 
+-----------------
  
 (1 row)
 
 \c
-SELECT get_val_in_shmem();
- get_val_in_shmem 
-------------------
-             1236
+SELECT get_val_in_dsm();
+ get_val_in_dsm 
+----------------
+           1236
+(1 row)
+
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+ 1414
 (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..7076f825260 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
+SELECT set_val_in_dsm(1236);
+SELECT set_val_in_hash('test', '1414');
 \c
-SELECT get_val_in_shmem();
+SELECT get_val_in_dsm();
+SELECT get_val_in_hash('test');
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..74ceeccfd3b 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
@@ -3,8 +3,14 @@
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
 
-CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+CREATE FUNCTION set_val_in_dsm(val INT) RETURNS VOID
 	AS 'MODULE_PATHNAME' LANGUAGE C;
 
-CREATE FUNCTION get_val_in_shmem() RETURNS INT
+CREATE FUNCTION get_val_in_dsm() RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION set_val_in_hash(key TEXT, val TEXT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS TEXT
 	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..09fa305853a 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,15 +25,31 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	dsa_handle	val;
+} TestDSMRegistryHashEntry;
+
+static TestDSMRegistryStruct *tdr_dsm;
+static dsa_area *tdr_dsa;
+static dshash_table *tdr_hash;
+
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
 
 static void
-tdr_init_shmem(void *ptr)
+init_tdr_dsm(void *ptr)
 {
-	TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&state->lck, LWLockNewTrancheId());
-	state->val = 0;
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId());
+	dsm->val = 0;
 }
 
 static void
@@ -40,37 +57,91 @@ tdr_attach_shmem(void)
 {
 	bool		found;
 
-	tdr_state = GetNamedDSMSegment("test_dsm_registry",
-								   sizeof(TestDSMRegistryStruct),
-								   tdr_init_shmem,
-								   &found);
-	LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
+								 sizeof(TestDSMRegistryStruct),
+								 init_tdr_dsm,
+								 &found);
+	LWLockRegisterTranche(tdr_dsm->lck.tranche, "test_dsm_registry");
+
+	if (tdr_dsa == NULL)
+		tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
+
+	if (tdr_hash == NULL)
+		tdr_hash = GetNamedDSMHash("test_dsm_registry_hash", &dsh_params, &found);
 }
 
-PG_FUNCTION_INFO_V1(set_val_in_shmem);
+PG_FUNCTION_INFO_V1(set_val_in_dsm);
 Datum
-set_val_in_shmem(PG_FUNCTION_ARGS)
+set_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
-	tdr_state->val = PG_GETARG_INT32(0);
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_EXCLUSIVE);
+	tdr_dsm->val = PG_GETARG_INT32(0);
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_VOID();
 }
 
-PG_FUNCTION_INFO_V1(get_val_in_shmem);
+PG_FUNCTION_INFO_V1(get_val_in_dsm);
 Datum
-get_val_in_shmem(PG_FUNCTION_ARGS)
+get_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	int			ret;
 
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_SHARED);
-	ret = tdr_state->val;
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_SHARED);
+	ret = tdr_dsm->val;
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_INT32(ret);
 }
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	char	   *val = TextDatumGetCString(PG_GETARG_DATUM(1));
+	bool		found;
+
+	if (strlen(key) >= offsetof(TestDSMRegistryHashEntry, val))
+		ereport(ERROR,
+				(errmsg("key too long")));
+
+	tdr_attach_shmem();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	if (found)
+		dsa_free(tdr_dsa, entry->val);
+
+	entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);
+	strcpy(dsa_get_address(tdr_dsa, entry->val), val);
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	text	   *val = NULL;
+
+	tdr_attach_shmem();
+
+	entry = dshash_find(tdr_hash, key, false);
+	if (entry == NULL)
+		PG_RETURN_NULL();
+
+	val = cstring_to_text(dsa_get_address(tdr_dsa, entry->val));
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_TEXT_P(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a8346cda633..245ab978ecb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1730,6 +1730,8 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSAState
+NamedDSMHashState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -2998,6 +3000,7 @@ Tcl_NotifierProcs
 Tcl_Obj
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#30Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#29)
Re: add function for creating/attaching hash table in DSM registry

Hi,

Thank you for the updated patch.

Using a DSA from the registry is cumbersome. You essentially need

another batch of shared memory to keep track of the pointers and do

locking, so it might not be tremendously useful on its own

Isn't this true while using dshash from the registry as well?
IIUC, this introduced an overhead of one dsm_create call for every
dsa_create or dshash_create
call.

I don't know if it's better to manage 3 dshash tables than to manage 1 with
special entries for DSAs/dshash tables.

What if we make the DSM registry hash table generic so it can be used for
dsm segments, dsas as well as dshashs?

The DSMRegistryEntry could be modified as follows to contain a dsa_pointer
instead of actual values.
typedef struct DSMRegistryEntry
{
char name[64];
dsa_pointer value;
} DSMRegistryEntry;

This dsa_pointer could point to a memory chunk in the same dsa that's
created by init_dsm_registry
to store the Dshash registry table.

This pointer can be cast to a structure pointer with information about
DSMs, DSAs, or DSHASHs,
based on which one we want to register in the registry.

-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+       char            key[64];
+       dsa_handle      val;
+} TestDSMRegistryHashEntry;

Did you mean to create val as dsa_pointer instead of dsa_handle?
You assigned it a dsa_pointer in set_val_in_hash() function.

+
+       entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);

One other thing we could do is add a dsa_is_attached() function and then
ERROR if you try to reattach an already-attached DSA/dshash. I've done
this in the attached patch.

Sounds good. Not a problem of this patch but it would be great if
we had dsa equivalent of dsm_find_mapping that could actually return a
backend
local reference of the dsa_area.

Thank you,
Rahila Syed

#31Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#30)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Tue, Jun 17, 2025 at 05:43:24PM +0530, Rahila Syed wrote:

Using a DSA from the registry is cumbersome. You essentially need
another batch of shared memory to keep track of the pointers and do
locking, so it might not be tremendously useful on its own

Isn't this true while using dshash from the registry as well?

No, once you have a pointer to the dshash table, you should be able to
access it without any other special runtime-generated pointers.

What if we make the DSM registry hash table generic so it can be used for
dsm segments, dsas as well as dshashs?

The DSMRegistryEntry could be modified as follows to contain a dsa_pointer
instead of actual values.
typedef struct DSMRegistryEntry
{
char name[64];
dsa_pointer value;
} DSMRegistryEntry;

This dsa_pointer could point to a memory chunk in the same dsa that's
created by init_dsm_registry to store the Dshash registry table.

This pointer can be cast to a structure pointer with information about
DSMs, DSAs, or DSHASHs, based on which one we want to register in the
registry.

I like this idea, but I took it one step further in the attached patch and
made the registry entry struct flexible enough to store any type of entry.
Specifically, I've added a new "type" enum followed by a union of the
different structs used to store the entry data. I was originally trying to
avoid this kind of invasive change, but it's not nearly as complicated as I
feared, and there are benefits such as fewer shared memory things to juggle
and better sanity checking. It should also be easy to extend in the
future. WDYT?

-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+       char            key[64];
+       dsa_handle      val;
+} TestDSMRegistryHashEntry;

Did you mean to create val as dsa_pointer instead of dsa_handle?
You assigned it a dsa_pointer in set_val_in_hash() function.

Yes, good catch.

--
nathan

Attachments:

v9-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From 1bc7ab1c4ad4aa420b282cb40e09353fe9349745 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v9 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 263 +++++++++++++++++-
 src/backend/utils/mmgr/dsa.c                  |  15 +
 src/include/storage/dsm_registry.h            |   7 +-
 src/include/utils/dsa.h                       |   1 +
 .../expected/test_dsm_registry.out            |  26 +-
 .../sql/test_dsm_registry.sql                 |   6 +-
 .../test_dsm_registry--1.0.sql                |  10 +-
 .../test_dsm_registry/test_dsm_registry.c     | 111 ++++++--
 src/tools/pgindent/typedefs.list              |   5 +
 9 files changed, 398 insertions(+), 46 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..136fe8b8d7d 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,20 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A DSA can be created in or retrieved from the registry by calling
+ * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
+ * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
+ * ensures the DSA is attached to the current backend.  This function
+ * guarantees that only one backend initializes the DSA and that all other
+ * backends just attach it.
+ *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +46,12 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -40,15 +60,48 @@ typedef struct DSMRegistryCtxStruct
 
 static DSMRegistryCtxStruct *DSMRegistryCtx;
 
-typedef struct DSMRegistryEntry
+typedef struct NamedDSMState
 {
-	char		name[64];
 	dsm_handle	handle;
 	size_t		size;
+} NamedDSMState;
+
+typedef struct NamedDSAState
+{
+	dsa_handle	handle;
+	int			tranche;
+	char		tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+} NamedDSAState;
+
+typedef struct NamedDSHState
+{
+	NamedDSAState dsa;
+	dshash_table_handle handle;
+	int			tranche;
+	char		tranche_name[DSMR_NAME_LEN];
+} NamedDSHState;
+
+typedef enum DSMREntryType
+{
+	DSMR_ENTRY_TYPE_DSM,
+	DSMR_ENTRY_TYPE_DSA,
+	DSMR_ENTRY_TYPE_DSH,
+} DSMREntryType;
+
+typedef struct DSMRegistryEntry
+{
+	char		name[DSMR_NAME_LEN];
+	DSMREntryType type;
+	union
+	{
+		NamedDSMState dsm;
+		NamedDSAState dsa;
+		NamedDSHState dsh;
+	}			data;
 } DSMRegistryEntry;
 
 static const dshash_parameters dsh_params = {
-	offsetof(DSMRegistryEntry, handle),
+	offsetof(DSMRegistryEntry, type),
 	sizeof(DSMRegistryEntry),
 	dshash_strcmp,
 	dshash_strhash,
@@ -141,7 +194,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		ereport(ERROR,
 				(errmsg("DSM segment name cannot be empty")));
 
-	if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
 		ereport(ERROR,
 				(errmsg("DSM segment name too long")));
 
@@ -158,32 +211,39 @@ GetNamedDSMSegment(const char *name, size_t size,
 	entry = dshash_find_or_insert(dsm_registry_table, name, found);
 	if (!(*found))
 	{
+		NamedDSMState *state = &entry->data.dsm;
+		dsm_segment *seg;
+
+		entry->type = DSMR_ENTRY_TYPE_DSM;
+
 		/* Initialize the segment. */
-		dsm_segment *seg = dsm_create(size, 0);
+		seg = dsm_create(size, 0);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
-		entry->handle = dsm_segment_handle(seg);
-		entry->size = size;
+		state->handle = dsm_segment_handle(seg);
+		state->size = size;
 		ret = dsm_segment_address(seg);
 
 		if (init_callback)
 			(*init_callback) (ret);
 	}
-	else if (entry->size != size)
-	{
+	else if (entry->type != DSMR_ENTRY_TYPE_DSM)
 		ereport(ERROR,
-				(errmsg("requested DSM segment size does not match size of "
-						"existing segment")));
-	}
+				(errmsg("requested DSM segment does not match type of existing entry")));
+	else if (entry->data.dsm.size != size)
+		ereport(ERROR,
+				(errmsg("requested DSM segment size does not match size of existing segment")));
 	else
 	{
-		dsm_segment *seg = dsm_find_mapping(entry->handle);
+		NamedDSMState *state = &entry->data.dsm;
+		dsm_segment *seg;
 
 		/* If the existing segment is not already attached, attach it now. */
+		seg = dsm_find_mapping(state->handle);
 		if (seg == NULL)
 		{
-			seg = dsm_attach(entry->handle);
+			seg = dsm_attach(state->handle);
 			if (seg == NULL)
 				elog(ERROR, "could not map dynamic shared memory segment");
 
@@ -198,3 +258,178 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Initialize or attach a named DSA.
+ *
+ * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
+ * generated if needed.  Note that the lock tranche will be registered with the
+ * provided name.  Also note that this should be called at most once for a
+ * given DSA in each backend.
+ */
+dsa_area *
+GetNamedDSA(const char *name, bool *found)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+	dsa_area   *ret;
+
+	Assert(found);
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSA name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
+		ereport(ERROR,
+				(errmsg("DSA name too long")));
+
+	/* 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_or_insert(dsm_registry_table, name, found);
+	if (!(*found))
+	{
+		NamedDSAState *state = &entry->data.dsa;
+
+		entry->type = DSMR_ENTRY_TYPE_DSA;
+
+		/* Initialize the LWLock tranche for the DSA. */
+		state->tranche = LWLockNewTrancheId();
+		strcpy(state->tranche_name, name);
+		LWLockRegisterTranche(state->tranche, state->tranche_name);
+
+		/* Initialize the DSA. */
+		ret = dsa_create(state->tranche);
+		dsa_pin(ret);
+		dsa_pin_mapping(ret);
+
+		/* Store handle for other backends to use. */
+		state->handle = dsa_get_handle(ret);
+	}
+	else if (entry->type != DSMR_ENTRY_TYPE_DSA)
+		ereport(ERROR,
+				(errmsg("requested DSA does not match type of existing entry")));
+	else if (dsa_is_attached(entry->data.dsa.handle))
+		ereport(ERROR,
+				(errmsg("requested DSA already attached to current process")));
+	else
+	{
+		NamedDSAState *state = &entry->data.dsa;
+
+		/* Initialize existing LWLock tranche for the DSA. */
+		LWLockRegisterTranche(state->tranche, state->tranche_name);
+
+		/* Attach to existing DSA. */
+		ret = dsa_attach(state->handle);
+		dsa_pin_mapping(ret);
+	}
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+
+	return ret;
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the DSA lock tranche will be registered with the provided name with " DSA"
+ * appended.  The dshash lock tranche will be registered with the provided
+ * name.  Also note that this should be called at most once for a given table
+ * in each backend.
+ */
+dshash_table *
+GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+	dshash_table *ret;
+
+	Assert(params);
+	Assert(found);
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSHash name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
+		ereport(ERROR,
+				(errmsg("DSHash name too long")));
+
+	/* 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_or_insert(dsm_registry_table, name, found);
+	if (!(*found))
+	{
+		NamedDSAState *dsa_state = &entry->data.dsh.dsa;
+		NamedDSHState *dsh_state = &entry->data.dsh;
+		dshash_parameters params_copy;
+		dsa_area   *dsa;
+
+		entry->type = DSMR_ENTRY_TYPE_DSH;
+
+		/* Initialize the LWLock tranche for the DSA. */
+		dsa_state->tranche = LWLockNewTrancheId();
+		sprintf(dsa_state->tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+
+		/* Initialize the LWLock tranche for the dshash table. */
+		dsh_state->tranche = LWLockNewTrancheId();
+		strcpy(dsh_state->tranche_name, name);
+		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+
+		/* Initialize the DSA for the hash table. */
+		dsa = dsa_create(dsa_state->tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = dsh_state->tranche;
+		ret = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles in the DSM segment for other backends to use. */
+		dsa_state->handle = dsa_get_handle(dsa);
+		dsh_state->handle = dshash_get_hash_table_handle(ret);
+	}
+	else if (entry->type != DSMR_ENTRY_TYPE_DSH)
+		ereport(ERROR,
+				(errmsg("requested DSHash does not match type of existing entry")));
+	else if (dsa_is_attached(entry->data.dsa.handle))
+		ereport(ERROR,
+				(errmsg("requested DSHash already attached to current process")));
+	else
+	{
+		NamedDSAState *dsa_state = &entry->data.dsh.dsa;
+		NamedDSHState *dsh_state = &entry->data.dsh;
+		dsa_area   *dsa;
+
+		/* XXX: Should we verify params matches what table was created with? */
+
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(dsa_state->handle);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		ret = dshash_attach(dsa, params, dsh_state->handle, NULL);
+	}
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+
+	return ret;
+}
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 17d4f7a7a06..be43e9351c3 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -531,6 +531,21 @@ dsa_attach(dsa_handle handle)
 	return area;
 }
 
+/*
+ * Returns whether the area with the given handle was already attached by the
+ * current process.  The area must have been created with dsa_create (not
+ * dsa_create_in_place).
+ */
+bool
+dsa_is_attached(dsa_handle handle)
+{
+	/*
+	 * An area handle is really a DSM segment handle for the first segment, so
+	 * we can just search for that.
+	 */
+	return dsm_find_mapping(handle) != NULL;
+}
+
 /*
  * Attach to an area that was created with dsa_create_in_place.  The caller
  * must somehow know the location in memory that was used when the area was
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..4871ed509eb 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,15 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dsa_area *GetNamedDSA(const char *name, bool *found);
+extern dshash_table *GetNamedDSHash(const char *name,
+									const dshash_parameters *params,
+									bool *found);
 extern Size DSMRegistryShmemSize(void);
 extern void DSMRegistryShmemInit(void);
 
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index 9eca8788908..0a6067be628 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -145,6 +145,7 @@ extern dsa_area *dsa_create_in_place_ext(void *place, size_t size,
 										 size_t init_segment_size,
 										 size_t max_segment_size);
 extern dsa_area *dsa_attach(dsa_handle handle);
+extern bool dsa_is_attached(dsa_handle handle);
 extern dsa_area *dsa_attach_in_place(void *place, dsm_segment *segment);
 extern void dsa_release_in_place(void *place);
 extern void dsa_on_dsm_detach_release_in_place(dsm_segment *, Datum);
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..7ee02bb51e3 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
@@ -1,14 +1,26 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
- set_val_in_shmem 
-------------------
+SELECT set_val_in_dsm(1236);
+ set_val_in_dsm 
+----------------
+ 
+(1 row)
+
+SELECT set_val_in_hash('test', '1414');
+ set_val_in_hash 
+-----------------
  
 (1 row)
 
 \c
-SELECT get_val_in_shmem();
- get_val_in_shmem 
-------------------
-             1236
+SELECT get_val_in_dsm();
+ get_val_in_dsm 
+----------------
+           1236
+(1 row)
+
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+ 1414
 (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..7076f825260 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
+SELECT set_val_in_dsm(1236);
+SELECT set_val_in_hash('test', '1414');
 \c
-SELECT get_val_in_shmem();
+SELECT get_val_in_dsm();
+SELECT get_val_in_hash('test');
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..74ceeccfd3b 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
@@ -3,8 +3,14 @@
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
 
-CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+CREATE FUNCTION set_val_in_dsm(val INT) RETURNS VOID
 	AS 'MODULE_PATHNAME' LANGUAGE C;
 
-CREATE FUNCTION get_val_in_shmem() RETURNS INT
+CREATE FUNCTION get_val_in_dsm() RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION set_val_in_hash(key TEXT, val TEXT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS TEXT
 	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..a9e60c4126b 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,15 +25,31 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	dsa_pointer val;
+} TestDSMRegistryHashEntry;
+
+static TestDSMRegistryStruct *tdr_dsm;
+static dsa_area *tdr_dsa;
+static dshash_table *tdr_hash;
+
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
 
 static void
-tdr_init_shmem(void *ptr)
+init_tdr_dsm(void *ptr)
 {
-	TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&state->lck, LWLockNewTrancheId());
-	state->val = 0;
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId());
+	dsm->val = 0;
 }
 
 static void
@@ -40,37 +57,91 @@ tdr_attach_shmem(void)
 {
 	bool		found;
 
-	tdr_state = GetNamedDSMSegment("test_dsm_registry",
-								   sizeof(TestDSMRegistryStruct),
-								   tdr_init_shmem,
-								   &found);
-	LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
+								 sizeof(TestDSMRegistryStruct),
+								 init_tdr_dsm,
+								 &found);
+	LWLockRegisterTranche(tdr_dsm->lck.tranche, "test_dsm_registry");
+
+	if (tdr_dsa == NULL)
+		tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
+
+	if (tdr_hash == NULL)
+		tdr_hash = GetNamedDSHash("test_dsm_registry_hash", &dsh_params, &found);
 }
 
-PG_FUNCTION_INFO_V1(set_val_in_shmem);
+PG_FUNCTION_INFO_V1(set_val_in_dsm);
 Datum
-set_val_in_shmem(PG_FUNCTION_ARGS)
+set_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
-	tdr_state->val = PG_GETARG_INT32(0);
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_EXCLUSIVE);
+	tdr_dsm->val = PG_GETARG_INT32(0);
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_VOID();
 }
 
-PG_FUNCTION_INFO_V1(get_val_in_shmem);
+PG_FUNCTION_INFO_V1(get_val_in_dsm);
 Datum
-get_val_in_shmem(PG_FUNCTION_ARGS)
+get_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	int			ret;
 
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_SHARED);
-	ret = tdr_state->val;
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_SHARED);
+	ret = tdr_dsm->val;
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_INT32(ret);
 }
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	char	   *val = TextDatumGetCString(PG_GETARG_DATUM(1));
+	bool		found;
+
+	if (strlen(key) >= offsetof(TestDSMRegistryHashEntry, val))
+		ereport(ERROR,
+				(errmsg("key too long")));
+
+	tdr_attach_shmem();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	if (found)
+		dsa_free(tdr_dsa, entry->val);
+
+	entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);
+	strcpy(dsa_get_address(tdr_dsa, entry->val), val);
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	text	   *val = NULL;
+
+	tdr_attach_shmem();
+
+	entry = dshash_find(tdr_hash, key, false);
+	if (entry == NULL)
+		PG_RETURN_NULL();
+
+	val = cstring_to_text(dsa_get_address(tdr_dsa, entry->val));
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_TEXT_P(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 32d6e718adc..651e4e4f741 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -601,6 +601,7 @@ DR_intorel
 DR_printtup
 DR_sqlfunction
 DR_transientrel
+DSMREntryType
 DSMRegistryCtxStruct
 DSMRegistryEntry
 DWORD
@@ -1736,6 +1737,9 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSAState
+NamedDSHState
+NamedDSMState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -3006,6 +3010,7 @@ Tcl_Obj
 Tcl_Size
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#32Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#31)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

On Tue, Jun 17, 2025 at 11:57:33AM -0500, Nathan Bossart wrote:

I like this idea, but I took it one step further in the attached patch and
made the registry entry struct flexible enough to store any type of entry.
Specifically, I've added a new "type" enum followed by a union of the
different structs used to store the entry data. I was originally trying to
avoid this kind of invasive change, but it's not nearly as complicated as I
feared, and there are benefits such as fewer shared memory things to juggle
and better sanity checking. It should also be easy to extend in the
future. WDYT?

Sorry for the noise. I noticed a couple of silly mistakes in v9.

--
nathan

Attachments:

v10-0001-simplify-creating-hash-table-in-dsm-registry.patchtext/plain; charset=us-asciiDownload
From be6f43b6fbb46acabe2709a7d3a444f1059e7616 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 4 Jun 2025 14:21:14 -0500
Subject: [PATCH v10 1/1] simplify creating hash table in dsm registry

---
 src/backend/storage/ipc/dsm_registry.c        | 265 +++++++++++++++++-
 src/backend/utils/mmgr/dsa.c                  |  15 +
 src/include/storage/dsm_registry.h            |   7 +-
 src/include/utils/dsa.h                       |   1 +
 .../expected/test_dsm_registry.out            |  26 +-
 .../sql/test_dsm_registry.sql                 |   6 +-
 .../test_dsm_registry--1.0.sql                |  10 +-
 .../test_dsm_registry/test_dsm_registry.c     | 111 ++++++--
 src/tools/pgindent/typedefs.list              |   5 +
 9 files changed, 400 insertions(+), 46 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..828c2ff0c7f 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,20 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A DSA can be created in or retrieved from the registry by calling
+ * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
+ * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
+ * ensures the DSA is attached to the current backend.  This function
+ * guarantees that only one backend initializes the DSA and that all other
+ * backends just attach it.
+ *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +46,12 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -40,15 +60,48 @@ typedef struct DSMRegistryCtxStruct
 
 static DSMRegistryCtxStruct *DSMRegistryCtx;
 
-typedef struct DSMRegistryEntry
+typedef struct NamedDSMState
 {
-	char		name[64];
 	dsm_handle	handle;
 	size_t		size;
+} NamedDSMState;
+
+typedef struct NamedDSAState
+{
+	dsa_handle	handle;
+	int			tranche;
+	char		tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+} NamedDSAState;
+
+typedef struct NamedDSHState
+{
+	NamedDSAState dsa;
+	dshash_table_handle handle;
+	int			tranche;
+	char		tranche_name[DSMR_NAME_LEN];
+} NamedDSHState;
+
+typedef enum DSMREntryType
+{
+	DSMR_ENTRY_TYPE_DSM,
+	DSMR_ENTRY_TYPE_DSA,
+	DSMR_ENTRY_TYPE_DSH,
+} DSMREntryType;
+
+typedef struct DSMRegistryEntry
+{
+	char		name[DSMR_NAME_LEN];
+	DSMREntryType type;
+	union
+	{
+		NamedDSMState dsm;
+		NamedDSAState dsa;
+		NamedDSHState dsh;
+	}			data;
 } DSMRegistryEntry;
 
 static const dshash_parameters dsh_params = {
-	offsetof(DSMRegistryEntry, handle),
+	offsetof(DSMRegistryEntry, type),
 	sizeof(DSMRegistryEntry),
 	dshash_strcmp,
 	dshash_strhash,
@@ -141,7 +194,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		ereport(ERROR,
 				(errmsg("DSM segment name cannot be empty")));
 
-	if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
 		ereport(ERROR,
 				(errmsg("DSM segment name too long")));
 
@@ -158,32 +211,39 @@ GetNamedDSMSegment(const char *name, size_t size,
 	entry = dshash_find_or_insert(dsm_registry_table, name, found);
 	if (!(*found))
 	{
+		NamedDSMState *state = &entry->data.dsm;
+		dsm_segment *seg;
+
+		entry->type = DSMR_ENTRY_TYPE_DSM;
+
 		/* Initialize the segment. */
-		dsm_segment *seg = dsm_create(size, 0);
+		seg = dsm_create(size, 0);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
-		entry->handle = dsm_segment_handle(seg);
-		entry->size = size;
+		state->handle = dsm_segment_handle(seg);
+		state->size = size;
 		ret = dsm_segment_address(seg);
 
 		if (init_callback)
 			(*init_callback) (ret);
 	}
-	else if (entry->size != size)
-	{
+	else if (entry->type != DSMR_ENTRY_TYPE_DSM)
 		ereport(ERROR,
-				(errmsg("requested DSM segment size does not match size of "
-						"existing segment")));
-	}
+				(errmsg("requested DSM segment does not match type of existing entry")));
+	else if (entry->data.dsm.size != size)
+		ereport(ERROR,
+				(errmsg("requested DSM segment size does not match size of existing segment")));
 	else
 	{
-		dsm_segment *seg = dsm_find_mapping(entry->handle);
+		NamedDSMState *state = &entry->data.dsm;
+		dsm_segment *seg;
 
 		/* If the existing segment is not already attached, attach it now. */
+		seg = dsm_find_mapping(state->handle);
 		if (seg == NULL)
 		{
-			seg = dsm_attach(entry->handle);
+			seg = dsm_attach(state->handle);
 			if (seg == NULL)
 				elog(ERROR, "could not map dynamic shared memory segment");
 
@@ -198,3 +258,180 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Initialize or attach a named DSA.
+ *
+ * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
+ * generated if needed.  Note that the lock tranche will be registered with the
+ * provided name.  Also note that this should be called at most once for a
+ * given DSA in each backend.
+ */
+dsa_area *
+GetNamedDSA(const char *name, bool *found)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+	dsa_area   *ret;
+
+	Assert(found);
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSA name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
+		ereport(ERROR,
+				(errmsg("DSA name too long")));
+
+	/* 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_or_insert(dsm_registry_table, name, found);
+	if (!(*found))
+	{
+		NamedDSAState *state = &entry->data.dsa;
+
+		entry->type = DSMR_ENTRY_TYPE_DSA;
+
+		/* Initialize the LWLock tranche for the DSA. */
+		state->tranche = LWLockNewTrancheId();
+		strcpy(state->tranche_name, name);
+		LWLockRegisterTranche(state->tranche, state->tranche_name);
+
+		/* Initialize the DSA. */
+		ret = dsa_create(state->tranche);
+		dsa_pin(ret);
+		dsa_pin_mapping(ret);
+
+		/* Store handle for other backends to use. */
+		state->handle = dsa_get_handle(ret);
+	}
+	else if (entry->type != DSMR_ENTRY_TYPE_DSA)
+		ereport(ERROR,
+				(errmsg("requested DSA does not match type of existing entry")));
+	else
+	{
+		NamedDSAState *state = &entry->data.dsa;
+
+		if (dsa_is_attached(state->handle))
+			ereport(ERROR,
+					(errmsg("requested DSA already attached to current process")));
+
+		/* Initialize existing LWLock tranche for the DSA. */
+		LWLockRegisterTranche(state->tranche, state->tranche_name);
+
+		/* Attach to existing DSA. */
+		ret = dsa_attach(state->handle);
+		dsa_pin_mapping(ret);
+	}
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+
+	return ret;
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the DSA lock tranche will be registered with the provided name with " DSA"
+ * appended.  The dshash lock tranche will be registered with the provided
+ * name.  Also note that this should be called at most once for a given table
+ * in each backend.
+ */
+dshash_table *
+GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+	dshash_table *ret;
+
+	Assert(params);
+	Assert(found);
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSHash name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
+		ereport(ERROR,
+				(errmsg("DSHash name too long")));
+
+	/* 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_or_insert(dsm_registry_table, name, found);
+	if (!(*found))
+	{
+		NamedDSAState *dsa_state = &entry->data.dsh.dsa;
+		NamedDSHState *dsh_state = &entry->data.dsh;
+		dshash_parameters params_copy;
+		dsa_area   *dsa;
+
+		entry->type = DSMR_ENTRY_TYPE_DSH;
+
+		/* Initialize the LWLock tranche for the DSA. */
+		dsa_state->tranche = LWLockNewTrancheId();
+		sprintf(dsa_state->tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+
+		/* Initialize the LWLock tranche for the dshash table. */
+		dsh_state->tranche = LWLockNewTrancheId();
+		strcpy(dsh_state->tranche_name, name);
+		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+
+		/* Initialize the DSA for the hash table. */
+		dsa = dsa_create(dsa_state->tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = dsh_state->tranche;
+		ret = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles for other backends to use. */
+		dsa_state->handle = dsa_get_handle(dsa);
+		dsh_state->handle = dshash_get_hash_table_handle(ret);
+	}
+	else if (entry->type != DSMR_ENTRY_TYPE_DSH)
+		ereport(ERROR,
+				(errmsg("requested DSHash does not match type of existing entry")));
+	else
+	{
+		NamedDSAState *dsa_state = &entry->data.dsh.dsa;
+		NamedDSHState *dsh_state = &entry->data.dsh;
+		dsa_area   *dsa;
+
+		/* XXX: Should we verify params matches what table was created with? */
+
+		if (dsa_is_attached(dsa_state->handle))
+			ereport(ERROR,
+					(errmsg("requested DSHash already attached to current process")));
+
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(dsa_state->handle);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		ret = dshash_attach(dsa, params, dsh_state->handle, NULL);
+	}
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+
+	return ret;
+}
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 17d4f7a7a06..be43e9351c3 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -531,6 +531,21 @@ dsa_attach(dsa_handle handle)
 	return area;
 }
 
+/*
+ * Returns whether the area with the given handle was already attached by the
+ * current process.  The area must have been created with dsa_create (not
+ * dsa_create_in_place).
+ */
+bool
+dsa_is_attached(dsa_handle handle)
+{
+	/*
+	 * An area handle is really a DSM segment handle for the first segment, so
+	 * we can just search for that.
+	 */
+	return dsm_find_mapping(handle) != NULL;
+}
+
 /*
  * Attach to an area that was created with dsa_create_in_place.  The caller
  * must somehow know the location in memory that was used when the area was
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..4871ed509eb 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,15 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dsa_area *GetNamedDSA(const char *name, bool *found);
+extern dshash_table *GetNamedDSHash(const char *name,
+									const dshash_parameters *params,
+									bool *found);
 extern Size DSMRegistryShmemSize(void);
 extern void DSMRegistryShmemInit(void);
 
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index 9eca8788908..0a6067be628 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -145,6 +145,7 @@ extern dsa_area *dsa_create_in_place_ext(void *place, size_t size,
 										 size_t init_segment_size,
 										 size_t max_segment_size);
 extern dsa_area *dsa_attach(dsa_handle handle);
+extern bool dsa_is_attached(dsa_handle handle);
 extern dsa_area *dsa_attach_in_place(void *place, dsm_segment *segment);
 extern void dsa_release_in_place(void *place);
 extern void dsa_on_dsm_detach_release_in_place(dsm_segment *, Datum);
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..7ee02bb51e3 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
@@ -1,14 +1,26 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
- set_val_in_shmem 
-------------------
+SELECT set_val_in_dsm(1236);
+ set_val_in_dsm 
+----------------
+ 
+(1 row)
+
+SELECT set_val_in_hash('test', '1414');
+ set_val_in_hash 
+-----------------
  
 (1 row)
 
 \c
-SELECT get_val_in_shmem();
- get_val_in_shmem 
-------------------
-             1236
+SELECT get_val_in_dsm();
+ get_val_in_dsm 
+----------------
+           1236
+(1 row)
+
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+ 1414
 (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..7076f825260 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
+SELECT set_val_in_dsm(1236);
+SELECT set_val_in_hash('test', '1414');
 \c
-SELECT get_val_in_shmem();
+SELECT get_val_in_dsm();
+SELECT get_val_in_hash('test');
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..74ceeccfd3b 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
@@ -3,8 +3,14 @@
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
 
-CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+CREATE FUNCTION set_val_in_dsm(val INT) RETURNS VOID
 	AS 'MODULE_PATHNAME' LANGUAGE C;
 
-CREATE FUNCTION get_val_in_shmem() RETURNS INT
+CREATE FUNCTION get_val_in_dsm() RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION set_val_in_hash(key TEXT, val TEXT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS TEXT
 	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..a9e60c4126b 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,15 +25,31 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	dsa_pointer val;
+} TestDSMRegistryHashEntry;
+
+static TestDSMRegistryStruct *tdr_dsm;
+static dsa_area *tdr_dsa;
+static dshash_table *tdr_hash;
+
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
 
 static void
-tdr_init_shmem(void *ptr)
+init_tdr_dsm(void *ptr)
 {
-	TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&state->lck, LWLockNewTrancheId());
-	state->val = 0;
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId());
+	dsm->val = 0;
 }
 
 static void
@@ -40,37 +57,91 @@ tdr_attach_shmem(void)
 {
 	bool		found;
 
-	tdr_state = GetNamedDSMSegment("test_dsm_registry",
-								   sizeof(TestDSMRegistryStruct),
-								   tdr_init_shmem,
-								   &found);
-	LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
+								 sizeof(TestDSMRegistryStruct),
+								 init_tdr_dsm,
+								 &found);
+	LWLockRegisterTranche(tdr_dsm->lck.tranche, "test_dsm_registry");
+
+	if (tdr_dsa == NULL)
+		tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
+
+	if (tdr_hash == NULL)
+		tdr_hash = GetNamedDSHash("test_dsm_registry_hash", &dsh_params, &found);
 }
 
-PG_FUNCTION_INFO_V1(set_val_in_shmem);
+PG_FUNCTION_INFO_V1(set_val_in_dsm);
 Datum
-set_val_in_shmem(PG_FUNCTION_ARGS)
+set_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
-	tdr_state->val = PG_GETARG_INT32(0);
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_EXCLUSIVE);
+	tdr_dsm->val = PG_GETARG_INT32(0);
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_VOID();
 }
 
-PG_FUNCTION_INFO_V1(get_val_in_shmem);
+PG_FUNCTION_INFO_V1(get_val_in_dsm);
 Datum
-get_val_in_shmem(PG_FUNCTION_ARGS)
+get_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	int			ret;
 
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_SHARED);
-	ret = tdr_state->val;
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_SHARED);
+	ret = tdr_dsm->val;
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_INT32(ret);
 }
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	char	   *val = TextDatumGetCString(PG_GETARG_DATUM(1));
+	bool		found;
+
+	if (strlen(key) >= offsetof(TestDSMRegistryHashEntry, val))
+		ereport(ERROR,
+				(errmsg("key too long")));
+
+	tdr_attach_shmem();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	if (found)
+		dsa_free(tdr_dsa, entry->val);
+
+	entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);
+	strcpy(dsa_get_address(tdr_dsa, entry->val), val);
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	text	   *val = NULL;
+
+	tdr_attach_shmem();
+
+	entry = dshash_find(tdr_hash, key, false);
+	if (entry == NULL)
+		PG_RETURN_NULL();
+
+	val = cstring_to_text(dsa_get_address(tdr_dsa, entry->val));
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_TEXT_P(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 32d6e718adc..651e4e4f741 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -601,6 +601,7 @@ DR_intorel
 DR_printtup
 DR_sqlfunction
 DR_transientrel
+DSMREntryType
 DSMRegistryCtxStruct
 DSMRegistryEntry
 DWORD
@@ -1736,6 +1737,9 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSAState
+NamedDSHState
+NamedDSMState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -3006,6 +3010,7 @@ Tcl_Obj
 Tcl_Size
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#33Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#31)
Re: add function for creating/attaching hash table in DSM registry

Hi Nathan,

I like this idea, but I took it one step further in the attached patch and
made the registry entry struct flexible enough to store any type of entry.
Specifically, I've added a new "type" enum followed by a union of the
different structs used to store the entry data. I was originally trying to
avoid this kind of invasive change, but it's not nearly as complicated as I
feared, and there are benefits such as fewer shared memory things to juggle
and better sanity checking. It should also be easy to extend in the
future. WDYT?

Thank you for implementing these changes.
The improvements look good and enhance the feature's utility. I have
already started incorporating
GetNamedDSA into my code to display memory context statistics.

A potential future enhancement could be allowing GetNamedDSHASH to accept
an existing DSA name.
This would enable the DSHASH to reuse a DSA area instead of creating a new
one each time.
I plan to use this registry to store DSA pointers that all belong to the
same DSA area, and this enhancement
would be particularly beneficial. If you find this idea useful, I would be
interested in working on it.

Thank you,
Rahila Syed

#34Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#33)
Re: add function for creating/attaching hash table in DSM registry

On Fri, Jun 20, 2025 at 04:53:52PM +0530, Rahila Syed wrote:

Thank you for implementing these changes.
The improvements look good and enhance the feature's utility. I have
already started incorporating
GetNamedDSA into my code to display memory context statistics.

Great! Thanks for the reviews.

A potential future enhancement could be allowing GetNamedDSHASH to accept
an existing DSA name.
This would enable the DSHASH to reuse a DSA area instead of creating a new
one each time.
I plan to use this registry to store DSA pointers that all belong to the
same DSA area, and this enhancement
would be particularly beneficial. If you find this idea useful, I would be
interested in working on it.

We briefly discussed returning the dshash's DSA upthread [0]/messages/by-id/aEmRcAH1Rmaocz3D@nathan, and using an
existing DSA seems like a variation on that same idea. I'm a little wary
of allowing folks to mess with the named dshash internal DSAs. Not only
does messing with the DSA introduce some risk of breakage, but it could
make it more difficult to change implementation details for the named
dshash code in the future. I suppose we could document the expected usage
and try to do a few sanity checks, though...

[0]: /messages/by-id/aEmRcAH1Rmaocz3D@nathan

--
nathan

#35Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#34)
1 attachment(s)
Re: add function for creating/attaching hash table in DSM registry

Here is what I have staged for commit.

--
nathan

Attachments:

v11-0001-Add-GetNamedDSA-and-GetNamedDSHash.patchtext/plain; charset=iso-8859-1Download
From e344a2757838e6b99168d4aa1093fee4da18d8a8 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 24 Jun 2025 16:32:00 -0500
Subject: [PATCH v11 1/1] Add GetNamedDSA() and GetNamedDSHash().
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Presently, the dynamic shared memory (DSM) registry only provides
GetNamedDSMSegment(), which allocates a fixed-size segment.  To use
the DSM registry for more sophisticated things like dynamic shared
memory areas (DSAs) or a hash table backed by a DSA (dshash), users
need to create a DSM segment that stores various handles and LWLock
tranche IDs and to write fairly complicated initialization code.
Furthermore, there is likely little variation in this
initialization code between libraries.

This commit introduces functions that simplify allocating a DSA or
dshash within the DSM registry.  These functions are very similar
to GetNamedDSMSegment().  Notable differences include the lack of
an initialization callback parameter and the prohibition of calling
the functions more than once for a given entry in each backend
(which should be trivially avoidable in most circumstances).  While
at it, this commit bumps the maximum DSM registry entry name length
from 63 bytes to 127 bytes.

The test_dsm_registry test module contains tests for the new
functions and also serves as a complete usage example.

Reviewed-by: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Reviewed-by: Florents Tselai <florents.tselai@gmail.com>
Reviewed-by: Rahila Syed <rahilasyed90@gmail.com>
Discussion: https://postgres./m/aEC8HGy2tRQjZg_8%40nathan
---
 src/backend/storage/ipc/dsm_registry.c        | 265 +++++++++++++++++-
 src/backend/utils/mmgr/dsa.c                  |  15 +
 src/include/storage/dsm_registry.h            |   7 +-
 src/include/utils/dsa.h                       |   1 +
 .../expected/test_dsm_registry.out            |  26 +-
 .../sql/test_dsm_registry.sql                 |   6 +-
 .../test_dsm_registry--1.0.sql                |  10 +-
 .../test_dsm_registry/test_dsm_registry.c     | 111 ++++++--
 src/tools/pgindent/typedefs.list              |   5 +
 9 files changed, 400 insertions(+), 46 deletions(-)

diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 1d4fd31ffed..828c2ff0c7f 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -15,6 +15,20 @@
  * current backend.  This function guarantees that only one backend
  * initializes the segment and that all other backends just attach it.
  *
+ * A DSA can be created in or retrieved from the registry by calling
+ * GetNamedDSA().  As with GetNamedDSMSegment(), if a DSA with the provided
+ * name does not yet exist, it is created.  Otherwise, GetNamedDSA()
+ * ensures the DSA is attached to the current backend.  This function
+ * guarantees that only one backend initializes the DSA and that all other
+ * backends just attach it.
+ *
+ * A dshash table can be created in or retrieved from the registry by
+ * calling GetNamedDSHash().  As with GetNamedDSMSegment(), if a hash
+ * table with the provided name does not yet exist, it is created.
+ * Otherwise, GetNamedDSHash() ensures the hash table is attached to the
+ * current backend.  This function guarantees that only one backend
+ * initializes the table and that all other backends just attach it.
+ *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -32,6 +46,12 @@
 #include "storage/shmem.h"
 #include "utils/memutils.h"
 
+#define DSMR_NAME_LEN				128
+
+#define DSMR_DSA_TRANCHE_SUFFIX		" DSA"
+#define DSMR_DSA_TRANCHE_SUFFIX_LEN (sizeof(DSMR_DSA_TRANCHE_SUFFIX) - 1)
+#define DSMR_DSA_TRANCHE_NAME_LEN	(DSMR_NAME_LEN + DSMR_DSA_TRANCHE_SUFFIX_LEN)
+
 typedef struct DSMRegistryCtxStruct
 {
 	dsa_handle	dsah;
@@ -40,15 +60,48 @@ typedef struct DSMRegistryCtxStruct
 
 static DSMRegistryCtxStruct *DSMRegistryCtx;
 
-typedef struct DSMRegistryEntry
+typedef struct NamedDSMState
 {
-	char		name[64];
 	dsm_handle	handle;
 	size_t		size;
+} NamedDSMState;
+
+typedef struct NamedDSAState
+{
+	dsa_handle	handle;
+	int			tranche;
+	char		tranche_name[DSMR_DSA_TRANCHE_NAME_LEN];
+} NamedDSAState;
+
+typedef struct NamedDSHState
+{
+	NamedDSAState dsa;
+	dshash_table_handle handle;
+	int			tranche;
+	char		tranche_name[DSMR_NAME_LEN];
+} NamedDSHState;
+
+typedef enum DSMREntryType
+{
+	DSMR_ENTRY_TYPE_DSM,
+	DSMR_ENTRY_TYPE_DSA,
+	DSMR_ENTRY_TYPE_DSH,
+} DSMREntryType;
+
+typedef struct DSMRegistryEntry
+{
+	char		name[DSMR_NAME_LEN];
+	DSMREntryType type;
+	union
+	{
+		NamedDSMState dsm;
+		NamedDSAState dsa;
+		NamedDSHState dsh;
+	}			data;
 } DSMRegistryEntry;
 
 static const dshash_parameters dsh_params = {
-	offsetof(DSMRegistryEntry, handle),
+	offsetof(DSMRegistryEntry, type),
 	sizeof(DSMRegistryEntry),
 	dshash_strcmp,
 	dshash_strhash,
@@ -141,7 +194,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		ereport(ERROR,
 				(errmsg("DSM segment name cannot be empty")));
 
-	if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
 		ereport(ERROR,
 				(errmsg("DSM segment name too long")));
 
@@ -158,32 +211,39 @@ GetNamedDSMSegment(const char *name, size_t size,
 	entry = dshash_find_or_insert(dsm_registry_table, name, found);
 	if (!(*found))
 	{
+		NamedDSMState *state = &entry->data.dsm;
+		dsm_segment *seg;
+
+		entry->type = DSMR_ENTRY_TYPE_DSM;
+
 		/* Initialize the segment. */
-		dsm_segment *seg = dsm_create(size, 0);
+		seg = dsm_create(size, 0);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
-		entry->handle = dsm_segment_handle(seg);
-		entry->size = size;
+		state->handle = dsm_segment_handle(seg);
+		state->size = size;
 		ret = dsm_segment_address(seg);
 
 		if (init_callback)
 			(*init_callback) (ret);
 	}
-	else if (entry->size != size)
-	{
+	else if (entry->type != DSMR_ENTRY_TYPE_DSM)
 		ereport(ERROR,
-				(errmsg("requested DSM segment size does not match size of "
-						"existing segment")));
-	}
+				(errmsg("requested DSM segment does not match type of existing entry")));
+	else if (entry->data.dsm.size != size)
+		ereport(ERROR,
+				(errmsg("requested DSM segment size does not match size of existing segment")));
 	else
 	{
-		dsm_segment *seg = dsm_find_mapping(entry->handle);
+		NamedDSMState *state = &entry->data.dsm;
+		dsm_segment *seg;
 
 		/* If the existing segment is not already attached, attach it now. */
+		seg = dsm_find_mapping(state->handle);
 		if (seg == NULL)
 		{
-			seg = dsm_attach(entry->handle);
+			seg = dsm_attach(state->handle);
 			if (seg == NULL)
 				elog(ERROR, "could not map dynamic shared memory segment");
 
@@ -198,3 +258,180 @@ GetNamedDSMSegment(const char *name, size_t size,
 
 	return ret;
 }
+
+/*
+ * Initialize or attach a named DSA.
+ *
+ * This routine returns a pointer to the DSA.  A new LWLock tranche ID will be
+ * generated if needed.  Note that the lock tranche will be registered with the
+ * provided name.  Also note that this should be called at most once for a
+ * given DSA in each backend.
+ */
+dsa_area *
+GetNamedDSA(const char *name, bool *found)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+	dsa_area   *ret;
+
+	Assert(found);
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSA name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
+		ereport(ERROR,
+				(errmsg("DSA name too long")));
+
+	/* 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_or_insert(dsm_registry_table, name, found);
+	if (!(*found))
+	{
+		NamedDSAState *state = &entry->data.dsa;
+
+		entry->type = DSMR_ENTRY_TYPE_DSA;
+
+		/* Initialize the LWLock tranche for the DSA. */
+		state->tranche = LWLockNewTrancheId();
+		strcpy(state->tranche_name, name);
+		LWLockRegisterTranche(state->tranche, state->tranche_name);
+
+		/* Initialize the DSA. */
+		ret = dsa_create(state->tranche);
+		dsa_pin(ret);
+		dsa_pin_mapping(ret);
+
+		/* Store handle for other backends to use. */
+		state->handle = dsa_get_handle(ret);
+	}
+	else if (entry->type != DSMR_ENTRY_TYPE_DSA)
+		ereport(ERROR,
+				(errmsg("requested DSA does not match type of existing entry")));
+	else
+	{
+		NamedDSAState *state = &entry->data.dsa;
+
+		if (dsa_is_attached(state->handle))
+			ereport(ERROR,
+					(errmsg("requested DSA already attached to current process")));
+
+		/* Initialize existing LWLock tranche for the DSA. */
+		LWLockRegisterTranche(state->tranche, state->tranche_name);
+
+		/* Attach to existing DSA. */
+		ret = dsa_attach(state->handle);
+		dsa_pin_mapping(ret);
+	}
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+
+	return ret;
+}
+
+/*
+ * Initialize or attach a named dshash table.
+ *
+ * This routine returns the address of the table.  The tranche_id member of
+ * params is ignored; new tranche IDs will be generated if needed.  Note that
+ * the DSA lock tranche will be registered with the provided name with " DSA"
+ * appended.  The dshash lock tranche will be registered with the provided
+ * name.  Also note that this should be called at most once for a given table
+ * in each backend.
+ */
+dshash_table *
+GetNamedDSHash(const char *name, const dshash_parameters *params, bool *found)
+{
+	DSMRegistryEntry *entry;
+	MemoryContext oldcontext;
+	dshash_table *ret;
+
+	Assert(params);
+	Assert(found);
+
+	if (!name || *name == '\0')
+		ereport(ERROR,
+				(errmsg("DSHash name cannot be empty")));
+
+	if (strlen(name) >= offsetof(DSMRegistryEntry, type))
+		ereport(ERROR,
+				(errmsg("DSHash name too long")));
+
+	/* 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_or_insert(dsm_registry_table, name, found);
+	if (!(*found))
+	{
+		NamedDSAState *dsa_state = &entry->data.dsh.dsa;
+		NamedDSHState *dsh_state = &entry->data.dsh;
+		dshash_parameters params_copy;
+		dsa_area   *dsa;
+
+		entry->type = DSMR_ENTRY_TYPE_DSH;
+
+		/* Initialize the LWLock tranche for the DSA. */
+		dsa_state->tranche = LWLockNewTrancheId();
+		sprintf(dsa_state->tranche_name, "%s%s", name, DSMR_DSA_TRANCHE_SUFFIX);
+		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+
+		/* Initialize the LWLock tranche for the dshash table. */
+		dsh_state->tranche = LWLockNewTrancheId();
+		strcpy(dsh_state->tranche_name, name);
+		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+
+		/* Initialize the DSA for the hash table. */
+		dsa = dsa_create(dsa_state->tranche);
+		dsa_pin(dsa);
+		dsa_pin_mapping(dsa);
+
+		/* Initialize the dshash table. */
+		memcpy(&params_copy, params, sizeof(dshash_parameters));
+		params_copy.tranche_id = dsh_state->tranche;
+		ret = dshash_create(dsa, &params_copy, NULL);
+
+		/* Store handles for other backends to use. */
+		dsa_state->handle = dsa_get_handle(dsa);
+		dsh_state->handle = dshash_get_hash_table_handle(ret);
+	}
+	else if (entry->type != DSMR_ENTRY_TYPE_DSH)
+		ereport(ERROR,
+				(errmsg("requested DSHash does not match type of existing entry")));
+	else
+	{
+		NamedDSAState *dsa_state = &entry->data.dsh.dsa;
+		NamedDSHState *dsh_state = &entry->data.dsh;
+		dsa_area   *dsa;
+
+		/* XXX: Should we verify params matches what table was created with? */
+
+		if (dsa_is_attached(dsa_state->handle))
+			ereport(ERROR,
+					(errmsg("requested DSHash already attached to current process")));
+
+		/* Initialize existing LWLock tranches for the DSA and dshash table. */
+		LWLockRegisterTranche(dsa_state->tranche, dsa_state->tranche_name);
+		LWLockRegisterTranche(dsh_state->tranche, dsh_state->tranche_name);
+
+		/* Attach to existing DSA for the hash table. */
+		dsa = dsa_attach(dsa_state->handle);
+		dsa_pin_mapping(dsa);
+
+		/* Attach to existing dshash table. */
+		ret = dshash_attach(dsa, params, dsh_state->handle, NULL);
+	}
+
+	dshash_release_lock(dsm_registry_table, entry);
+	MemoryContextSwitchTo(oldcontext);
+
+	return ret;
+}
diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c
index 17d4f7a7a06..be43e9351c3 100644
--- a/src/backend/utils/mmgr/dsa.c
+++ b/src/backend/utils/mmgr/dsa.c
@@ -531,6 +531,21 @@ dsa_attach(dsa_handle handle)
 	return area;
 }
 
+/*
+ * Returns whether the area with the given handle was already attached by the
+ * current process.  The area must have been created with dsa_create (not
+ * dsa_create_in_place).
+ */
+bool
+dsa_is_attached(dsa_handle handle)
+{
+	/*
+	 * An area handle is really a DSM segment handle for the first segment, so
+	 * we can just search for that.
+	 */
+	return dsm_find_mapping(handle) != NULL;
+}
+
 /*
  * Attach to an area that was created with dsa_create_in_place.  The caller
  * must somehow know the location in memory that was used when the area was
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index b381e44bc9d..4871ed509eb 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -13,10 +13,15 @@
 #ifndef DSM_REGISTRY_H
 #define DSM_REGISTRY_H
 
+#include "lib/dshash.h"
+
 extern void *GetNamedDSMSegment(const char *name, size_t size,
 								void (*init_callback) (void *ptr),
 								bool *found);
-
+extern dsa_area *GetNamedDSA(const char *name, bool *found);
+extern dshash_table *GetNamedDSHash(const char *name,
+									const dshash_parameters *params,
+									bool *found);
 extern Size DSMRegistryShmemSize(void);
 extern void DSMRegistryShmemInit(void);
 
diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h
index 9eca8788908..0a6067be628 100644
--- a/src/include/utils/dsa.h
+++ b/src/include/utils/dsa.h
@@ -145,6 +145,7 @@ extern dsa_area *dsa_create_in_place_ext(void *place, size_t size,
 										 size_t init_segment_size,
 										 size_t max_segment_size);
 extern dsa_area *dsa_attach(dsa_handle handle);
+extern bool dsa_is_attached(dsa_handle handle);
 extern dsa_area *dsa_attach_in_place(void *place, dsm_segment *segment);
 extern void dsa_release_in_place(void *place);
 extern void dsa_on_dsm_detach_release_in_place(dsm_segment *, Datum);
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..7ee02bb51e3 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
@@ -1,14 +1,26 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
- set_val_in_shmem 
-------------------
+SELECT set_val_in_dsm(1236);
+ set_val_in_dsm 
+----------------
+ 
+(1 row)
+
+SELECT set_val_in_hash('test', '1414');
+ set_val_in_hash 
+-----------------
  
 (1 row)
 
 \c
-SELECT get_val_in_shmem();
- get_val_in_shmem 
-------------------
-             1236
+SELECT get_val_in_dsm();
+ get_val_in_dsm 
+----------------
+           1236
+(1 row)
+
+SELECT get_val_in_hash('test');
+ get_val_in_hash 
+-----------------
+ 1414
 (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..7076f825260 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
@@ -1,4 +1,6 @@
 CREATE EXTENSION test_dsm_registry;
-SELECT set_val_in_shmem(1236);
+SELECT set_val_in_dsm(1236);
+SELECT set_val_in_hash('test', '1414');
 \c
-SELECT get_val_in_shmem();
+SELECT get_val_in_dsm();
+SELECT get_val_in_hash('test');
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..74ceeccfd3b 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
@@ -3,8 +3,14 @@
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
 
-CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+CREATE FUNCTION set_val_in_dsm(val INT) RETURNS VOID
 	AS 'MODULE_PATHNAME' LANGUAGE C;
 
-CREATE FUNCTION get_val_in_shmem() RETURNS INT
+CREATE FUNCTION get_val_in_dsm() RETURNS INT
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION set_val_in_hash(key TEXT, val TEXT) RETURNS VOID
+	AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_hash(key TEXT) RETURNS TEXT
 	AS 'MODULE_PATHNAME' LANGUAGE C;
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 96a890be228..a9e60c4126b 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -15,6 +15,7 @@
 #include "fmgr.h"
 #include "storage/dsm_registry.h"
 #include "storage/lwlock.h"
+#include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
 
@@ -24,15 +25,31 @@ typedef struct TestDSMRegistryStruct
 	LWLock		lck;
 } TestDSMRegistryStruct;
 
-static TestDSMRegistryStruct *tdr_state;
+typedef struct TestDSMRegistryHashEntry
+{
+	char		key[64];
+	dsa_pointer val;
+} TestDSMRegistryHashEntry;
+
+static TestDSMRegistryStruct *tdr_dsm;
+static dsa_area *tdr_dsa;
+static dshash_table *tdr_hash;
+
+static const dshash_parameters dsh_params = {
+	offsetof(TestDSMRegistryHashEntry, val),
+	sizeof(TestDSMRegistryHashEntry),
+	dshash_strcmp,
+	dshash_strhash,
+	dshash_strcpy
+};
 
 static void
-tdr_init_shmem(void *ptr)
+init_tdr_dsm(void *ptr)
 {
-	TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&state->lck, LWLockNewTrancheId());
-	state->val = 0;
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId());
+	dsm->val = 0;
 }
 
 static void
@@ -40,37 +57,91 @@ tdr_attach_shmem(void)
 {
 	bool		found;
 
-	tdr_state = GetNamedDSMSegment("test_dsm_registry",
-								   sizeof(TestDSMRegistryStruct),
-								   tdr_init_shmem,
-								   &found);
-	LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
+								 sizeof(TestDSMRegistryStruct),
+								 init_tdr_dsm,
+								 &found);
+	LWLockRegisterTranche(tdr_dsm->lck.tranche, "test_dsm_registry");
+
+	if (tdr_dsa == NULL)
+		tdr_dsa = GetNamedDSA("test_dsm_registry_dsa", &found);
+
+	if (tdr_hash == NULL)
+		tdr_hash = GetNamedDSHash("test_dsm_registry_hash", &dsh_params, &found);
 }
 
-PG_FUNCTION_INFO_V1(set_val_in_shmem);
+PG_FUNCTION_INFO_V1(set_val_in_dsm);
 Datum
-set_val_in_shmem(PG_FUNCTION_ARGS)
+set_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
-	tdr_state->val = PG_GETARG_INT32(0);
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_EXCLUSIVE);
+	tdr_dsm->val = PG_GETARG_INT32(0);
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_VOID();
 }
 
-PG_FUNCTION_INFO_V1(get_val_in_shmem);
+PG_FUNCTION_INFO_V1(get_val_in_dsm);
 Datum
-get_val_in_shmem(PG_FUNCTION_ARGS)
+get_val_in_dsm(PG_FUNCTION_ARGS)
 {
 	int			ret;
 
 	tdr_attach_shmem();
 
-	LWLockAcquire(&tdr_state->lck, LW_SHARED);
-	ret = tdr_state->val;
-	LWLockRelease(&tdr_state->lck);
+	LWLockAcquire(&tdr_dsm->lck, LW_SHARED);
+	ret = tdr_dsm->val;
+	LWLockRelease(&tdr_dsm->lck);
 
 	PG_RETURN_INT32(ret);
 }
+
+PG_FUNCTION_INFO_V1(set_val_in_hash);
+Datum
+set_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	char	   *val = TextDatumGetCString(PG_GETARG_DATUM(1));
+	bool		found;
+
+	if (strlen(key) >= offsetof(TestDSMRegistryHashEntry, val))
+		ereport(ERROR,
+				(errmsg("key too long")));
+
+	tdr_attach_shmem();
+
+	entry = dshash_find_or_insert(tdr_hash, key, &found);
+	if (found)
+		dsa_free(tdr_dsa, entry->val);
+
+	entry->val = dsa_allocate(tdr_dsa, strlen(val) + 1);
+	strcpy(dsa_get_address(tdr_dsa, entry->val), val);
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_hash);
+Datum
+get_val_in_hash(PG_FUNCTION_ARGS)
+{
+	TestDSMRegistryHashEntry *entry;
+	char	   *key = TextDatumGetCString(PG_GETARG_DATUM(0));
+	text	   *val = NULL;
+
+	tdr_attach_shmem();
+
+	entry = dshash_find(tdr_hash, key, false);
+	if (entry == NULL)
+		PG_RETURN_NULL();
+
+	val = cstring_to_text(dsa_get_address(tdr_dsa, entry->val));
+
+	dshash_release_lock(tdr_hash, entry);
+
+	PG_RETURN_TEXT_P(val);
+}
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 32d6e718adc..651e4e4f741 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -601,6 +601,7 @@ DR_intorel
 DR_printtup
 DR_sqlfunction
 DR_transientrel
+DSMREntryType
 DSMRegistryCtxStruct
 DSMRegistryEntry
 DWORD
@@ -1736,6 +1737,9 @@ Name
 NameData
 NameHashEntry
 NamedArgExpr
+NamedDSAState
+NamedDSHState
+NamedDSMState
 NamedLWLockTranche
 NamedLWLockTrancheRequest
 NamedTuplestoreScan
@@ -3006,6 +3010,7 @@ Tcl_Obj
 Tcl_Size
 Tcl_Time
 TempNamespaceStatus
+TestDSMRegistryHashEntry
 TestDSMRegistryStruct
 TestDecodingData
 TestDecodingTxnData
-- 
2.39.5 (Apple Git-154)

#36Rahila Syed
rahilasyed90@gmail.com
In reply to: Nathan Bossart (#35)
Re: add function for creating/attaching hash table in DSM registry

Hi,

Here is what I have staged for commit.

Thank you for sharing the updated patch.
This looks good to me. I used the GetNamedDSA added by this patch in my
code and it worked fine.

/* XXX: Should we verify params matches what table was created with? */

Are you planning to address the above before you commit? It seems like a
helpful check since GetNamedDshash
takes the params as arguments. That said, I don't have a strong preference
either way.

I have a question: is there a way to remove the entries from the registry
and free the space?
For example, if a user decides to call dshash_destroy the dshash entry in
the registry would no longer be needed.

Thank you,
Rahila Syed

#37Nathan Bossart
nathandbossart@gmail.com
In reply to: Rahila Syed (#36)
Re: add function for creating/attaching hash table in DSM registry

On Mon, Jun 30, 2025 at 01:29:22PM +0530, Rahila Syed wrote:

/* XXX: Should we verify params matches what table was created with? */

Are you planning to address the above before you commit? It seems like a
helpful check since GetNamedDshash takes the params as arguments. That
said, I don't have a strong preference either way.

I was not planning on this, primarily because I'm not sure about comparing
the function pointers. Note the following comment above the declaration of
dshash_parameters:

* Compare, hash, and copy functions must be supplied even when attaching,
* because we can't safely share function pointers between backends in general.
* The user data pointer supplied to the create and attach functions will be
* passed to these functions.

I have a question: is there a way to remove the entries from the registry
and free the space? For example, if a user decides to call
dshash_destroy the dshash entry in the registry would no longer be
needed.

See the following thread:

/messages/by-id/flat/CAAdDe3N=j8mbkJJhmU6hTQRUXKEQMoJWsQz7JZyVK=rDWnVdiA@mail.gmail.com

--
nathan

#38Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#37)
Re: add function for creating/attaching hash table in DSM registry

Committed.

--
nathan

#39Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#38)
Re: add function for creating/attaching hash table in DSM registry

Oops, it looks like renaming the test_dsm_registry functions wasn't a good
idea:

https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=crake&amp;dt=2025-07-02%2017%3A11%3A10

Will fix...

--
nathan

#40Nathan Bossart
nathandbossart@gmail.com
In reply to: Nathan Bossart (#39)
Re: add function for creating/attaching hash table in DSM registry

On Wed, Jul 02, 2025 at 12:37:37PM -0500, Nathan Bossart wrote:

Oops, it looks like renaming the test_dsm_registry functions wasn't a good
idea:

https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=crake&amp;dt=2025-07-02%2017%3A11%3A10

Will fix...

Seems to be fixed with commit 0c2b717.

--
nathan

#41Florents Tselai
florents.tselai@gmail.com
In reply to: Nathan Bossart (#40)
Re: add function for creating/attaching hash table in DSM registry

On 2 Jul 2025, at 10:11 PM, Nathan Bossart <nathandbossart@gmail.com> wrote:

On Wed, Jul 02, 2025 at 12:37:37PM -0500, Nathan Bossart wrote:

Oops, it looks like renaming the test_dsm_registry functions wasn't a good
idea:

https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=crake&amp;dt=2025-07-02%2017%3A11%3A10

Will fix...

Seems to be fixed with commit 0c2b717.

It ocurred to me that no one raised the point for documenting these in xfunc;
I think they should be.

I toolk the liberty of opening a new item & patch for that
https://commitfest.postgresql.org/patch/5914/