Proposal: Add a callback data parameter to GetNamedDSMSegment

Started by Zsolt Parragiabout 1 month ago30 messages
#1Zsolt Parragi
zsolt.parragi@percona.com
1 attachment(s)

Hello hackers,

While developing an extension and trying to write some generic code
around DSM areas, I noticed a limitation: although GetNamedDSMSegment
accepts a callback function, there is no way to pass additional
arguments to that callback.

For example, the documentation for creating LWLocks after startup [1]https://www.postgresql.org/docs/current/xfunc-c.html#XFUNC-ADDIN-LWLOCKS-AFTER-STARTUP
suggests creating locks in this callback. That works fine as long as
the callback only needs to create a hardcoded lock. But if the lock
name is a parameter to the function calling GetNamedDSMSegment, and
not fixed, I do not see a clean way to pass it through to the callback
(short of relying on global variables, for example).

As a proper solution for this, and possibly for other similar issues,
I propose adding a generic callback argument to GetNamedDSMSegment
that can be forwarded to the callback function.

What do you think about this?

[1]: https://www.postgresql.org/docs/current/xfunc-c.html#XFUNC-ADDIN-LWLOCKS-AFTER-STARTUP

Attachments:

0001-Add-a-callback-data-parameter-to-GetNamedDSMSegment.patchapplication/octet-stream; name=0001-Add-a-callback-data-parameter-to-GetNamedDSMSegment.patchDownload
From bbdf4e76ad8d1ea2504a16669f4dfcd6cdcee3a1 Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Tue, 2 Dec 2025 20:01:33 +0000
Subject: [PATCH] Add a callback data parameter to GetNamedDSMSegment

This function already supports a callback, but there is no way to pass
user data to that callback.

For example, the documentation notes that when using GetNamedDSMSegment,
LWLocks should be initialized in the init callback. However, if the calling
code is generic (for example, when it receives a tranche_name as a
parameter), there is no clean way to pass that parameter into the init
callback.

This change addresses that limitation by adding a generic callback argument
that is forwarded to the callback function.
---
 contrib/pg_prewarm/autoprewarm.c                    |  3 ++-
 doc/src/sgml/xfunc.sgml                             | 13 ++++++++-----
 src/backend/storage/ipc/dsm_registry.c              |  8 +++++---
 src/include/storage/dsm_registry.h                  |  3 ++-
 .../modules/injection_points/injection_points.c     |  3 ++-
 src/test/modules/test_dsa/test_dsa.c                |  8 ++++----
 .../modules/test_dsm_registry/test_dsm_registry.c   |  3 ++-
 7 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..9a68e765e54 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,7 +858,7 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 537ee6fa254..c91defb6a6c 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,18 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, void *arg),
+                         void *callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..cb150bac690 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -180,11 +180,13 @@ 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.  callback_arg is passed to
+ * the init_callback as an additional argument.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, void *arg),
+				   void *callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..150eba1765c 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, void *arg),
+								void *callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index b7c1c58ea56..412d2a64ed7 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -125,7 +125,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -189,6 +189,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..67c22dc0f5b 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,11 +21,11 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
-	*tranche_id = LWLockNewTrancheId("test_dsa");
+	*tranche_id = LWLockNewTrancheId(arg);
 }
 
 /* Test basic DSA functionality */
@@ -39,7 +39,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_pointer p[100];
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, "test_dsa", &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -80,7 +80,7 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner childowner;
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, "test_dsa", &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..7c5d4ee015b 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -44,7 +44,7 @@ static const dshash_parameters dsh_params = {
 };
 
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 NULL,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#2Sami Imseih
samimseih@gmail.com
In reply to: Zsolt Parragi (#1)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

Hi,

Can you provide more details on the use-case?

For example, the documentation for creating LWLocks after startup [1]
suggests creating locks in this callback. That works fine as long as
the callback only needs to create a hardcoded lock.

The callback is called on the first invocation of GetNamedDSMSegment for
a particular segment name. Subsequent calls just attach an existing segment.

But if the lock name is a parameter to the function calling GetNamedDSMSegment, and
not fixed, I do not see a clean way to pass it through to the callback

Keep in mind that the tranche name shows up in wait events, so you
will end up with different wait event names.

Also,

commit 38b602b capped the number of lwlock tranches to 256, so you
may hit this limit if you are creating many lwlocks.

#define MAX_NAMED_TRANCHES 256

--
Sami Imseih
Amazon Web services (AWS)

#3Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#2)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Wed, Dec 03, 2025 at 12:47:46PM -0600, Sami Imseih wrote:

Can you provide more details on the use-case?

I think the main use-case is creating multiple DSM segments in the registry
that use the same initialization callback. I ran into this when I was
working on GetNamedDSA() and GetNamedDSHash(). In early versions of the
patch, the new functions used GetNamedDSMSegment() to allocate the space
for all the DSA/dshash information [0]/messages/by-id/attachment/177621/v8-0001-simplify-creating-hash-table-in-dsm-registry.patch. Since initializing those segments
required the user-provided name string, I ended up taking a lock after
calling GetNamedDSMSegment() and doing most of the initialization there.
My gut feeling is that this is an obscure enough use-case that this
workaround is probably sufficient, but I am interested to hear more...

[0]: /messages/by-id/attachment/177621/v8-0001-simplify-creating-hash-table-in-dsm-registry.patch

--
nathan

#4Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#3)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

My gut feeling is that this is an obscure enough use-case that this
workaround is probably sufficient, but I am interested to hear more...

There are probably other good reasons to allow a generic argument
to the init callback. Besides the lwlock name, I can see someone
wanting to pass some other initialization info that may vary
depending on extension GUCs, etc.

--
Sami

#5Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#4)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Wed, Dec 03, 2025 at 02:27:29PM -0600, Sami Imseih wrote:

There are probably other good reasons to allow a generic argument
to the init callback. Besides the lwlock name, I can see someone
wanting to pass some other initialization info that may vary
depending on extension GUCs, etc.

The value of a GUC could be obtained in the callback via the global
variable or GetConfigOption(), right?

--
nathan

#6Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#5)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Wed, Dec 03, 2025 at 02:27:29PM -0600, Sami Imseih wrote:

There are probably other good reasons to allow a generic argument
to the init callback. Besides the lwlock name, I can see someone
wanting to pass some other initialization info that may vary
depending on extension GUCs, etc.

The value of a GUC could be obtained in the callback via the global
variable or GetConfigOption(), right?

Yes, that's true. It will be hard to find other good use-cases that
can't be solved with a global variable, but we can also say this is
a low-cost change, so why not just do it.

--
Sami

#7Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#6)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Wed, Dec 03, 2025 at 02:59:16PM -0600, Sami Imseih wrote:

Yes, that's true. It will be hard to find other good use-cases that
can't be solved with a global variable, but we can also say this is
a low-cost change, so why not just do it.

Well, for one, it requires all existing extensions that use
GetNamedDSMSegment() to be updated. That might not be too terrible, but in
any case, I think we need a stronger reason than the simplicity of the
implementation to do something.

--
nathan

#8Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Nathan Bossart (#7)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

I ran into this when I was
working on GetNamedDSA() and GetNamedDSHash()

Thanks for mentioning these, I didn't notice them when I rebased on
the master branch. One of my use cases was this, I implemented
something similar to GetNamedDSHash - it's a generic wrapper for
dshash in C++, and I ran into the same issue.

Besides the lwlock name, I can see someone
wanting to pass some other initialization info that may vary
depending on extension GUCs, etc.

Yes, that was my thought too. GUCs/globals can be accessed directly,
but there's still the reusing the same function part. and also
calculated local variables.

My other use case is using GetNamedDSMSegment without DSHash, with a
flexible array, similar to:

struct Foo {
LWLock lock;
size_t size;
Bar data[];
};

* To create a few of these, I have to provide a lock name to the
callback, that's the "reusing the same callback" part again
* And then there's the question of initialization. Either I leave it
to the caller after returning from GetNamedDSHash using the lock, or
somehow I have to tell the initialization callback the array size -
even if I can calculate the size based on a GUC, I wouldn't want to
trust that instead of the actual information from the caller.

Well, for one, it requires all existing extensions that use
GetNamedDSMSegment() to be updated. That might not be too terrible, but in
any case, I think we need a stronger reason than the simplicity of the
implementation to do something.

I did some searching for usage before my initial email, and I only
found 3 extensions that use this method, which seemed like a small
number. Also, the LWLockInitialize change already requires ifdefs in
this function for PG19.

But maybe the second use case alone is too specific to justify this
change, and there are workarounds to it.

#9Sami Imseih
samimseih@gmail.com
In reply to: Zsolt Parragi (#8)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

struct Foo {
LWLock lock;
size_t size;
Bar data[];
};

* To create a few of these, I have to provide a lock name to the
callback, that's the "reusing the same callback" part again
* And then there's the question of initialization. Either I leave it
to the caller after returning from GetNamedDSHash using the lock,

"caller after returning from GetNamedDSHash" <- do you mean
GetNamedDSMSegment ?

or somehow I have to tell the initialization callback the array size -
even if I can calculate the size based on a GUC,

```
typedef struct Bar {
int f1;
int f2;
} Bar;

typedef struct Foo {
LWLock lock;
size_t size;
Bar data[];
} Foo;

foo_state = GetNamedDSMSegment("Foo",

offsetof(Foo, data) + BAR_ARRAY_SIZE * sizeof(int),
foo_init_state,
&found);
```

wouldn't the above be sufficient to create a DSM segment containing
a flexible array?

--
Sami Imseih
Amazon Web Services (AWS)

#10Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Sami Imseih (#9)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

"caller after returning from GetNamedDSHash" <- do you mean
GetNamedDSMSegment ?

Yes, that was a typo.

wouldn't the above be sufficient to create a DSM segment containing
a flexible array?

Yes, it creates it, but can I initialize it properly in
foo_init_state? How can I set the size member to the proper array
size, and how can I zero-initialize the array with the correct length
in it? What I can do currently is:

1. create the lwlock and set size to 0 in foo_init_state
2. take the lwlock after GetNamedDSMSegment returns
3. if size is 0 set it properly and zero-initialize the array

That's why I said that there is a workaround, but it would be nicer if
I could do it properly in the init callback, by passing the array size
as a parameter to it.

#11Nathan Bossart
nathandbossart@gmail.com
In reply to: Zsolt Parragi (#10)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Thu, Dec 04, 2025 at 05:40:21PM +0000, Zsolt Parragi wrote:

wouldn't the above be sufficient to create a DSM segment containing
a flexible array?

Yes, it creates it, but can I initialize it properly in
foo_init_state? How can I set the size member to the proper array
size, and how can I zero-initialize the array with the correct length
in it? What I can do currently is:

1. create the lwlock and set size to 0 in foo_init_state
2. take the lwlock after GetNamedDSMSegment returns
3. if size is 0 set it properly and zero-initialize the array

That's why I said that there is a workaround, but it would be nicer if
I could do it properly in the init callback, by passing the array size
as a parameter to it.

So, it seems like we've established at least 2 potential use-cases for this
argument (tranche name and flexible arrays). And the amount of extension
breakage doesn't seem too bad either. IMHO it'd be reasonable to proceed
with this change.

--
nathan

#12Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#11)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

wouldn't the above be sufficient to create a DSM segment containing
a flexible array?

Yes, it creates it, but can I initialize it properly in
foo_init_state? How can I set the size member to the proper array
size, and how can I zero-initialize the array with the correct length
in it? What I can do currently is:

1. create the lwlock and set size to 0 in foo_init_state
2. take the lwlock after GetNamedDSMSegment returns
3. if size is 0 set it properly and zero-initialize the array

That's why I said that there is a workaround, but it would be nicer if
I could do it properly in the init callback, by passing the array size
as a parameter to it.

So, it seems like we've established at least 2 potential use-cases for this
argument (tranche name and flexible arrays). And the amount of extension
breakage doesn't seem too bad either. IMHO it'd be reasonable to proceed
with this change.

I agree.

The workaround used above looks unsafe, because a backend could attach
to a partially initialized segment. Providing more flexibility in the
init callback
is a clear improvement.

The patch itself is straightforward. One thing that stands out is this:

```
static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, void *arg)
{
int *tranche_id = (int *) ptr;

-       *tranche_id = LWLockNewTrancheId("test_dsa");
+       *tranche_id = LWLockNewTrancheId(arg);
 }

/* Test basic DSA functionality */
@@ -39,7 +39,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
dsa_pointer p[100];

tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-
init_tranche, &found);
+
init_tranche, "test_dsa", &found);

```

In almost all cases, the segment name is also used as the tranche name.
Currently, we set this name twice; in the GetNamedDSMSegment and inside
the init callback. The proposed patch does not improve this either.

I suggest passing the segment_name explicitly to the callback, and
reserving the extra argument for more complex data. If we are
changing the API, this seems like the right time to do so. So,
I think we should do something like:

```

static void
init_tranche(void *ptr, const char *segment_name, void *arg)
{
int *tranche_id = (int *) ptr;
*tranche_id = LWLockNewTrancheId(segment_name);
}

```
This works well for the first use-case identified. Instead of hard-
coding the tranche name in the callback, the name can be
retrieved as the segment name set in GetNamedDSMSegment.

The caller could still pass this name via the extra callback args, but
it's better to separate things a bit here, and reserve the extra callback
arguments for more complex data.

What do you think?

--
Sami Imseih
Amazon Web Services (AWS)

#13Sami Imseih
samimseih@gmail.com
In reply to: Sami Imseih (#12)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

This works well for the first use-case identified. Instead of hard-
coding the tranche name in the callback, the name can be
retrieved as the segment name set in GetNamedDSMSegment.

The caller could still pass this name via the extra callback args, but
it's better to separate things a bit here, and reserve the extra callback
arguments for more complex data.

What do you think?

As I did not hear back, I went ahead and prepared a patch with the above.

I went back-forth on if it makes sense to provide the name as an
extra argument and decided it provides more flexibility. For example
I can use the same init callback and arguments for different segments.

Also, the name provides a guarantee of the name of the segment that
this callback is initializing.

Overall, I felt it was a better approach.

I updated the code comments and documentation.

Also, in test_dsa.c, i updated the name of the segments to
reflect the name of the functions creating the segments, like
below:
```
@@ -38,8 +38,8 @@ test_dsa_basic(PG_FUNCTION_ARGS)
dsa_area *a;
dsa_pointer p[100];

-       tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-
 init_tranche, &found);
+       tranche_id = GetNamedDSMSegment("test_dsa_basic", sizeof(int),
+
 init_tranche, NULL, &found);

@@ -79,8 +79,8 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
ResourceOwner oldowner;
ResourceOwner childowner;

-       tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-
 init_tranche, &found);
+       tranche_id = GetNamedDSMSegment("test_dsa_resowners", sizeof(int),
+
 init_tranche, NULL, &found);

```
This is good for showing the same init callback being re-used
for different named segment initizations.

I also updated the commit message.

--
Sami Imseih
Amazon Web Services (AWS)

Attachments:

v2-0001-Pass-name-and-callback-data-to-GetNamedDSMSegment.patchapplication/octet-stream; name=v2-0001-Pass-name-and-callback-data-to-GetNamedDSMSegment.patchDownload
From ed63279c6510b8d37593070df47e7853d20ceb4a Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Tue, 2 Dec 2025 20:01:33 +0000
Subject: [PATCH v2 1/1] Pass name and callback data to GetNamedDSMSegment init
 callback

Modify the signature of GetNamedDSMSegment to pass the segment name
and an additional init_callback_arg to the init_callback function.
This allows the callback to differentiate initialization logic based on
the segment's name and additional context.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c                  |  5 +++--
 doc/src/sgml/xfunc.sgml                           | 15 ++++++++++-----
 src/backend/storage/ipc/dsm_registry.c            | 10 ++++++----
 src/include/storage/dsm_registry.h                |  3 ++-
 .../modules/injection_points/injection_points.c   |  5 +++--
 src/test/modules/test_dsa/test_dsa.c              | 12 ++++++------
 .../modules/test_dsm_registry/test_dsm_registry.c |  3 ++-
 7 files changed, 32 insertions(+), 21 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..2ff24dad167 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,11 +858,11 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, const char *name, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
-	LWLockInitialize(&state->lock, LWLockNewTrancheId("autoprewarm"));
+	LWLockInitialize(&state->lock, LWLockNewTrancheId(name));
 	state->bgworker_pid = InvalidPid;
 	state->pid_using_dumpfile = InvalidPid;
 }
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..5a56e653e38 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,20 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      The <parameter>name</parameter> parameter is passed to the callback to uniquely
+      identify the segment, enabling different initialization logic for different segments.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..53e17e4f86e 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -179,12 +179,14 @@ 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.
+ * This routine returns the address of the segment. The init_callback is used to
+ * initialize the segment when it is first created. An optional callback_arg is
+ * passed to the init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), name, init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..6e4bd3e2cb3 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..e7c8cdddbf2 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, const char *name, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, "injection_points", NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..b7412c28204 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,11 +21,11 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, const char *name, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
-	*tranche_id = LWLockNewTrancheId("test_dsa");
+	*tranche_id = LWLockNewTrancheId(name);
 }
 
 /* Test basic DSA functionality */
@@ -38,8 +38,8 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_area   *a;
 	dsa_pointer p[100];
 
-	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+	tranche_id = GetNamedDSMSegment("test_dsa_basic", sizeof(int),
+									init_tranche, NULL, &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -79,8 +79,8 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner oldowner;
 	ResourceOwner childowner;
 
-	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+	tranche_id = GetNamedDSMSegment("test_dsa_resowners", sizeof(int),
+									init_tranche, NULL, &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..47330b22a7f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -44,7 +44,7 @@ static const dshash_parameters dsh_params = {
 };
 
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, const char *name, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 NULL,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#14Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#13)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

Thanks for the new patch.

On Thu, Dec 11, 2025 at 04:12:02PM -0600, Sami Imseih wrote:

I went back-forth on if it makes sense to provide the name as an
extra argument and decided it provides more flexibility. For example
I can use the same init callback and arguments for different segments.

If the initialization callback function needed the name, it could be
provided via the "void *" callback argument, right? I'm not following why
we need to provide it separately.

--
nathan

#15Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Sami Imseih (#13)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

As I did not hear back, I went ahead and prepared a patch with the above.

If the question was for me sorry for not replying, I assumed it was
meant for Nathan.

Personally I'm not sure if we need this as even requiring a name isn't
a common use case, but I'm also fine with this version.

The only additional thing I would do is to add some kind of test that
verifies that we indeed forward the pointer to the callback in
test_dsa, for example:

+const char* forwardedData = "ForwardedData";
+
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, const char *name, void *arg)
 {
        int                *tranche_id = (int *) ptr;
-       *tranche_id = LWLockNewTrancheId("test_dsa");
+       if (arg != forwardedData) {
+               elog(ERROR, "Didn't receive expected arg pointer");
+       }
+
+       *tranche_id = LWLockNewTrancheId(name);
 }

As otherwise we no longer test the value of the pointer in any of the
tests with the additional name parameter.

On a slightly related topic, I mentioned earlier that I found 3
extensions using GetNamedDSMSegment. One of them,
pg_track_optimizer[1]https://github.com/danolivo/pg_track_optimizer/ could use the new GetNamedDSHash function,
except that it doesn't provide a callback similar to what we have in
GetNamedDSMSegment, and it relies on initializing the hash in the
callback, before it returns. That's not possible with the new
function.

What do you think, would it make sense to also include callbacks for
GetNamedDSHash and GetNamedDSA?

[1]: https://github.com/danolivo/pg_track_optimizer/

Show quoted text

On Thu, Dec 11, 2025 at 10:12 PM Sami Imseih <samimseih@gmail.com> wrote:

This works well for the first use-case identified. Instead of hard-
coding the tranche name in the callback, the name can be
retrieved as the segment name set in GetNamedDSMSegment.

The caller could still pass this name via the extra callback args, but
it's better to separate things a bit here, and reserve the extra callback
arguments for more complex data.

What do you think?

As I did not hear back, I went ahead and prepared a patch with the above.

I went back-forth on if it makes sense to provide the name as an
extra argument and decided it provides more flexibility. For example
I can use the same init callback and arguments for different segments.

Also, the name provides a guarantee of the name of the segment that
this callback is initializing.

Overall, I felt it was a better approach.

I updated the code comments and documentation.

Also, in test_dsa.c, i updated the name of the segments to
reflect the name of the functions creating the segments, like
below:
```
@@ -38,8 +38,8 @@ test_dsa_basic(PG_FUNCTION_ARGS)
dsa_area *a;
dsa_pointer p[100];

-       tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-
init_tranche, &found);
+       tranche_id = GetNamedDSMSegment("test_dsa_basic", sizeof(int),
+
init_tranche, NULL, &found);

@@ -79,8 +79,8 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
ResourceOwner oldowner;
ResourceOwner childowner;

-       tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-
init_tranche, &found);
+       tranche_id = GetNamedDSMSegment("test_dsa_resowners", sizeof(int),
+
init_tranche, NULL, &found);

```
This is good for showing the same init callback being re-used
for different named segment initizations.

I also updated the commit message.

--
Sami Imseih
Amazon Web Services (AWS)

Attachments:

v3-0001-Pass-name-and-callback-data-to-GetNamedDSMSegment-in.patchapplication/octet-stream; name=v3-0001-Pass-name-and-callback-data-to-GetNamedDSMSegment-in.patchDownload
From b911d18a7244efb957e6f7f71a71b390ad8d84ec Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 22:54:06 +0000
Subject: [PATCH 1/1] Pass name and callback data to GetNamedDSMSegment init
 callback

Modify the signature of GetNamedDSMSegment to pass the segment name
and an additional init_callback_arg to the init_callback function.
This allows the callback to differentiate initialization logic based on
the segment's name and additional context.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c               |  5 +++--
 doc/src/sgml/xfunc.sgml                        | 15 ++++++++++-----
 src/backend/storage/ipc/dsm_registry.c         | 10 ++++++----
 src/include/storage/dsm_registry.h             |  3 ++-
 .../injection_points/injection_points.c        |  5 +++--
 src/test/modules/test_dsa/test_dsa.c           | 18 ++++++++++++------
 .../test_dsm_registry/test_dsm_registry.c      |  3 ++-
 7 files changed, 38 insertions(+), 21 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..2ff24dad167 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,11 +858,11 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, const char *name, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
-	LWLockInitialize(&state->lock, LWLockNewTrancheId("autoprewarm"));
+	LWLockInitialize(&state->lock, LWLockNewTrancheId(name));
 	state->bgworker_pid = InvalidPid;
 	state->pid_using_dumpfile = InvalidPid;
 }
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..5a56e653e38 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,20 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      The <parameter>name</parameter> parameter is passed to the callback to uniquely
+      identify the segment, enabling different initialization logic for different segments.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..53e17e4f86e 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -179,12 +179,14 @@ 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.
+ * This routine returns the address of the segment. The init_callback is used to
+ * initialize the segment when it is first created. An optional callback_arg is
+ * passed to the init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), name, init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..6e4bd3e2cb3 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..e7c8cdddbf2 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, const char *name, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, "injection_points", NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..81568194a84 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -20,12 +20,18 @@
 
 PG_MODULE_MAGIC;
 
+const char* forwardedData = "ForwardedData";
+
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, const char *name, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
-	*tranche_id = LWLockNewTrancheId("test_dsa");
+	if (arg != forwardedData) {
+		elog(ERROR, "Didn't receive expected arg pointer");
+	}
+
+	*tranche_id = LWLockNewTrancheId(name);
 }
 
 /* Test basic DSA functionality */
@@ -38,8 +44,8 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_area   *a;
 	dsa_pointer p[100];
 
-	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+	tranche_id = GetNamedDSMSegment("test_dsa_basic", sizeof(int),
+									init_tranche, forwardedData, &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -79,8 +85,8 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner oldowner;
 	ResourceOwner childowner;
 
-	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+	tranche_id = GetNamedDSMSegment("test_dsa_resowners", sizeof(int),
+									init_tranche, forwardedData, &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..47330b22a7f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -44,7 +44,7 @@ static const dshash_parameters dsh_params = {
 };
 
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, const char *name, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 NULL,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#16Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#14)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

I went back-forth on if it makes sense to provide the name as an
extra argument and decided it provides more flexibility. For example
I can use the same init callback and arguments for different segments.

If the initialization callback function needed the name, it could be
provided via the "void *" callback argument, right? I'm not following why
we need to provide it separately.

While it's true it can be passed as extra data, it is less error-prone
as we guarantee the real name of the segment is made available to
the callback. Also a caller to GetNamedDSMSegment does not need to
pass the name twice, as the name and as extra data. The most common
case I would think is using the segment name as the tranche name when
initializing a lwlock.

--
Sami Imseih
Amazon Web Services (AWS)

#17Sami Imseih
samimseih@gmail.com
In reply to: Zsolt Parragi (#15)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On a slightly related topic, I mentioned earlier that I found 3
extensions using GetNamedDSMSegment. One of them,
pg_track_optimizer[1] could use the new GetNamedDSHash function,
except that it doesn't provide a callback similar to what we have in
c, and it relies on initializing the hash in the
callback, before it returns. That's not possible with the new
function.

What do you think, would it make sense to also include callbacks for
GetNamedDSHash and GetNamedDSA?

[1]: https://github.com/danolivo/pg_track_optimizer/

GetNamedDSA and GetNamedDSHash do not have a need for a callback,
because there isn't custom initialization logic that can be applied there.

--
Sami Imseih
Amazon Web Services (AWS)

#18Sami Imseih
samimseih@gmail.com
In reply to: Zsolt Parragi (#15)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment
+const char* forwardedData = "ForwardedData";
+
static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, const char *name, void *arg)
{
int                *tranche_id = (int *) ptr;
-       *tranche_id = LWLockNewTrancheId("test_dsa");
+       if (arg != forwardedData) {
+               elog(ERROR, "Didn't receive expected arg pointer");
+       }
+
+       *tranche_id = LWLockNewTrancheId(name);
}

As otherwise we no longer test the value of the pointer in any of the
tests with the additional name parameter.

Good call adding the tests. I do get compilation errors though with
v3, due to passing a const to a void pointer. I think this test could be
made better by checking that the arg data matches some data that
we expect.

```
-init_tranche(void *ptr)
+init_tranche(void *ptr, const char *name, void *arg)
{
int *tranche_id = (int *) ptr;
- *tranche_id = LWLockNewTrancheId("test_dsa");
+ /* Verify arg if given */
+ if (arg && strcmp(name, (char *) arg) != 0)
+ elog(ERROR, "didn't receive expected arg data");
+
+ *tranche_id = LWLockNewTrancheId(name);
}
```

--
Sami Imseih
Amazon Web Services (AWS)

Attachments:

v4-0001-Pass-name-and-callback-data-to-GetNamedDSMSegment.patchapplication/octet-stream; name=v4-0001-Pass-name-and-callback-data-to-GetNamedDSMSegment.patchDownload
From f8dace7c732320f7c61f764b27366e9922b59436 Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 22:54:06 +0000
Subject: [PATCH v4 1/1] Pass name and callback data to GetNamedDSMSegment init
 callback

Modify the signature of GetNamedDSMSegment to pass the segment name
and an additional init_callback_arg to the init_callback function.
This allows the callback to differentiate initialization logic based on
the segment's name and additional context.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c                 |  5 +++--
 doc/src/sgml/xfunc.sgml                          | 15 ++++++++++-----
 src/backend/storage/ipc/dsm_registry.c           | 10 ++++++----
 src/include/storage/dsm_registry.h               |  3 ++-
 .../modules/injection_points/injection_points.c  |  5 +++--
 src/test/modules/test_dsa/test_dsa.c             | 16 ++++++++++------
 .../test_dsm_registry/test_dsm_registry.c        |  3 ++-
 7 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..2ff24dad167 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,11 +858,11 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, const char *name, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
-	LWLockInitialize(&state->lock, LWLockNewTrancheId("autoprewarm"));
+	LWLockInitialize(&state->lock, LWLockNewTrancheId(name));
 	state->bgworker_pid = InvalidPid;
 	state->pid_using_dumpfile = InvalidPid;
 }
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..5a56e653e38 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,20 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      The <parameter>name</parameter> parameter is passed to the callback to uniquely
+      identify the segment, enabling different initialization logic for different segments.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..53e17e4f86e 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -179,12 +179,14 @@ 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.
+ * This routine returns the address of the segment. The init_callback is used to
+ * initialize the segment when it is first created. An optional callback_arg is
+ * passed to the init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), name, init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..6e4bd3e2cb3 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, const char *name, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..e7c8cdddbf2 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, const char *name, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, "injection_points", NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..fc41ce9bdb8 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,11 +21,15 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, const char *name, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
-	*tranche_id = LWLockNewTrancheId("test_dsa");
+	/* Verify arg if given */
+	if (arg && strcmp(name, (char *) arg) != 0)
+		elog(ERROR, "didn't receive expected arg data");
+
+	*tranche_id = LWLockNewTrancheId(name);
 }
 
 /* Test basic DSA functionality */
@@ -38,8 +42,8 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_area   *a;
 	dsa_pointer p[100];
 
-	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+	tranche_id = GetNamedDSMSegment("test_dsa_basic", sizeof(int),
+									init_tranche, "test_dsa_basic", &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -79,8 +83,8 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner oldowner;
 	ResourceOwner childowner;
 
-	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+	tranche_id = GetNamedDSMSegment("test_dsa_resowners", sizeof(int),
+									init_tranche, "test_dsa_resowners", &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..47330b22a7f 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -44,7 +44,7 @@ static const dshash_parameters dsh_params = {
 };
 
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, const char *name, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 NULL,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#19Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Sami Imseih (#18)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

GetNamedDSA and GetNamedDSHash do not have a need for a callback,
because there isn't custom initialization logic that can be applied there.

The use case for GetNamedDSHash is:

1. I want to create a dshash
2. I want to make sure that I can preload some initial data into it
before any backend is able to access it

My trivial example for it would be persistent statistics: when I want
to collect some information, save it to disk before shutdown, and on
the next startup, I want to load the previous state before continuing
collecting. pg_track_optimizer seems to do this. There are also
definitely other reasons.

* If I do it with GetNamedDSMSegment, it is doable inside the init
function: the init function is called when GetNamedDSMSegment still
holds a lock
* If I do it with traditional shared memory manually it is again
doable, as I control how I call dshash_create. If the code doesn't
already hold a lock, I take a lock when I call it
* But how can I do it with GetNamedDSHash? Should I take a different
lock manually every time before I call GetNamedDSHash? That means I
also have to store that different lock somewhere, I have to call
GetNamedDSMSegment to get storage for that, and at that point it is
easier to call dshash_create there in the initialization function, and
skip GetNamedDSHash entirely.

The answer might be this, that GetNamedDSHash is only for the simple
use cases, and for anything requiring more control, we have to fall
back to using GetNamedDSMSegment. I was just wondering if it would be
useful to add this functionality to GetNamedDSHash.

I think this test could be
made better by checking that the arg data matches some data that
we expect.

I verified the pointer address because it shouldn't change inside
GetNamedDSHash, but as long as the data is the same it doesn't really
matter, this version also looks good.

#20Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#16)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Thu, Dec 11, 2025 at 05:17:30PM -0600, Sami Imseih wrote:

If the initialization callback function needed the name, it could be
provided via the "void *" callback argument, right? I'm not following why
we need to provide it separately.

While it's true it can be passed as extra data, it is less error-prone
as we guarantee the real name of the segment is made available to
the callback. Also a caller to GetNamedDSMSegment does not need to
pass the name twice, as the name and as extra data. The most common
case I would think is using the segment name as the tranche name when
initializing a lwlock.

But... they can just pass that in the "void *" argument. I'm pretty firmly
-1 for adding more than the one callback argument here.

--
nathan

#21Nathan Bossart
nathandbossart@gmail.com
In reply to: Zsolt Parragi (#19)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Fri, Dec 12, 2025 at 07:10:24AM +0000, Zsolt Parragi wrote:

GetNamedDSA and GetNamedDSHash do not have a need for a callback,
because there isn't custom initialization logic that can be applied there.

The use case for GetNamedDSHash is:

I'm going to stay focused on the GetNamedDSMSegment() argument for now, but
would like to consider this (perhaps in a new thread) afterwards.

--
nathan

#22Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#21)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

My trivial example for it would be persistent statistics: when I want
to collect some information, save it to disk before shutdown, and on
the next startup, I want to load the previous state before continuing
collecting. pg_track_optimizer seems to do this. There are also
definitely other reasons.

From what I can tell pg_track_optimizer has SQL functions that
flush the data to disk and loads it back. This is because dsm and
by extension dsa and dshash cannot be accessed by postmaster.

A better way to deal with this is via pluggable stats, which can be
written to disk. There is also on-going work, close to commitable,
that will provide callbacks to allow pluggable stats to
serialize/deserialize extra data that does not fit in stats ( i.e. data
stored in dsa or some dshash table ) [0]/messages/by-id/CAA5RZ0s9SDOu+Z6veoJCHWk+kDeTktAtC-KY9fQ9Z6BJdDUirQ@mail.gmail.com.

(pgstat.c write and read the stats during checkpoint and startup,
to overcome the postmaster accessing dsm limitation).

If the initialization callback function needed the name, it could be
provided via the "void *" callback argument, right? I'm not following why
we need to provide it separately.

While it's true it can be passed as extra data, it is less error-prone
as we guarantee the real name of the segment is made available to
the callback. Also a caller to GetNamedDSMSegment does not need to
pass the name twice, as the name and as extra data. The most common
case I would think is using the segment name as the tranche name when
initializing a lwlock.

But... they can just pass that in the "void *" argument. I'm pretty firmly
-1 for adding more than the one callback argument here.

Ok. I was going back-forth on this, but at the end I also could
not convince myself that this is required ( at least not yet ). The main reason
I had was that an extension may want to validate that the callback is being
called during the initialization of the segment they expect, and also that
is a small convenience to simply refer to the name of the segment when
creating a tranche. But, I will agree with you that these reasons are
not justified.

As far as testing, I did not think it's worth it since in the cases
out there now
a NULL void * will result in an error when calling LWLockNewTrancheId.

See v5.

--
Sami Imseih
Amazon Web Services (AWS)

[0]: /messages/by-id/CAA5RZ0s9SDOu+Z6veoJCHWk+kDeTktAtC-KY9fQ9Z6BJdDUirQ@mail.gmail.com

Attachments:

v5-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSeg.patchapplication/octet-stream; name=v5-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSeg.patchDownload
From b76a13725c6ac170c1597a71f7bfec8c8d99dbb9 Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 22:54:06 +0000
Subject: [PATCH v5 1/1] Add init_callback_arg parameter to GetNamedDSMSegment

Modify GetNamedDSMSegment to accept an optional init_callback_arg
parameter that gets passed to the init_callback function. This allows
callbacks to differentiate initialization logic based on additional
context provided by the caller.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c                    |  5 +++--
 doc/src/sgml/xfunc.sgml                             | 13 ++++++++-----
 src/backend/storage/ipc/dsm_registry.c              |  8 +++++---
 src/include/storage/dsm_registry.h                  |  3 ++-
 .../modules/injection_points/injection_points.c     |  5 +++--
 src/test/modules/test_dsa/test_dsa.c                |  8 ++++----
 .../modules/test_dsm_registry/test_dsm_registry.c   |  5 +++--
 7 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..f367a303dc1 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,11 +858,11 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
-	LWLockInitialize(&state->lock, LWLockNewTrancheId("autoprewarm"));
+	LWLockInitialize(&state->lock, LWLockNewTrancheId((char *) arg));
 	state->bgworker_pid = InvalidPid;
 	state->pid_using_dumpfile = InvalidPid;
 }
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   "autoprewarm",
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..91284abb679 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,18 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..c68fad52c54 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -180,11 +180,13 @@ 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. An optional init_callback_arg is
+ * passed to init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..b2670ebefc7 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..36f07295818 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..78a8452dd4f 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,11 +21,11 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
-	*tranche_id = LWLockNewTrancheId("test_dsa");
+	*tranche_id = LWLockNewTrancheId((char *) arg);
 }
 
 /* Test basic DSA functionality */
@@ -39,7 +39,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_pointer p[100];
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, "test_dsa", &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -80,7 +80,7 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner childowner;
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, "test_dsa", &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..89b0392ac6b 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -44,11 +44,11 @@ static const dshash_parameters dsh_params = {
 };
 
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId((char *) arg));
 	dsm->val = 0;
 }
 
@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 "test_dsm_registry",
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#23Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#22)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Fri, Dec 12, 2025 at 12:48:52PM -0600, Sami Imseih wrote:

As far as testing, I did not think it's worth it since in the cases out
there now a NULL void * will result in an error when calling
LWLockNewTrancheId.

I think we should pass NULL to all the existing in-tree calls to
GetNamedDSMSegment(), except for perhaps a new test in test_dsm_registry
that verifies the pointer value in the initialization function. In all the
other cases, there's no issue with hard-coding the tranche names, so we can
keep those simple.

--
nathan

#24Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#23)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Fri, Dec 12, 2025 at 12:48:52PM -0600, Sami Imseih wrote:

As far as testing, I did not think it's worth it since in the cases out
there now a NULL void * will result in an error when calling
LWLockNewTrancheId.

I think we should pass NULL to all the existing in-tree calls to
GetNamedDSMSegment(), except for perhaps a new test in test_dsm_registry
that verifies the pointer value in the initialization function. In all the
other cases, there's no issue with hard-coding the tranche names, so we can
keep those simple.

fair point. In that case why don't we just keep:

```
static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
{
TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;

-       LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+       LWLockInitialize(&dsm->lck, LWLockNewTrancheId((char *) arg));
        dsm->val = 0;
 }

@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",

sizeof(TestDSMRegistryStruct),
init_tdr_dsm,
+
"test_dsm_registry",
&found);

if (tdr_dsa == NULL)
```

instead of creating a new test? For the other GetNamedDSMSegment
calls, I'll pass
NULL to the void * and hard code the tranche name in the init callback.

--
Sami Imseih
Amazon Web Services (AWS)

#25Zsolt Parragi
zsolt.parragi@percona.com
In reply to: Sami Imseih (#24)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

fair point. In that case why don't we just keep:

I did something similar, but using a simple pointer comparison instead
of strings, to avoid a warning about discarding const qualifiers.

What do you think about this version?

+void *sample_arg_ptr = &tdr_dsm;
+
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
 {
        TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
-       LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+       if (arg != sample_arg_ptr)
+               ereport(ERROR,
+                               (errmsg("Unexpected arg pointer address")));
+
+       LWLockInitialize(&dsm->lck,
LWLockNewTrancheId("test_dsm_registry_dsm"));
        dsm->val = 0;
 }
Show quoted text

On Fri, Dec 12, 2025 at 8:57 PM Sami Imseih <samimseih@gmail.com> wrote:

On Fri, Dec 12, 2025 at 12:48:52PM -0600, Sami Imseih wrote:

As far as testing, I did not think it's worth it since in the cases out
there now a NULL void * will result in an error when calling
LWLockNewTrancheId.

I think we should pass NULL to all the existing in-tree calls to
GetNamedDSMSegment(), except for perhaps a new test in test_dsm_registry
that verifies the pointer value in the initialization function. In all the
other cases, there's no issue with hard-coding the tranche names, so we can
keep those simple.

fair point. In that case why don't we just keep:

```
static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
{
TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;

-       LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+       LWLockInitialize(&dsm->lck, LWLockNewTrancheId((char *) arg));
dsm->val = 0;
}

@@ -60,6 +60,7 @@ tdr_attach_shmem(void)
tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",

sizeof(TestDSMRegistryStruct),
init_tdr_dsm,
+
"test_dsm_registry",
&found);

if (tdr_dsa == NULL)
```

instead of creating a new test? For the other GetNamedDSMSegment
calls, I'll pass
NULL to the void * and hard code the tranche name in the init callback.

--
Sami Imseih
Amazon Web Services (AWS)

Attachments:

v6-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSegmen.patchapplication/octet-stream; name=v6-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSegmen.patchDownload
From 4cf66958ca26a75e4ffdf8527beb5aab3733108a Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 22:54:06 +0000
Subject: [PATCH] Add init_callback_arg parameter to GetNamedDSMSegment

Modify GetNamedDSMSegment to accept an optional init_callback_arg
parameter that gets passed to the init_callback function. This allows
callbacks to differentiate initialization logic based on additional
context provided by the caller.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c                    |  3 ++-
 doc/src/sgml/xfunc.sgml                             | 13 ++++++++-----
 src/backend/storage/ipc/dsm_registry.c              |  8 +++++---
 src/include/storage/dsm_registry.h                  |  3 ++-
 .../modules/injection_points/injection_points.c     |  5 +++--
 src/test/modules/test_dsa/test_dsa.c                |  6 +++---
 .../modules/test_dsm_registry/test_dsm_registry.c   | 11 +++++++++--
 7 files changed, 32 insertions(+), 17 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..9a68e765e54 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,7 +858,7 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..91284abb679 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,18 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..c68fad52c54 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -180,11 +180,13 @@ 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. An optional init_callback_arg is
+ * passed to init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..b2670ebefc7 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..36f07295818 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..1b4fe963998 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,7 +21,7 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
@@ -39,7 +39,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_pointer p[100];
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, NULL, &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -80,7 +80,7 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner childowner;
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, NULL, &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..cc1cdad0d60 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -43,12 +43,18 @@ static const dshash_parameters dsh_params = {
 	dshash_strcpy
 };
 
+void *sample_arg_ptr = &tdr_dsm;
+
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
-	LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+	if (arg != sample_arg_ptr)
+		ereport(ERROR,
+				(errmsg("Unexpected arg pointer address")));
+
+	LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry_dsm"));
 	dsm->val = 0;
 }
 
@@ -60,6 +66,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 sample_arg_ptr,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#26Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#24)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Fri, Dec 12, 2025 at 02:56:39PM -0600, Sami Imseih wrote:

fair point. In that case why don't we just keep:

[...]
-       LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+       LWLockInitialize(&dsm->lck, LWLockNewTrancheId((char *) arg));
dsm->val = 0;
[...]

instead of creating a new test? For the other GetNamedDSMSegment calls,
I'll pass NULL to the void * and hard code the tranche name in the init
callback.

I think we should verify the pointer value more directly. For example, we
could pass something like (uintptr_t) 0x12345 via the callback argument and
then verify it's the same in the callback.

--
nathan

#27Sami Imseih
samimseih@gmail.com
In reply to: Nathan Bossart (#26)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment
[...]
-       LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
+       LWLockInitialize(&dsm->lck, LWLockNewTrancheId((char *) arg));
dsm->val = 0;
[...]

instead of creating a new test? For the other GetNamedDSMSegment calls,
I'll pass NULL to the void * and hard code the tranche name in the init
callback.

I think we should verify the pointer value more directly. For example, we
could pass something like (uintptr_t) 0x12345 via the callback argument and
then verify it's the same in the callback.

This is better than what I had earlier where I was testing that a string
was passed correctly. See v6.

--
Sami Imseih
Amazon Web Services(AWS)

Attachments:

v6-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSeg.patchapplication/octet-stream; name=v6-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSeg.patchDownload
From eee50c048c2bf5cd869be9c5377ea5c77a56d14b Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 22:54:06 +0000
Subject: [PATCH v6 1/1] Add init_callback_arg parameter to GetNamedDSMSegment

Modify GetNamedDSMSegment to accept an optional init_callback_arg
parameter that gets passed to the init_callback function. This allows
callbacks to differentiate initialization logic based on additional
context provided by the caller.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c                    |  3 ++-
 doc/src/sgml/xfunc.sgml                             | 13 ++++++++-----
 src/backend/storage/ipc/dsm_registry.c              |  8 +++++---
 src/include/storage/dsm_registry.h                  |  3 ++-
 .../modules/injection_points/injection_points.c     |  5 +++--
 src/test/modules/test_dsa/test_dsa.c                |  6 +++---
 .../modules/test_dsm_registry/test_dsm_registry.c   | 10 +++++++++-
 7 files changed, 32 insertions(+), 16 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..9a68e765e54 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,7 +858,7 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..91284abb679 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,18 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..c68fad52c54 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -180,11 +180,13 @@ 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. An optional init_callback_arg is
+ * passed to init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..b2670ebefc7 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..36f07295818 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..1b4fe963998 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,7 +21,7 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
@@ -39,7 +39,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_pointer p[100];
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, NULL, &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -80,7 +80,7 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner childowner;
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, NULL, &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..240d6625a2e 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -44,10 +44,13 @@ static const dshash_parameters dsh_params = {
 };
 
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
+	/* Verify the callback argument is passed correctly */
+	Assert((uintptr_t) arg == 0x12345);
+
 	LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
 	dsm->val = 0;
 }
@@ -57,9 +60,14 @@ tdr_attach_shmem(void)
 {
 	bool		found;
 
+	/*
+	 * Initialize or attach test_dsm_registry_dsm. Pass an arbitrary argument
+	 * to init_callback so we can verify it is passed correctly.
+	 */
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 (void *) (uintptr_t) 0x12345,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#28Sami Imseih
samimseih@gmail.com
In reply to: Sami Imseih (#27)
1 attachment(s)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

This is better than what I had earlier where I was testing that a string
was passed correctly. See v6.

I was just using an Assert in v6 to test the pointer value, which is
not what we want for testing. Better to have a error with proper logging.
I fixed that in v7.

--
Sami Imseih
Amazon Web Services(AWS)

Attachments:

v7-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSeg.patchapplication/octet-stream; name=v7-0001-Add-init_callback_arg-parameter-to-GetNamedDSMSeg.patchDownload
From 309d40b7fd7e69614a2e417247e7b0a98dcc2e5c Mon Sep 17 00:00:00 2001
From: Zsolt Parragi <zsolt.parragi@percona.com>
Date: Thu, 11 Dec 2025 22:54:06 +0000
Subject: [PATCH v7 1/1] Add init_callback_arg parameter to GetNamedDSMSegment

Modify GetNamedDSMSegment to accept an optional init_callback_arg
parameter that gets passed to the init_callback function. This allows
callbacks to differentiate initialization logic based on additional
context provided by the caller.

Author: zsolt.parragi@percona.com
Reviewed-by: Nathan Bossart <bossartn@amazon.com>
Reviewed-by: Sami Imseih <samimseih@gmail.com>
Discussion: https://postgr.es/m/CAN4CZFMjh8TrT9ZhWgjVTzBDkYZi2a84BnZ8bM%2BfLPuq7Cirzg%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c                    |  3 ++-
 doc/src/sgml/xfunc.sgml                             | 13 ++++++++-----
 src/backend/storage/ipc/dsm_registry.c              |  8 +++++---
 src/include/storage/dsm_registry.h                  |  3 ++-
 .../modules/injection_points/injection_points.c     |  5 +++--
 src/test/modules/test_dsa/test_dsa.c                |  6 +++---
 .../modules/test_dsm_registry/test_dsm_registry.c   | 12 +++++++++++-
 7 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 5ba1240d51f..9a68e765e54 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -858,7 +858,7 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
 }
 
 static void
-apw_init_state(void *ptr)
+apw_init_state(void *ptr, void *arg)
 {
 	AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
 
@@ -880,6 +880,7 @@ apw_init_shmem(void)
 	apw_state = GetNamedDSMSegment("autoprewarm",
 								   sizeof(AutoPrewarmSharedState),
 								   apw_init_state,
+								   NULL,
 								   &found);
 
 	return found;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 7c76ab8c349..91284abb679 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3696,15 +3696,18 @@ LWLockRelease(AddinShmemInitLock);
       use the shared memory should obtain a pointer to it by calling:
 <programlisting>
 void *GetNamedDSMSegment(const char *name, size_t size,
-                         void (*init_callback) (void *ptr),
+                         void (*init_callback) (void *ptr, void *init_callback_arg),
+                         void *init_callback_arg,
                          bool *found)
 </programlisting>
       If a dynamic shared memory segment with the given name does not yet
       exist, this function will allocate it and initialize it with the provided
-      <function>init_callback</function> callback function.  If the segment has
-      already been allocated and initialized by another backend, this function
-      simply attaches the existing dynamic shared memory segment to the current
-      backend.
+      <function>init_callback</function> callback function.  The
+      <parameter>init_callback_arg</parameter> parameter is passed to the callback
+      function, allowing additional context to be provided during initialization.
+      If the segment has already been allocated and initialized by another backend,
+      this function simply attaches the existing dynamic shared memory segment to
+      the current backend.
      </para>
 
      <para>
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index 66240318e83..c68fad52c54 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -180,11 +180,13 @@ 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. An optional init_callback_arg is
+ * passed to init_callback to provide additional context during initialization.
  */
 void *
 GetNamedDSMSegment(const char *name, size_t size,
-				   void (*init_callback) (void *ptr), bool *found)
+				   void (*init_callback) (void *ptr, void *init_callback_arg),
+				   void *init_callback_arg, bool *found)
 {
 	DSMRegistryEntry *entry;
 	MemoryContext oldcontext;
@@ -235,7 +237,7 @@ GetNamedDSMSegment(const char *name, size_t size,
 		seg = dsm_create(size, 0);
 
 		if (init_callback)
-			(*init_callback) (dsm_segment_address(seg));
+			(*init_callback) (dsm_segment_address(seg), init_callback_arg);
 
 		dsm_pin_segment(seg);
 		dsm_pin_mapping(seg);
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
index 4871ed509eb..b2670ebefc7 100644
--- a/src/include/storage/dsm_registry.h
+++ b/src/include/storage/dsm_registry.h
@@ -16,7 +16,8 @@
 #include "lib/dshash.h"
 
 extern void *GetNamedDSMSegment(const char *name, size_t size,
-								void (*init_callback) (void *ptr),
+								void (*init_callback) (void *ptr, void *init_callback_arg),
+								void *init_callback_arg,
 								bool *found);
 extern dsa_area *GetNamedDSA(const char *name, bool *found);
 extern dshash_table *GetNamedDSHash(const char *name,
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 417b61f31c5..36f07295818 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -115,7 +115,7 @@ static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
  * when initializing dynamically with a DSM or when loading the module.
  */
 static void
-injection_point_init_state(void *ptr)
+injection_point_init_state(void *ptr, void *arg)
 {
 	InjectionPointSharedState *state = (InjectionPointSharedState *) ptr;
 
@@ -159,7 +159,7 @@ injection_shmem_startup(void)
 		 * First time through, so initialize.  This is shared with the dynamic
 		 * initialization using a DSM.
 		 */
-		injection_point_init_state(inj_state);
+		injection_point_init_state(inj_state, NULL);
 	}
 
 	LWLockRelease(AddinShmemInitLock);
@@ -179,6 +179,7 @@ injection_init_shmem(void)
 	inj_state = GetNamedDSMSegment("injection_points",
 								   sizeof(InjectionPointSharedState),
 								   injection_point_init_state,
+								   NULL,
 								   &found);
 }
 
diff --git a/src/test/modules/test_dsa/test_dsa.c b/src/test/modules/test_dsa/test_dsa.c
index 21e4ce6a745..1b4fe963998 100644
--- a/src/test/modules/test_dsa/test_dsa.c
+++ b/src/test/modules/test_dsa/test_dsa.c
@@ -21,7 +21,7 @@
 PG_MODULE_MAGIC;
 
 static void
-init_tranche(void *ptr)
+init_tranche(void *ptr, void *arg)
 {
 	int		   *tranche_id = (int *) ptr;
 
@@ -39,7 +39,7 @@ test_dsa_basic(PG_FUNCTION_ARGS)
 	dsa_pointer p[100];
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, NULL, &found);
 
 	a = dsa_create(*tranche_id);
 	for (int i = 0; i < 100; i++)
@@ -80,7 +80,7 @@ test_dsa_resowners(PG_FUNCTION_ARGS)
 	ResourceOwner childowner;
 
 	tranche_id = GetNamedDSMSegment("test_dsa", sizeof(int),
-									init_tranche, &found);
+									init_tranche, NULL, &found);
 
 	/* Create DSA in parent resource owner */
 	a = dsa_create(*tranche_id);
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 4cc2ccdac3f..2b68628b52d 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -43,11 +43,20 @@ static const dshash_parameters dsh_params = {
 	dshash_strcpy
 };
 
+/* Test callback argument for init_tdr_dsm verification */
+#define TDR_DSM_CALLBACK_ARG ((void *) (uintptr_t) 0x12345)
+
 static void
-init_tdr_dsm(void *ptr)
+init_tdr_dsm(void *ptr, void *arg)
 {
 	TestDSMRegistryStruct *dsm = (TestDSMRegistryStruct *) ptr;
 
+	/* Verify the callback argument is passed correctly */
+	if (arg != TDR_DSM_CALLBACK_ARG)
+		ereport(ERROR,
+				(errmsg("unexpected callback argument: got %p, expected %p",
+						arg, TDR_DSM_CALLBACK_ARG)));
+
 	LWLockInitialize(&dsm->lck, LWLockNewTrancheId("test_dsm_registry"));
 	dsm->val = 0;
 }
@@ -60,6 +69,7 @@ tdr_attach_shmem(void)
 	tdr_dsm = GetNamedDSMSegment("test_dsm_registry_dsm",
 								 sizeof(TestDSMRegistryStruct),
 								 init_tdr_dsm,
+								 TDR_DSM_CALLBACK_ARG,
 								 &found);
 
 	if (tdr_dsa == NULL)
-- 
2.43.0

#29Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#28)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

On Sat, Dec 13, 2025 at 08:21:58AM -0600, Sami Imseih wrote:

I was just using an Assert in v6 to test the pointer value, which is
not what we want for testing. Better to have a error with proper logging.
I fixed that in v7.

LGTM at a glance. I didn't see a cfest entry for this one. Can you add
one so that we get some CI test coverage?

--
nathan

#30Nathan Bossart
nathandbossart@gmail.com
In reply to: Sami Imseih (#28)
Re: Proposal: Add a callback data parameter to GetNamedDSMSegment

Committed.

--
nathan