introduce dynamic shared memory registry
Every once in a while, I find myself wanting to use shared memory in a
loadable module without requiring it to be loaded at server start via
shared_preload_libraries. The DSM API offers a nice way to create and
manage dynamic shared memory segments, so creating a segment after server
start is easy enough. However, AFAICT there's no easy way to teach other
backends about the segment without storing the handles in shared memory,
which puts us right back at square one.
The attached 0001 introduces a "DSM registry" to solve this problem. The
API provides an easy way to allocate/initialize a segment or to attach to
an existing one. The registry itself is just a dshash table that stores
the handles keyed by a module-specified string. 0002 adds a test for the
registry that demonstrates basic usage.
I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v1-0001-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From b63c28303384636699f2f514e71b62829346be4b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v1 1/2] add dsm registry
---
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 176 +++++++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
src/include/storage/dsm_registry.h | 22 +++
src/include/storage/lwlock.h | 4 +-
src/tools/pgindent/typedefs.list | 2 +
9 files changed, 213 insertions(+), 1 deletion(-)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..ea80f45716
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char key[256];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+static void init_dsm_registry(void);
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * Initialize or attach a DSM entry.
+ *
+ * *ptr should initially be set to NULL. If it is not NULL, this routine will
+ * assume that the segment has already been attached to the current session.
+ * Otherwise, this routine will set *ptr appropriately.
+ *
+ * init_callback is called to initialize the segment when it is first created.
+ */
+void
+dsm_registry_init_or_attach(const char *key, void **ptr, size_t size,
+ void (*init_callback) (void *ptr))
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ bool found;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+
+ Assert(key);
+ Assert(ptr);
+ Assert(size);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Quick exit if the value is already set. */
+ if (*ptr)
+ return;
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find_or_insert(dsm_registry_table, key_padded, &found);
+ if (!found)
+ {
+ /* Initialize DSM registry entry. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ *ptr = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (*ptr);
+ }
+ else
+ {
+ /* Attach to existing DSM registry entry. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ *ptr = dsm_segment_address(seg);
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 2225a4a6e6..034b656115 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -38,6 +38,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -113,6 +114,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -285,6 +287,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 79a16d077f..88fef448be 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 315a78cda9..f3faa991d1 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index f72f2906ce..e8f679c8ae 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -54,3 +54,4 @@ XactTruncationLock 44
WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
+DSMRegistryLock 49
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..8c311e50ae
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern void dsm_registry_init_or_attach(const char *key, void **ptr, size_t size,
+ void (*init_callback) (void *ptr));
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index b038e599c0..665d471418 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,7 +207,9 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
- LWTRANCHE_FIRST_USER_DEFINED,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
+ LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
/*
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d659adbfd6..c89a268d9e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -606,6 +606,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
--
2.25.1
v1-0002-test-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From 43c37b8dab039cb31efb8e65d67f1131b15c72b6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 4 Dec 2023 16:40:30 -0600
Subject: [PATCH v1 2/2] test dsm registry
---
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 ++
src/test/modules/test_dsm_registry/Makefile | 23 ++++++++
.../modules/test_dsm_registry/meson.build | 33 ++++++++++++
.../t/001_test_dsm_registry.pl | 25 +++++++++
.../test_dsm_registry--1.0.sql | 10 ++++
.../test_dsm_registry/test_dsm_registry.c | 54 +++++++++++++++++++
.../test_dsm_registry.control | 4 ++
9 files changed, 155 insertions(+)
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index b76f588559..bd53d52a3f 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..6f2508bc37
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+TAP_TESTS = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..e5d8272d10
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'tap': {
+ 'tests': [
+ 't/001_test_dsm_registry.pl',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl b/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl
new file mode 100644
index 0000000000..4ad6097d1c
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl
@@ -0,0 +1,25 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+$node->safe_psql('postgres', "CREATE DATABASE test;");
+$node->safe_psql('postgres', "CREATE EXTENSION test_dsm_registry;");
+$node->safe_psql('postgres', "SELECT set_val_in_shmem(1236);");
+
+$node->safe_psql('test', "CREATE EXTENSION test_dsm_registry;");
+my $result = $node->safe_psql('test', "SELECT get_val_in_shmem();");
+is($result, "1236", "check shmem val");
+
+$node->stop;
+
+done_testing();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..8f78012e52
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,54 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "port/atomics.h"
+#include "storage/dsm_registry.h"
+
+PG_MODULE_MAGIC;
+
+static pg_atomic_uint32 *val;
+
+static void
+init_val(void *ptr)
+{
+ pg_atomic_init_u32(ptr, 0);
+}
+
+static void
+dsm_registry_attach(void)
+{
+ dsm_registry_init_or_attach("test_dsm_registry", (void **) &val,
+ sizeof(pg_atomic_uint32), init_val);
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ dsm_registry_attach();
+
+ (void) pg_atomic_exchange_u32(val, PG_GETARG_UINT32(0));
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ dsm_registry_attach();
+
+ PG_RETURN_UINT32(pg_atomic_fetch_add_u32(val, 0));
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
--
2.25.1
On 12/4/23 22:46, Nathan Bossart wrote:
Every once in a while, I find myself wanting to use shared memory in a
loadable module without requiring it to be loaded at server start via
shared_preload_libraries. The DSM API offers a nice way to create and
manage dynamic shared memory segments, so creating a segment after server
start is easy enough. However, AFAICT there's no easy way to teach other
backends about the segment without storing the handles in shared memory,
which puts us right back at square one.The attached 0001 introduces a "DSM registry" to solve this problem. The
API provides an easy way to allocate/initialize a segment or to attach to
an existing one. The registry itself is just a dshash table that stores
the handles keyed by a module-specified string. 0002 adds a test for the
registry that demonstrates basic usage.I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.
Notwithstanding any dragons there may be, and not having actually looked
at the the patches, I love the concept! +<many>
--
Joe Conway
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Tue, Dec 5, 2023 at 10:35 AM Joe Conway <mail@joeconway.com> wrote:
Notwithstanding any dragons there may be, and not having actually looked
at the the patches, I love the concept! +<many>
Seems fine to me too. I haven't looked at the patches or searched for
dragons either, though.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Dec 04, 2023 at 09:46:47PM -0600, Nathan Bossart wrote:
The attached 0001 introduces a "DSM registry" to solve this problem. The
API provides an easy way to allocate/initialize a segment or to attach to
an existing one. The registry itself is just a dshash table that stores
the handles keyed by a module-specified string. 0002 adds a test for the
registry that demonstrates basic usage.I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.
Yes, tracking that in a more central way can have many usages, so your
patch sounds like a good idea. Note that we have one case in core
that be improved and make use of what you have here: autoprewarm.c.
The module supports the launch of dynamic workers but the library may
not be loaded with shared_preload_libraries, meaning that it can
allocate a chunk of shared memory worth a size of
AutoPrewarmSharedState without having requested it in a
shmem_request_hook. AutoPrewarmSharedState could be moved to a DSM
and tracked with the shared hash table introduced by the patch instead
of acquiring AddinShmemInitLock while eating the plate of other
facilities that asked for a chunk of shmem, leaving any conflict
handling to dsm_registry_table.
+dsm_registry_init_or_attach(const char *key, void **ptr, size_t size,
+ void (*init_callback) (void *ptr))
This is shaped around dshash_find_or_insert(), but it looks like you'd
want an equivalent for dshash_find(), as well.
--
Michael
On 5/12/2023 10:46, Nathan Bossart wrote:
I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.
I am delighted that you commenced this thread.
Designing extensions, every time I feel pain introducing one shared
value or some global stat, the extension must be required to be loadable
on startup only. It reduces the flexibility of even very lightweight
extensions, which look harmful to use in a cloud.
--
regards,
Andrei Lepikhov
Postgres Professional
On 18/12/2023 13:39, Andrei Lepikhov wrote:
On 5/12/2023 10:46, Nathan Bossart wrote:
I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.I am delighted that you commenced this thread.
Designing extensions, every time I feel pain introducing one shared
value or some global stat, the extension must be required to be loadable
on startup only. It reduces the flexibility of even very lightweight
extensions, which look harmful to use in a cloud.
After looking into the code, I have some comments:
1. The code looks good; I didn't find possible mishaps. Some proposed
changes are in the attachment.
2. I think a separate file for this feature looks too expensive.
According to the gist of that code, it is a part of the DSA module.
3. The dsm_registry_init_or_attach routine allocates a DSM segment. Why
not create dsa_area for a requestor and return it?
--
regards,
Andrei Lepikhov
Postgres Professional
Attachments:
additions.txttext/plain; charset=UTF-8; name=additions.txtDownload
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index ea80f45716..0343ce987f 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -45,8 +45,8 @@ static const dshash_parameters dsh_params = {
LWTRANCHE_DSM_REGISTRY_HASH
};
-static dsa_area *dsm_registry_dsa;
-static dshash_table *dsm_registry_table;
+static dsa_area *dsm_registry_dsa = NULL;
+static dshash_table *dsm_registry_table = NULL;
static void init_dsm_registry(void);
@@ -83,13 +83,20 @@ init_dsm_registry(void)
{
/* Quick exit if we already did this. */
if (dsm_registry_table)
+ {
+ Assert(dsm_registry_dsa != NULL);
return;
+ }
+
+ Assert(dsm_registry_dsa == NULL);
/* Otherwise, use a lock to ensure only one process creates the table. */
LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
{
+ Assert(DSMRegistryCtx->dsah == DSHASH_HANDLE_INVALID);
+
/* Initialize dynamic shared hash table for registry. */
dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
dsa_pin(dsm_registry_dsa);
@@ -102,6 +109,8 @@ init_dsm_registry(void)
}
else
{
+ Assert(DSMRegistryCtx->dsah != DSHASH_HANDLE_INVALID);
+
/* Attach to existing dynamic shared hash table. */
dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
dsa_pin_mapping(dsm_registry_dsa);
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 665d471418..e0e7b3b765 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -209,7 +209,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_LAUNCHER_HASH,
LWTRANCHE_DSM_REGISTRY_DSA,
LWTRANCHE_DSM_REGISTRY_HASH,
- LWTRANCHE_FIRST_USER_DEFINED
+ LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
/*
Hi!
This patch looks like a good solution for a pain in the ass, I'm too for
this patch to be committed.
Have looked through the code and agree with Andrei, the code looks good.
Just a suggestion - maybe it is worth adding a function for detaching the
segment,
for cases when we unload and/or re-load the extension?
--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
https://postgrespro.ru/
On Mon, Dec 18, 2023 at 3:32 AM Andrei Lepikhov
<a.lepikhov@postgrespro.ru> wrote:
2. I think a separate file for this feature looks too expensive.
According to the gist of that code, it is a part of the DSA module.
-1. I think this is a totally different thing than DSA. More files
aren't nearly as expensive as the confusion that comes from smushing
unrelated things together.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Dec 18, 2023 at 03:32:08PM +0700, Andrei Lepikhov wrote:
3. The dsm_registry_init_or_attach routine allocates a DSM segment. Why not
create dsa_area for a requestor and return it?
My assumption is that most modules just need a fixed-size segment, and if
they really needed a DSA segment, the handle, tranche ID, etc. could just
be stored in the DSM segment. Maybe that assumption is wrong, though...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Dec 18, 2023 at 12:05:28PM +0300, Nikita Malakhov wrote:
Just a suggestion - maybe it is worth adding a function for detaching the
segment,
for cases when we unload and/or re-load the extension?
Hm. We don't presently have a good way to unload a library, but you can
certainly DROP EXTENSION, in which case you might expect the segment to go
away or at least be reset. But even today, once a preloaded library is
loaded, it stays loaded and its shared memory remains regardless of whether
you CREATE/DROP extension. Can you think of problems with keeping the
segment attached?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Dec 19, 2023 at 10:49:23AM -0500, Robert Haas wrote:
On Mon, Dec 18, 2023 at 3:32 AM Andrei Lepikhov
<a.lepikhov@postgrespro.ru> wrote:2. I think a separate file for this feature looks too expensive.
According to the gist of that code, it is a part of the DSA module.-1. I think this is a totally different thing than DSA. More files
aren't nearly as expensive as the confusion that comes from smushing
unrelated things together.
Agreed. I think there's a decent chance that more functionality will be
added to this registry down the line, in which case it will be even more
important that this stuff stays separate from the tools it is built with.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Dec 08, 2023 at 04:36:52PM +0900, Michael Paquier wrote:
Yes, tracking that in a more central way can have many usages, so your
patch sounds like a good idea. Note that we have one case in core
that be improved and make use of what you have here: autoprewarm.c.
I'll add a patch for autoprewarm.c. Even if we don't proceed with that
change, it'll be a good demonstration.
+dsm_registry_init_or_attach(const char *key, void **ptr, size_t size, + void (*init_callback) (void *ptr))This is shaped around dshash_find_or_insert(), but it looks like you'd
want an equivalent for dshash_find(), as well.
What is the use-case for only verifying the existence of a segment?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Dec 19, 2023 at 10:19:11AM -0600, Nathan Bossart wrote:
On Fri, Dec 08, 2023 at 04:36:52PM +0900, Michael Paquier wrote:
Yes, tracking that in a more central way can have many usages, so your
patch sounds like a good idea. Note that we have one case in core
that be improved and make use of what you have here: autoprewarm.c.I'll add a patch for autoprewarm.c. Even if we don't proceed with that
change, it'll be a good demonstration.
Cool, thanks. It could just be a separate change on top of the main
one.
+dsm_registry_init_or_attach(const char *key, void **ptr, size_t size, + void (*init_callback) (void *ptr))This is shaped around dshash_find_or_insert(), but it looks like you'd
want an equivalent for dshash_find(), as well.What is the use-case for only verifying the existence of a segment?
One case I was thinking about is parallel aggregates that can define
combining and serial/deserial functions, where some of the operations
could happen in shared memory, requiring a DSM, and where each process
doing some aggregate combining would expect a DSM to exist before
making use of it. So a registry wrapper for dshash_find() could be
used as a way to perform sanity checks with what's stored in the
registry.
--
Michael
On Tue, Dec 19, 2023 at 10:14:44AM -0600, Nathan Bossart wrote:
On Tue, Dec 19, 2023 at 10:49:23AM -0500, Robert Haas wrote:
On Mon, Dec 18, 2023 at 3:32 AM Andrei Lepikhov
<a.lepikhov@postgrespro.ru> wrote:2. I think a separate file for this feature looks too expensive.
According to the gist of that code, it is a part of the DSA module.-1. I think this is a totally different thing than DSA. More files
aren't nearly as expensive as the confusion that comes from smushing
unrelated things together.Agreed. I think there's a decent chance that more functionality will be
added to this registry down the line, in which case it will be even more
important that this stuff stays separate from the tools it is built with.
+1 for keeping a clean separation between both.
--
Michael
On 20/12/2023 07:04, Michael Paquier wrote:
On Tue, Dec 19, 2023 at 10:14:44AM -0600, Nathan Bossart wrote:
On Tue, Dec 19, 2023 at 10:49:23AM -0500, Robert Haas wrote:
On Mon, Dec 18, 2023 at 3:32 AM Andrei Lepikhov
<a.lepikhov@postgrespro.ru> wrote:2. I think a separate file for this feature looks too expensive.
According to the gist of that code, it is a part of the DSA module.-1. I think this is a totally different thing than DSA. More files
aren't nearly as expensive as the confusion that comes from smushing
unrelated things together.Agreed. I think there's a decent chance that more functionality will be
added to this registry down the line, in which case it will be even more
important that this stuff stays separate from the tools it is built with.+1 for keeping a clean separation between both.
Thanks, I got the reason.
In that case, maybe change the test case to make it closer to real-life
usage - with locks and concurrent access (See attachment)?
--
regards,
Andrei Lepikhov
Postgres Professional
Attachments:
elaborate_tests.txttext/plain; charset=UTF-8; name=elaborate_tests.txtDownload
diff --git a/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl b/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl
index 4ad6097d1c..762489f3dd 100644
--- a/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl
+++ b/src/test/modules/test_dsm_registry/t/001_test_dsm_registry.pl
@@ -13,12 +13,27 @@ $node->init;
$node->start;
$node->safe_psql('postgres', "CREATE DATABASE test;");
-$node->safe_psql('postgres', "CREATE EXTENSION test_dsm_registry;");
-$node->safe_psql('postgres', "SELECT set_val_in_shmem(1236);");
-
$node->safe_psql('test', "CREATE EXTENSION test_dsm_registry;");
-my $result = $node->safe_psql('test', "SELECT get_val_in_shmem();");
-is($result, "1236", "check shmem val");
+
+my ($initial_value, $result, $custom_script);
+
+$initial_value = $node->safe_psql('test', "SELECT get_val_in_shmem();");
+
+$custom_script = File::Temp->new();
+append_to_file($custom_script, q{
+ \set val random(0, 1999)
+ SELECT increment_val_in_shmem(:val);
+ SELECT increment_val_in_shmem(-(:val));
+});
+
+$node->command_ok([ 'pgbench', '-i', 'test' ], 'Init database');
+$node->command_ok([ 'pgbench', '-t', '31', '-c', '3', '-j', '3',
+ '-f', "$custom_script", 'test' ],
+ 'Change shared value simlutaneously');
+
+$result = $node->safe_psql('test', "SELECT get_val_in_shmem();");
+print("res $initial_value, $result");
+is($result, $initial_value, "Check concurrent access");
$node->stop;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
index 8c55b0919b..0144845afa 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -3,7 +3,7 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
-CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+CREATE FUNCTION increment_val_in_shmem(val INT) RETURNS VOID
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION get_val_in_shmem() RETURNS INT
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 8f78012e52..eed29a9f34 100644
--- a/src/test/modules/test_dsm_registry/test_dsm_registry.c
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -13,33 +13,48 @@
#include "postgres.h"
#include "fmgr.h"
+#include "common/pg_prng.h"
#include "port/atomics.h"
#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
PG_MODULE_MAGIC;
-static pg_atomic_uint32 *val;
+typedef struct
+{
+ LWLock lock;
+ uint32 value;
+} SharedState;
+
+static SharedState *extstate;
static void
-init_val(void *ptr)
+init_dsm(void *ptr)
{
- pg_atomic_init_u32(ptr, 0);
+ SharedState *state = (SharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->value = pg_prng_uint32(&pg_global_prng_state);
}
static void
dsm_registry_attach(void)
{
- dsm_registry_init_or_attach("test_dsm_registry", (void **) &val,
- sizeof(pg_atomic_uint32), init_val);
+ dsm_registry_init_or_attach("test_dsm_registry", (void **) &extstate,
+ sizeof(SharedState), init_dsm);
}
-PG_FUNCTION_INFO_V1(set_val_in_shmem);
+PG_FUNCTION_INFO_V1(increment_val_in_shmem);
Datum
-set_val_in_shmem(PG_FUNCTION_ARGS)
+increment_val_in_shmem(PG_FUNCTION_ARGS)
{
+ uint32 increment = PG_GETARG_UINT32(0);
+
dsm_registry_attach();
- (void) pg_atomic_exchange_u32(val, PG_GETARG_UINT32(0));
+ LWLockAcquire(&extstate->lock, LW_EXCLUSIVE);
+ extstate->value += increment;
+ LWLockRelease(&extstate->lock);
PG_RETURN_VOID();
}
@@ -48,7 +63,13 @@ PG_FUNCTION_INFO_V1(get_val_in_shmem);
Datum
get_val_in_shmem(PG_FUNCTION_ARGS)
{
+ uint32 result;
+
dsm_registry_attach();
- PG_RETURN_UINT32(pg_atomic_fetch_add_u32(val, 0));
+ LWLockAcquire(&extstate->lock, LW_SHARED);
+ result = extstate->value;
+ LWLockRelease(&extstate->lock);
+
+ PG_RETURN_UINT32(result);
}
On Tue, Dec 5, 2023 at 9:17 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Every once in a while, I find myself wanting to use shared memory in a
loadable module without requiring it to be loaded at server start via
shared_preload_libraries. The DSM API offers a nice way to create and
manage dynamic shared memory segments, so creating a segment after server
start is easy enough. However, AFAICT there's no easy way to teach other
backends about the segment without storing the handles in shared memory,
which puts us right back at square one.The attached 0001 introduces a "DSM registry" to solve this problem. The
API provides an easy way to allocate/initialize a segment or to attach to
an existing one. The registry itself is just a dshash table that stores
the handles keyed by a module-specified string. 0002 adds a test for the
registry that demonstrates basic usage.
+1 for something like this.
I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.
Isn't the worker_spi best place to show the use of the DSM registry
instead of a separate test extension? Note the custom wait event
feature that added its usage code to worker_spi. Since worker_spi
demonstrates typical coding patterns, having just set_val_in_shmem()
and get_val_in_shmem() in there makes this patch simple and shaves
some code off.
Comments on the v1 patch set:
1. IIUC, this feature lets external modules create as many DSM
segments as possible with different keys right? If yes, is capping the
max number of DSMs a good idea?
2. Why does this feature have to deal with DSMs? Why not DSAs? With
DSA and an API that gives the DSA handle to the external modules, the
modules can dsa_allocate and dsa_free right? Do you see any problem
with it?
3.
+typedef struct DSMRegistryEntry
+{
+ char key[256];
Key length 256 feels too much, can it be capped at NAMEDATALEN 64
bytes (similar to some of the key lengths for hash_create()) to start
with?
4. Do we need on_dsm_detach for each DSM created?
dsm_backend_shutdown
5.
+ *
+ * *ptr should initially be set to NULL. If it is not NULL, this routine will
+ * assume that the segment has already been attached to the current session.
+ * Otherwise, this routine will set *ptr appropriately.
+ /* Quick exit if the value is already set. */
+ if (*ptr)
+ return;
Instead of the above assumption and quick exit condition, can it be
something like if (dsm_find_mapping(dsm_segment_handle(*ptr)) != NULL)
return;?
6.
+static pg_atomic_uint32 *val;
Any specific reason for it to be an atomic variable?
7.
+static pg_atomic_uint32 *val;
Instead of a run-of-the-mill example with just an integer val that
gets stored in shared memory, can it be something more realistic, a
struct with 2 or more variables or a struct to store linked list
(slist_head or dlist_head) in shared memory or such?
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Wed, Dec 20, 2023 at 11:02:58AM +0200, Andrei Lepikhov wrote:
In that case, maybe change the test case to make it closer to real-life
usage - with locks and concurrent access (See attachment)?
I'm not following why we should make this test case more complicated. It
is only intended to test the DSM registry machinery, and setting/retrieving
an atomic variable seems like a realistic use-case to me.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Hi, all
I see most xxxShmemInit functions have the logic to handle IsUnderPostmaster env.
Do we need to consider it in DSMRegistryShmemInit() too? For example, add some assertions.
Others LGTM.
Zhang Mingli
www.hashdata.xyz
Show quoted text
On Dec 5, 2023 at 11:47 +0800, Nathan Bossart <nathandbossart@gmail.com>, wrote:
Every once in a while, I find myself wanting to use shared memory in a
loadable module without requiring it to be loaded at server start via
shared_preload_libraries. The DSM API offers a nice way to create and
manage dynamic shared memory segments, so creating a segment after server
start is easy enough. However, AFAICT there's no easy way to teach other
backends about the segment without storing the handles in shared memory,
which puts us right back at square one.The attached 0001 introduces a "DSM registry" to solve this problem. The
API provides an easy way to allocate/initialize a segment or to attach to
an existing one. The registry itself is just a dshash table that stores
the handles keyed by a module-specified string. 0002 adds a test for the
registry that demonstrates basic usage.I don't presently have any concrete plans to use this for anything, but I
thought it might be useful for extensions for caching, etc. and wanted to
see whether there was any interest in the feature.--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Wed, Dec 20, 2023 at 03:28:38PM +0530, Bharath Rupireddy wrote:
Isn't the worker_spi best place to show the use of the DSM registry
instead of a separate test extension? Note the custom wait event
feature that added its usage code to worker_spi. Since worker_spi
demonstrates typical coding patterns, having just set_val_in_shmem()
and get_val_in_shmem() in there makes this patch simple and shaves
some code off.
I don't agree. The test case really isn't that complicated, and I'd rather
have a dedicated test suite for this feature that we can build on instead
of trying to squeeze it into something unrelated.
1. IIUC, this feature lets external modules create as many DSM
segments as possible with different keys right? If yes, is capping the
max number of DSMs a good idea?
Why? Even if it is a good idea, what limit could we choose that wouldn't
be arbitrary and eventually cause problems down the road?
2. Why does this feature have to deal with DSMs? Why not DSAs? With
DSA and an API that gives the DSA handle to the external modules, the
modules can dsa_allocate and dsa_free right? Do you see any problem
with it?
Please see upthread discussion [0]/messages/by-id/20231219160117.GB831499@nathanxps13.
+typedef struct DSMRegistryEntry +{ + char key[256];Key length 256 feels too much, can it be capped at NAMEDATALEN 64
bytes (similar to some of the key lengths for hash_create()) to start
with?
Why is it too much?
4. Do we need on_dsm_detach for each DSM created?
Presently, I've designed this such that the DSM remains attached for the
lifetime of a session (and stays present even if all attached sessions go
away) to mimic what you get when you allocate shared memory during startup.
Perhaps there's a use-case for having backends do some cleanup before
exiting, in which case a detach_cb might be useful. IMHO we should wait
for a concrete use-case before adding too many bells and whistles, though.
+ * *ptr should initially be set to NULL. If it is not NULL, this routine will + * assume that the segment has already been attached to the current session. + * Otherwise, this routine will set *ptr appropriately.+ /* Quick exit if the value is already set. */ + if (*ptr) + return;Instead of the above assumption and quick exit condition, can it be
something like if (dsm_find_mapping(dsm_segment_handle(*ptr)) != NULL)
return;?
Yeah, I think something like that could be better. One of the things I
dislike about the v1 API is that it depends a little too much on the caller
doing exactly the right things, and I think it's possible to make it a
little more robust.
+static pg_atomic_uint32 *val;
Any specific reason for it to be an atomic variable?
A regular integer would probably be fine for testing, but I figured we
might as well ensure correctness for when this code is inevitably
copy/pasted somewhere.
+static pg_atomic_uint32 *val;
Instead of a run-of-the-mill example with just an integer val that
gets stored in shared memory, can it be something more realistic, a
struct with 2 or more variables or a struct to store linked list
(slist_head or dlist_head) in shared memory or such?
This is the second time this has come up [1]/messages/by-id/20231220153342.GA833819@nathanxps13. The intent of this test is
to verify the DSM registry behavior, not how folks are going to use the
shared memory it manages, so I'm really not inclined to make this more
complicated without a good reason. I don't mind changing this if I'm
outvoted on this one, though.
[0]: /messages/by-id/20231219160117.GB831499@nathanxps13
[1]: /messages/by-id/20231220153342.GA833819@nathanxps13
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Dec 21, 2023 at 12:03:18AM +0800, Zhang Mingli wrote:
I see most xxxShmemInit functions have the logic to handle IsUnderPostmaster env.
Do we need to consider it in DSMRegistryShmemInit() too? For example, add some assertions.
Others LGTM.
Good point. I _think_ the registry is safe to set up and use in
single-user mode but not in a regular postmaster process. It'd probably be
wise to add some assertions along those lines, but even if we didn't, I
think the DSM code has existing assertions that will catch it. In any
case, I'd like to avoid requiring folks to add special
single-user-mode-only logic if we can avoid it.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On 20/12/2023 17:33, Nathan Bossart wrote:
On Wed, Dec 20, 2023 at 11:02:58AM +0200, Andrei Lepikhov wrote:
In that case, maybe change the test case to make it closer to real-life
usage - with locks and concurrent access (See attachment)?I'm not following why we should make this test case more complicated. It
is only intended to test the DSM registry machinery, and setting/retrieving
an atomic variable seems like a realistic use-case to me.
I could provide you at least two reasons here:
1. A More complicated example would be a tutorial on using the feature
correctly. It will reduce the number of questions in mailing lists.
2. Looking into existing extensions, I see that the most common case of
using a shared memory segment is maintaining some hash table or state
structure that needs at least one lock.
Try to rewrite the pg_prewarm according to this new feature, and you
will realize how difficult it is.
--
regards,
Andrei Lepikhov
Postgres Professional
Here is a new version of the patch. In addition to various small changes,
I've rewritten the test suite to use an integer and a lock, added a
dsm_registry_find() function, and adjusted autoprewarm to use the registry.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v2-0001-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From f8d48285e8117ff0bbf35f5022f3096df59385ae Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v2 1/2] add dsm registry
---
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 209 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 24 ++
src/include/storage/lwlock.h | 4 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 ++
.../expected/test_dsm_registry.out | 16 ++
.../modules/test_dsm_registry/meson.build | 33 +++
.../sql/test_dsm_registry.sql | 7 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 75 +++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
20 files changed, 426 insertions(+), 1 deletion(-)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..5fc970001e
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,209 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char key[256];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+static void init_dsm_registry(void);
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * Find an existing DSM entry.
+ *
+ * This routine returns whether a segment is registered with the provided key.
+ */
+bool
+dsm_registry_find(const char *key)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+
+ Assert(key);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find(dsm_registry_table, key_padded, false);
+ if (entry)
+ dshash_release_lock(dsm_registry_table, entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return entry != NULL;
+}
+
+/*
+ * Initialize or attach a DSM entry.
+ *
+ * This routine returns the address of the segment. init_callback is called to
+ * initialize the segment when it is first created.
+ */
+void *
+dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(key);
+ Assert(size);
+ Assert(found);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find_or_insert(dsm_registry_table, key_padded, found);
+ if (!(*found))
+ {
+ /* Initialize DSM registry entry. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing DSM registry entry. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of already-attached DSM registry entry. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 706140eb9f..44d6a243e5 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 79a16d077f..88fef448be 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 315a78cda9..f3faa991d1 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7e79163466..de1bc986e1 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -324,6 +324,7 @@ XactTruncation "Waiting to execute <function>pg_xact_status</function> or update
WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consumption."
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
XactBuffer "Waiting for I/O on a transaction status SLRU buffer."
CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer."
@@ -354,6 +355,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..5e9c1000bc
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern bool dsm_registry_find(const char *key);
+extern void *dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index b038e599c0..665d471418 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,7 +207,9 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
- LWTRANCHE_FIRST_USER_DEFINED,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
+ LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
/*
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index b76f588559..bd53d52a3f 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..b9746f8a1e
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,16 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+CREATE DATABASE test;
+\c test
+CREATE EXTENSION test_dsm_registry;
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..4a7992109b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..bf2f468f10
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,7 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+
+CREATE DATABASE test;
+\c test
+CREATE EXTENSION test_dsm_registry;
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..068f47ed02
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,75 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+init_state(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+dsm_registry_attach(void)
+{
+ bool found;
+
+ tdr_state = dsm_registry_init_or_attach("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ init_state, &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e37ef9aa76..4270109fb8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2796,6 +2798,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
v2-0002-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From 5b187c946c77d28607fff3fa41372ddff0622212 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v2 2/2] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 44 ++++++++++----------------------
1 file changed, 14 insertions(+), 30 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 0993bd2453..ed12142d04 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -38,6 +38,7 @@
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +96,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +139,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +751,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +771,9 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = dsm_registry_init_or_attach("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ init_state, &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
On Wed, Dec 27, 2023 at 01:53:27PM -0600, Nathan Bossart wrote:
Here is a new version of the patch. In addition to various small changes,
I've rewritten the test suite to use an integer and a lock, added a
dsm_registry_find() function, and adjusted autoprewarm to use the registry.
Here's a v3 that fixes a silly mistake in the test.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v3-0001-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From b9b1638c9eb55eba51356690a2b3da1fe0b2b14e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v3 1/2] add dsm registry
---
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 209 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 24 ++
src/include/storage/lwlock.h | 2 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 ++
.../expected/test_dsm_registry.out | 14 ++
.../modules/test_dsm_registry/meson.build | 33 +++
.../sql/test_dsm_registry.sql | 4 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 75 +++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
20 files changed, 420 insertions(+)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..5fc970001e
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,209 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char key[256];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+static void init_dsm_registry(void);
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * Find an existing DSM entry.
+ *
+ * This routine returns whether a segment is registered with the provided key.
+ */
+bool
+dsm_registry_find(const char *key)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+
+ Assert(key);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find(dsm_registry_table, key_padded, false);
+ if (entry)
+ dshash_release_lock(dsm_registry_table, entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return entry != NULL;
+}
+
+/*
+ * Initialize or attach a DSM entry.
+ *
+ * This routine returns the address of the segment. init_callback is called to
+ * initialize the segment when it is first created.
+ */
+void *
+dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(key);
+ Assert(size);
+ Assert(found);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find_or_insert(dsm_registry_table, key_padded, found);
+ if (!(*found))
+ {
+ /* Initialize DSM registry entry. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing DSM registry entry. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of already-attached DSM registry entry. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 706140eb9f..44d6a243e5 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 79a16d077f..88fef448be 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 315a78cda9..f3faa991d1 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7e79163466..de1bc986e1 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -324,6 +324,7 @@ XactTruncation "Waiting to execute <function>pg_xact_status</function> or update
WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consumption."
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
XactBuffer "Waiting for I/O on a transaction status SLRU buffer."
CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer."
@@ -354,6 +355,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..5e9c1000bc
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern bool dsm_registry_find(const char *key);
+extern void *dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index b038e599c0..e0e7b3b765 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index b76f588559..bd53d52a3f 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..8ffbd343a0
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+\c
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..4a7992109b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..b3351be0a1
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+\c
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..068f47ed02
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,75 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+init_state(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+dsm_registry_attach(void)
+{
+ bool found;
+
+ tdr_state = dsm_registry_init_or_attach("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ init_state, &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e37ef9aa76..4270109fb8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2796,6 +2798,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
v3-0002-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From 4f7a4672aee298ee86bdb54a2cf1438fa80ced35 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v3 2/2] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 44 ++++++++++----------------------
1 file changed, 14 insertions(+), 30 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 0993bd2453..ed12142d04 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -38,6 +38,7 @@
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +96,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +139,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +751,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +771,9 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = dsm_registry_init_or_attach("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ init_state, &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
On Wed, Dec 20, 2023 at 9:33 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Wed, Dec 20, 2023 at 03:28:38PM +0530, Bharath Rupireddy wrote:
Isn't the worker_spi best place to show the use of the DSM registry
instead of a separate test extension? Note the custom wait event
feature that added its usage code to worker_spi. Since worker_spi
demonstrates typical coding patterns, having just set_val_in_shmem()
and get_val_in_shmem() in there makes this patch simple and shaves
some code off.I don't agree. The test case really isn't that complicated, and I'd rather
have a dedicated test suite for this feature that we can build on instead
of trying to squeeze it into something unrelated.
With the use of dsm registry for pg_prewarm, do we need this
test_dsm_registry module at all? Because 0002 patch pretty much
demonstrates how to use the DSM registry. With this comment and my
earlier comment on incorporating the use of dsm registry in
worker_spi, I'm trying to make a point to reduce the code for this
feature. However, if others have different opinions, I'm okay with
having a separate test module.
1. IIUC, this feature lets external modules create as many DSM
segments as possible with different keys right? If yes, is capping the
max number of DSMs a good idea?Why? Even if it is a good idea, what limit could we choose that wouldn't
be arbitrary and eventually cause problems down the road?
I've tried with a shared memory structure size of 10GB on my
development machine and I have seen an intermittent crash (I haven't
got a chance to dive deep into it). I think the shared memory that a
named-DSM segment can get via this DSM registry feature depends on the
total shared memory area that a typical DSM client can allocate today.
I'm not sure it's worth it to limit the shared memory for this feature
given that we don't limit the shared memory via startup hook.
2. Why does this feature have to deal with DSMs? Why not DSAs? With
DSA and an API that gives the DSA handle to the external modules, the
modules can dsa_allocate and dsa_free right? Do you see any problem
with it?Please see upthread discussion [0].
Per my understanding, this feature allows one to define and manage
named-DSM segments.
+typedef struct DSMRegistryEntry +{ + char key[256];Key length 256 feels too much, can it be capped at NAMEDATALEN 64
bytes (similar to some of the key lengths for hash_create()) to start
with?Why is it too much?
Are we expecting, for instance, a 128-bit UUID being used as a key and
hence limiting it to a higher value 256 instead of just NAMEDATALEN?
My thoughts were around saving a few bytes of shared memory space that
can get higher when multiple modules using a DSM registry with
multiple DSM segments.
4. Do we need on_dsm_detach for each DSM created?
Presently, I've designed this such that the DSM remains attached for the
lifetime of a session (and stays present even if all attached sessions go
away) to mimic what you get when you allocate shared memory during startup.
Perhaps there's a use-case for having backends do some cleanup before
exiting, in which case a detach_cb might be useful. IMHO we should wait
for a concrete use-case before adding too many bells and whistles, though.
On Thu, Dec 28, 2023 at 9:05 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Wed, Dec 27, 2023 at 01:53:27PM -0600, Nathan Bossart wrote:
Here is a new version of the patch. In addition to various small changes,
I've rewritten the test suite to use an integer and a lock, added a
dsm_registry_find() function, and adjusted autoprewarm to use the registry.Here's a v3 that fixes a silly mistake in the test.
Some comments on the v3 patch set:
1. Typo: missing "an" before "already-attached".
+ /* Return address of already-attached DSM registry entry. */
2. Do you see any immediate uses of dsm_registry_find()? Especially
given that dsm_registry_init_or_attach() does the necessary work
(returns the DSM address if DSM already exists for a given key) for a
postgres process. If there is no immediate use (the argument to remove
this function goes similar to detach_cb above), I'm sure we can
introduce it when there's one.
3. I think we don't need miscadmin.h inclusion in autoprewarm.c, do we?
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Fri, Dec 29, 2023 at 08:53:54PM +0530, Bharath Rupireddy wrote:
With the use of dsm registry for pg_prewarm, do we need this
test_dsm_registry module at all? Because 0002 patch pretty much
demonstrates how to use the DSM registry. With this comment and my
earlier comment on incorporating the use of dsm registry in
worker_spi, I'm trying to make a point to reduce the code for this
feature. However, if others have different opinions, I'm okay with
having a separate test module.
I don't have a strong opinion here, but I lean towards still having a
dedicated test suite, if for no other reason that to guarantee some
coverage even if the other in-tree uses disappear.
I've tried with a shared memory structure size of 10GB on my
development machine and I have seen an intermittent crash (I haven't
got a chance to dive deep into it). I think the shared memory that a
named-DSM segment can get via this DSM registry feature depends on the
total shared memory area that a typical DSM client can allocate today.
I'm not sure it's worth it to limit the shared memory for this feature
given that we don't limit the shared memory via startup hook.
I would be interested to see more details about the crashes you are seeing.
Is this unique to the registry or an existing problem with DSM segments?
Are we expecting, for instance, a 128-bit UUID being used as a key and
hence limiting it to a higher value 256 instead of just NAMEDATALEN?
My thoughts were around saving a few bytes of shared memory space that
can get higher when multiple modules using a DSM registry with
multiple DSM segments.
I'm not really expecting folks to use more than, say, 16 characters for the
key, but I intentionally set it much higher in case someone did have a
reason to use longer keys. I'll lower it to 64 in the next revision unless
anyone else objects.
2. Do you see any immediate uses of dsm_registry_find()? Especially
given that dsm_registry_init_or_attach() does the necessary work
(returns the DSM address if DSM already exists for a given key) for a
postgres process. If there is no immediate use (the argument to remove
this function goes similar to detach_cb above), I'm sure we can
introduce it when there's one.
See [0]/messages/by-id/ZYIu_JL-usgd3E1q@paquier.xyz. FWIW I tend to agree that we could leave this one out for now.
3. I think we don't need miscadmin.h inclusion in autoprewarm.c, do we?
I'll take a look at this one.
[0]: /messages/by-id/ZYIu_JL-usgd3E1q@paquier.xyz
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Jan 2, 2024 at 11:21 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Are we expecting, for instance, a 128-bit UUID being used as a key and
hence limiting it to a higher value 256 instead of just NAMEDATALEN?
My thoughts were around saving a few bytes of shared memory space that
can get higher when multiple modules using a DSM registry with
multiple DSM segments.I'm not really expecting folks to use more than, say, 16 characters for the
key, but I intentionally set it much higher in case someone did have a
reason to use longer keys. I'll lower it to 64 in the next revision unless
anyone else objects.
This surely doesn't matter either way. We're not expecting this hash
table to have more than a handful of entries; the difference between
256, 64, and NAMEDATALEN won't even add up to kilobytes in any
realistic scenario, let along MB or GB.
--
Robert Haas
EDB: http://www.enterprisedb.com
Here's a new version of the patch set with Bharath's feedback addressed.
On Tue, Jan 02, 2024 at 11:31:14AM -0500, Robert Haas wrote:
On Tue, Jan 2, 2024 at 11:21 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Are we expecting, for instance, a 128-bit UUID being used as a key and
hence limiting it to a higher value 256 instead of just NAMEDATALEN?
My thoughts were around saving a few bytes of shared memory space that
can get higher when multiple modules using a DSM registry with
multiple DSM segments.I'm not really expecting folks to use more than, say, 16 characters for the
key, but I intentionally set it much higher in case someone did have a
reason to use longer keys. I'll lower it to 64 in the next revision unless
anyone else objects.This surely doesn't matter either way. We're not expecting this hash
table to have more than a handful of entries; the difference between
256, 64, and NAMEDATALEN won't even add up to kilobytes in any
realistic scenario, let along MB or GB.
Right.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v4-0001-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From 01343422efdd4bb0d3ccc4c45aa7e964ca5d04d0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v4 1/2] add dsm registry
---
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 176 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 23 +++
src/include/storage/lwlock.h | 2 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 +++
.../expected/test_dsm_registry.out | 14 ++
.../modules/test_dsm_registry/meson.build | 33 ++++
.../sql/test_dsm_registry.sql | 4 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 75 ++++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
20 files changed, 386 insertions(+)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..2b2be4bb99
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char key[64];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+static void init_dsm_registry(void);
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * Initialize or attach a DSM entry.
+ *
+ * This routine returns the address of the segment. init_callback is called to
+ * initialize the segment when it is first created.
+ */
+void *
+dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(key);
+ Assert(size);
+ Assert(found);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find_or_insert(dsm_registry_table, key_padded, found);
+ if (!(*found))
+ {
+ /* Initialize DSM registry entry. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing DSM registry entry. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of already-attached DSM registry entry. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 706140eb9f..44d6a243e5 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 79a16d077f..88fef448be 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 315a78cda9..f3faa991d1 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index f61ec3e59d..f13077bd8c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -325,6 +325,7 @@ WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consu
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
WALSummarizer "Waiting to read or update WAL summarization state."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
XactBuffer "Waiting for I/O on a transaction status SLRU buffer."
CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer."
@@ -355,6 +356,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..247ac0acc1
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern void *dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index b038e599c0..e0e7b3b765 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index b76f588559..bd53d52a3f 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..8ffbd343a0
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+\c
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..4a7992109b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..b3351be0a1
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+\c
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..068f47ed02
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,75 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+init_state(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+dsm_registry_attach(void)
+{
+ bool found;
+
+ tdr_state = dsm_registry_init_or_attach("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ init_state, &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5fd46b7bd1..469f7570f5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2799,6 +2801,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
v4-0002-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From 30e083b2930ce88bf7b9bbb925bd24953074635b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v4 2/2] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 45 ++++++++++----------------------
1 file changed, 14 insertions(+), 31 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 0993bd2453..56406ab6ba 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -32,12 +32,12 @@
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
-#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +95,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +138,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +750,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +770,9 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = dsm_registry_init_or_attach("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ init_state, &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
On Wed, Jan 3, 2024 at 4:19 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Here's a new version of the patch set with Bharath's feedback addressed.
Thanks. The v4 patches look good to me except for a few minor
comments. I've marked it as RfC in CF.
1. Update all the copyright to the new year. A run of
src/tools/copyright.pl on the source tree will take care of it at some
point, but still it's good if we can update while we are here.
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+# Copyright (c) 2023, PostgreSQL Global Development Group
+ * Copyright (c) 2023, PostgreSQL Global Development Group
2. Typo: missing "an" before "already-attached".
+ /* Return address of already-attached DSM registry entry. */
3. Use NAMEDATALEN instead of 64?
+ char key[64];
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Sat, Jan 06, 2024 at 07:34:15PM +0530, Bharath Rupireddy wrote:
1. Update all the copyright to the new year. A run of
src/tools/copyright.pl on the source tree will take care of it at some
point, but still it's good if we can update while we are here.
Done.
2. Typo: missing "an" before "already-attached".
+ /* Return address of already-attached DSM registry entry. */
Done.
3. Use NAMEDATALEN instead of 64?
+ char key[64];
I kept this the same, as I didn't see any need to tie the key size to
NAMEDATALEN.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v5-0001-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From c09b49c5a75846c537aaaf1e39843123ac5a6d91 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v5 1/2] add dsm registry
---
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 176 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 23 +++
src/include/storage/lwlock.h | 2 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 +++
.../expected/test_dsm_registry.out | 14 ++
.../modules/test_dsm_registry/meson.build | 33 ++++
.../sql/test_dsm_registry.sql | 4 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 75 ++++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
20 files changed, 386 insertions(+)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..3a32dcfe40
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,176 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char key[64];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+static void init_dsm_registry(void);
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * Initialize or attach a DSM entry.
+ *
+ * This routine returns the address of the segment. init_callback is called to
+ * initialize the segment when it is first created.
+ */
+void *
+dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char key_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(key);
+ Assert(size);
+ Assert(found);
+
+ if (strlen(key) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM registry key too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(key_padded, key);
+ entry = dshash_find_or_insert(dsm_registry_table, key_padded, found);
+ if (!(*found))
+ {
+ /* Initialize DSM registry entry. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing DSM registry entry. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of an already-attached DSM registry entry. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e5119ed55d..fbc62b1563 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 08bdc718b8..5a936171f7 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index b4b989ac56..2f2de5a562 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 088eb977d4..33c4c645b8 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -325,6 +325,7 @@ WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consu
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
WALSummarizer "Waiting to read or update WAL summarization state."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
XactBuffer "Waiting for I/O on a transaction status SLRU buffer."
CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer."
@@ -355,6 +356,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..5d4cf9ca49
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern void *dsm_registry_init_or_attach(const char *key, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 167ae34208..50a65e046d 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 00ff1d77d1..2c3b8d73bc 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..8ffbd343a0
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+\c
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..a4045fea37
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..b3351be0a1
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+\c
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..b57f13cebb
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,75 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+init_state(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+dsm_registry_attach(void)
+{
+ bool found;
+
+ tdr_state = dsm_registry_init_or_attach("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ init_state, &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ dsm_registry_attach();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5fd46b7bd1..469f7570f5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2799,6 +2801,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
v5-0002-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From b690a9be35085341d53cd3a937193b2f49abb158 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v5 2/2] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 45 ++++++++++----------------------
1 file changed, 14 insertions(+), 31 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 9085a409db..b9fd724788 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -32,12 +32,12 @@
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
-#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +95,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +138,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +750,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +770,9 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = dsm_registry_init_or_attach("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ init_state, &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
On Sat, Jan 6, 2024 at 10:05 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I kept this the same, as I didn't see any need to tie the key size to
NAMEDATALEN.
Thanks. A fresh look at the v5 patches left me with the following thoughts:
1. I think we need to add some notes about this new way of getting
shared memory for external modules in the <title>Shared Memory and
LWLocks</title> section in xfunc.sgml? This will at least tell there's
another way for external modules to get shared memory, not just with
the shmem_request_hook and shmem_startup_hook. What do you think?
2. FWIW, I'd like to call this whole feature "Support for named DSM
segments in Postgres". Do you see anything wrong with this?
3. IIUC, this feature eventually makes both shmem_request_hook and
shmem_startup_hook pointless, no? Or put another way, what's the
significance of shmem request and startup hooks in lieu of this new
feature? I think it's quite possible to get rid of the shmem request
and startup hooks (of course, not now but at some point in future to
not break the external modules), because all the external modules can
allocate and initialize the same shared memory via
dsm_registry_init_or_attach and its init_callback. All the external
modules will then need to call dsm_registry_init_or_attach in their
_PG_init callbacks and/or in their bg worker's main functions in case
the modules intend to start up bg workers. Am I right?
4. With the understanding in comment #3, can pg_stat_statements and
test_slru.c get rid of its shmem_request_hook and shmem_startup_hook
and use dsm_registry_init_or_attach? It's not that this patch need to
remove them now, but just asking if there's something in there that
makes this new feature unusable.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 8, 2024 at 10:53 AM Bharath Rupireddy <
bharath.rupireddyforpostgres@gmail.com> wrote:
On Sat, Jan 6, 2024 at 10:05 PM Nathan Bossart <nathandbossart@gmail.com>
wrote:I kept this the same, as I didn't see any need to tie the key size to
NAMEDATALEN.Thanks. A fresh look at the v5 patches left me with the following thoughts:
1. I think we need to add some notes about this new way of getting
shared memory for external modules in the <title>Shared Memory and
LWLocks</title> section in xfunc.sgml? This will at least tell there's
another way for external modules to get shared memory, not just with
the shmem_request_hook and shmem_startup_hook. What do you think?2. FWIW, I'd like to call this whole feature "Support for named DSM
segments in Postgres". Do you see anything wrong with this?3. IIUC, this feature eventually makes both shmem_request_hook and
shmem_startup_hook pointless, no? Or put another way, what's the
significance of shmem request and startup hooks in lieu of this new
feature? I think it's quite possible to get rid of the shmem request
and startup hooks (of course, not now but at some point in future to
not break the external modules), because all the external modules can
allocate and initialize the same shared memory via
dsm_registry_init_or_attach and its init_callback. All the external
modules will then need to call dsm_registry_init_or_attach in their
_PG_init callbacks and/or in their bg worker's main functions in case
the modules intend to start up bg workers. Am I right?4. With the understanding in comment #3, can pg_stat_statements and
test_slru.c get rid of its shmem_request_hook and shmem_startup_hook
and use dsm_registry_init_or_attach? It's not that this patch need to
remove them now, but just asking if there's something in there that
makes this new feature unusable.
+1, since doing for pg_prewarm, better to do for these modules as well.
A minor comment for v5:
+void *
+dsm_registry_init_or_attach(const char *key, size_t size,
I think the name could be simple as dsm_registry_init() like we use
elsewhere e.g. ShmemInitHash() which doesn't say attach explicitly.
Similarly, I think dshash_find_or_insert() can be as simple as
dshash_search() and
accept HASHACTION like hash_search().
Regards,
Amul
On Mon, Jan 08, 2024 at 10:53:17AM +0530, Bharath Rupireddy wrote:
1. I think we need to add some notes about this new way of getting
shared memory for external modules in the <title>Shared Memory and
LWLocks</title> section in xfunc.sgml? This will at least tell there's
another way for external modules to get shared memory, not just with
the shmem_request_hook and shmem_startup_hook. What do you think?
Good call. I definitely think this stuff ought to be documented. After a
quick read, I also wonder if it'd be worth spending some time refining that
section.
2. FWIW, I'd like to call this whole feature "Support for named DSM
segments in Postgres". Do you see anything wrong with this?
Why do you feel it should be renamed? I don't see anything wrong with it,
but I also don't see any particular advantage with that name compared to
"dynamic shared memory registry."
3. IIUC, this feature eventually makes both shmem_request_hook and
shmem_startup_hook pointless, no? Or put another way, what's the
significance of shmem request and startup hooks in lieu of this new
feature? I think it's quite possible to get rid of the shmem request
and startup hooks (of course, not now but at some point in future to
not break the external modules), because all the external modules can
allocate and initialize the same shared memory via
dsm_registry_init_or_attach and its init_callback. All the external
modules will then need to call dsm_registry_init_or_attach in their
_PG_init callbacks and/or in their bg worker's main functions in case
the modules intend to start up bg workers. Am I right?
Well, modules might need to do a number of other things (e.g., adding
hooks) that can presently only be done when preloaded, in which case I
doubt there's much benefit from switching to the DSM registry. I don't
really intend for it to replace the existing request/startup hooks, but
you're probably right that most, if not all, could use the registry
instead. IMHO this is well beyond the scope of this thread, though.
4. With the understanding in comment #3, can pg_stat_statements and
test_slru.c get rid of its shmem_request_hook and shmem_startup_hook
and use dsm_registry_init_or_attach? It's not that this patch need to
remove them now, but just asking if there's something in there that
makes this new feature unusable.
It might be possible, but IIUC you'd still need a way to know whether the
library was preloaded, i.e., all the other necessary hooks were in place.
It's convenient to just be able to check whether the shared memory was set
up for this purpose.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 08, 2024 at 11:13:42AM +0530, Amul Sul wrote:
+void * +dsm_registry_init_or_attach(const char *key, size_t size,I think the name could be simple as dsm_registry_init() like we use
elsewhere e.g. ShmemInitHash() which doesn't say attach explicitly.
That seems reasonable to me.
Similarly, I think dshash_find_or_insert() can be as simple as
dshash_search() and
accept HASHACTION like hash_search().
I'm not totally sure what you mean here. If you mean changing the dshash
API, I'd argue that's a topic for another thread.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 8, 2024 at 10:48 PM Nathan Bossart <nathandbossart@gmail.com>
wrote:
On Mon, Jan 08, 2024 at 11:13:42AM +0530, Amul Sul wrote:
+void * +dsm_registry_init_or_attach(const char *key, size_t size,I think the name could be simple as dsm_registry_init() like we use
elsewhere e.g. ShmemInitHash() which doesn't say attach explicitly.That seems reasonable to me.
Similarly, I think dshash_find_or_insert() can be as simple as
dshash_search() and
accept HASHACTION like hash_search().I'm not totally sure what you mean here. If you mean changing the dshash
API, I'd argue that's a topic for another thread.
Yes, you are correct. I didn't realize that existing code -- now sure, why
wouldn't we implemented as the dynahash. Sorry for the noise.
Regards,
Amul
On 9/1/2024 00:16, Nathan Bossart wrote:
On Mon, Jan 08, 2024 at 10:53:17AM +0530, Bharath Rupireddy wrote:
1. I think we need to add some notes about this new way of getting
shared memory for external modules in the <title>Shared Memory and
LWLocks</title> section in xfunc.sgml? This will at least tell there's
another way for external modules to get shared memory, not just with
the shmem_request_hook and shmem_startup_hook. What do you think?
+1. Maybe even more - in the section related to extensions, this
approach to using shared data can be mentioned, too.
2. FWIW, I'd like to call this whole feature "Support for named DSM
segments in Postgres". Do you see anything wrong with this?Why do you feel it should be renamed? I don't see anything wrong with it,
but I also don't see any particular advantage with that name compared to
"dynamic shared memory registry."
It is not a big issue, I suppose. But for me personally (as not a native
English speaker), the label "Named DSM segments" seems more
straightforward to understand.
3. IIUC, this feature eventually makes both shmem_request_hook and
shmem_startup_hook pointless, no? Or put another way, what's the
significance of shmem request and startup hooks in lieu of this new
feature? I think it's quite possible to get rid of the shmem request
and startup hooks (of course, not now but at some point in future to
not break the external modules), because all the external modules can
allocate and initialize the same shared memory via
dsm_registry_init_or_attach and its init_callback. All the external
modules will then need to call dsm_registry_init_or_attach in their
_PG_init callbacks and/or in their bg worker's main functions in case
the modules intend to start up bg workers. Am I right?Well, modules might need to do a number of other things (e.g., adding
hooks) that can presently only be done when preloaded, in which case I
doubt there's much benefit from switching to the DSM registry. I don't
really intend for it to replace the existing request/startup hooks, but
you're probably right that most, if not all, could use the registry
instead. IMHO this is well beyond the scope of this thread, though.
+1, it may be a many reasons to use these hooks.
3. Use NAMEDATALEN instead of 64?
+ char key[64];I kept this the same, as I didn't see any need to tie the key size to
NAMEDATALEN.
IMO, we should avoid magic numbers whenever possible. Current logic
according to which first users of this feature will be extensions
naturally bonds this size to the size of the 'name' type.
And one more point. I think the commit already deserves a more detailed
commit message.
--
regards,
Andrei Lepikhov
Postgres Professional
On Thu, Jan 11, 2024 at 09:50:19AM +0700, Andrei Lepikhov wrote:
On 9/1/2024 00:16, Nathan Bossart wrote:
On Mon, Jan 08, 2024 at 10:53:17AM +0530, Bharath Rupireddy wrote:
2. FWIW, I'd like to call this whole feature "Support for named DSM
segments in Postgres". Do you see anything wrong with this?Why do you feel it should be renamed? I don't see anything wrong with it,
but I also don't see any particular advantage with that name compared to
"dynamic shared memory registry."It is not a big issue, I suppose. But for me personally (as not a native
English speaker), the label "Named DSM segments" seems more straightforward
to understand.
That is good to know, thanks. I see that it would also align better with
RequestNamedLWLockTranche/GetNamedLWLockTranche.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 08, 2024 at 11:16:27AM -0600, Nathan Bossart wrote:
On Mon, Jan 08, 2024 at 10:53:17AM +0530, Bharath Rupireddy wrote:
1. I think we need to add some notes about this new way of getting
shared memory for external modules in the <title>Shared Memory and
LWLocks</title> section in xfunc.sgml? This will at least tell there's
another way for external modules to get shared memory, not just with
the shmem_request_hook and shmem_startup_hook. What do you think?Good call. I definitely think this stuff ought to be documented. After a
quick read, I also wonder if it'd be worth spending some time refining that
section.
+1. It would be a second thing to point at autoprewarm.c in the docs
as an extra pointer that can be fed to users reading the docs.
3. IIUC, this feature eventually makes both shmem_request_hook and
shmem_startup_hook pointless, no? Or put another way, what's the
significance of shmem request and startup hooks in lieu of this new
feature? I think it's quite possible to get rid of the shmem request
and startup hooks (of course, not now but at some point in future to
not break the external modules), because all the external modules can
allocate and initialize the same shared memory via
dsm_registry_init_or_attach and its init_callback. All the external
modules will then need to call dsm_registry_init_or_attach in their
_PG_init callbacks and/or in their bg worker's main functions in case
the modules intend to start up bg workers. Am I right?Well, modules might need to do a number of other things (e.g., adding
hooks) that can presently only be done when preloaded, in which case I
doubt there's much benefit from switching to the DSM registry. I don't
really intend for it to replace the existing request/startup hooks, but
you're probably right that most, if not all, could use the registry
instead. IMHO this is well beyond the scope of this thread, though.
Even if that's not in the scope of this thread, just removing these
hooks would break a lot of out-of-core things, and they still have a
lot of value when extensions expect to always be loaded with shared.
They don't cost in maintenance at this stage.
--
Michael
On Thu, Jan 11, 2024 at 10:42 AM Michael Paquier <michael@paquier.xyz> wrote:
3. IIUC, this feature eventually makes both shmem_request_hook and
shmem_startup_hook pointless, no? Or put another way, what's the
significance of shmem request and startup hooks in lieu of this new
feature? I think it's quite possible to get rid of the shmem request
and startup hooks (of course, not now but at some point in future to
not break the external modules), because all the external modules can
allocate and initialize the same shared memory via
dsm_registry_init_or_attach and its init_callback. All the external
modules will then need to call dsm_registry_init_or_attach in their
_PG_init callbacks and/or in their bg worker's main functions in case
the modules intend to start up bg workers. Am I right?Well, modules might need to do a number of other things (e.g., adding
hooks) that can presently only be done when preloaded, in which case I
doubt there's much benefit from switching to the DSM registry. I don't
really intend for it to replace the existing request/startup hooks, but
you're probably right that most, if not all, could use the registry
instead. IMHO this is well beyond the scope of this thread, though.Even if that's not in the scope of this thread, just removing these
hooks would break a lot of out-of-core things, and they still have a
lot of value when extensions expect to always be loaded with shared.
They don't cost in maintenance at this stage.
Adding some notes in the docs on when exactly one needs to use shmem
hooks and the named DSM segments can help greatly.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Here's a new version of the patch set in which I've attempted to address
the feedback in this thread. Note that 0001 is being tracked in a separate
thread [0]/messages/by-id/20240112041430.GA3557928@nathanxps13, but it is basically a prerequisite for adding the documentation
for this feature, so that's why I've also added it here.
[0]: /messages/by-id/20240112041430.GA3557928@nathanxps13
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v6-0003-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From 26436c7da5e546cf112e9f8ecc90d59cc2e1bedf Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v6 3/3] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 46 +++++++++++---------------------
1 file changed, 15 insertions(+), 31 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 9085a409db..9ea6c2252a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -32,12 +32,12 @@
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
-#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +95,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +138,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +750,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+apw_init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +770,10 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = GetNamedDSMSegment("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ apw_init_state,
+ &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
v6-0001-reorganize-shared-memory-and-lwlocks-documentatio.patchtext/x-diff; charset=us-asciiDownload
From 7cf22727a96757bf212ec106bd471bf55a6981b9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 11 Jan 2024 21:55:25 -0600
Subject: [PATCH v6 1/3] reorganize shared memory and lwlocks documentation
---
doc/src/sgml/xfunc.sgml | 182 +++++++++++++++++++++++++---------------
1 file changed, 114 insertions(+), 68 deletions(-)
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 89116ae74c..0ba52b41d4 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3397,90 +3397,136 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
</sect2>
<sect2 id="xfunc-shared-addin">
- <title>Shared Memory and LWLocks</title>
+ <title>Shared Memory</title>
- <para>
- Add-ins can reserve LWLocks and an allocation of shared memory on server
- startup. The add-in's shared library must be preloaded by specifying
- it in
- <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
- The shared library should register a <literal>shmem_request_hook</literal>
- in its <function>_PG_init</function> function. This
- <literal>shmem_request_hook</literal> can reserve LWLocks or shared memory.
- Shared memory is reserved by calling:
+ <sect3 id="xfunc-shared-addin-at-startup">
+ <title>Requesting Shared Memory at Startup</title>
+
+ <para>
+ Add-ins can reserve shared memory on server startup. To do so, the
+ add-in's shared library must be preloaded by specifying it in
+ <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
+ The shared library should also register a
+ <literal>shmem_request_hook</literal> in its
+ <function>_PG_init</function> function. This
+ <literal>shmem_request_hook</literal> can reserve shared memory by
+ calling:
<programlisting>
void RequestAddinShmemSpace(int size)
</programlisting>
- from your <literal>shmem_request_hook</literal>.
- </para>
- <para>
- LWLocks are reserved by calling:
+ Each backend sould obtain a pointer to the reserved shared memory by
+ calling:
+<programlisting>
+void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+</programlisting>
+ If this function sets <literal>foundPtr</literal> to
+ <literal>false</literal>, the caller should proceed to initialize the
+ contents of the reserved shared memory. If <literal>foundPtr</literal>
+ is set to <literal>true</literal>, the shared memory was already
+ initialized by another backend, and the caller need not initialize
+ further.
+ </para>
+
+ <para>
+ To avoid race conditions, each backend should use the LWLock
+ <function>AddinShmemInitLock</function> when initializing its allocation
+ of shared memory, as shown here:
+<programlisting>
+static mystruct *ptr = NULL;
+bool found;
+
+LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+ptr = ShmemInitStruct("my struct name", size, &found);
+if (!found)
+{
+ ... initialize contents of shared memory ...
+ ptr->locks = GetNamedLWLockTranche("my tranche name");
+}
+LWLockRelease(AddinShmemInitLock);
+</programlisting>
+ <literal>shmem_startup_hook</literal> provides a convenient place for the
+ initialization code, but it is not strictly required that all such code
+ be placed in this hook. Each backend will execute the registered
+ <literal>shmem_startup_hook</literal> shortly after it attaches to shared
+ memory. Note that add-ins should still acquire
+ <function>AddinShmemInitLock</function> within this hook, as shown in the
+ example above.
+ </para>
+
+ <para>
+ An example of a <literal>shmem_request_hook</literal> and
+ <literal>shmem_startup_hook</literal> can be found in
+ <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in
+ the <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
+ </sect2>
+
+ <sect2 id="xfunc-addin-lwlocks">
+ <title>LWLocks</title>
+
+ <sect3 id="xfunc-addin-lwlocks-at-startup">
+ <title>Requesting LWLocks at Startup</title>
+
+ <para>
+ Add-ins can reserve LWLocks on server startup. Like with shared memory,
+ the add-in's shared library must be preloaded by specifying it in
+ <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
+ and the shared library should register a
+ <literal>shmem_request_hook</literal> in its
+ <function>_PG_init</function> function. This
+ <literal>shmem_request_hook</literal> can reserve LWLocks by calling:
<programlisting>
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
</programlisting>
- from your <literal>shmem_request_hook</literal>. This will ensure that an array of
- <literal>num_lwlocks</literal> LWLocks is available under the name
- <literal>tranche_name</literal>. Use <function>GetNamedLWLockTranche</function>
- to get a pointer to this array.
- </para>
- <para>
- An example of a <literal>shmem_request_hook</literal> can be found in
- <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in the
- <productname>PostgreSQL</productname> source tree.
- </para>
- <para>
- There is another, more flexible method of obtaining LWLocks. First,
- allocate a <literal>tranche_id</literal> from a shared counter by
- calling:
+ This ensures that an array of <literal>num_lwlocks</literal> LWLocks is
+ available under the name <literal>tranche_name</literal>. A pointer to
+ this array can be obtained by calling:
<programlisting>
-int LWLockNewTrancheId(void)
+LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
</programlisting>
- Next, each individual process using the <literal>tranche_id</literal>
- should associate it with a <literal>tranche_name</literal> by calling:
+ </para>
+ </sect3>
+
+ <sect3 id="xfunc-addin-lwlocks-after-startup">
+ <title>Requesting LWLocks After Startup</title>
+
+ <para>
+ There is another, more flexible method of obtaining LWLocks that can be
+ done after server startup and outside a
+ <literal>shmem_request_hook</literal>. To do so, first allocate a
+ <literal>tranche_id</literal> by calling:
<programlisting>
-void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
+int LWLockNewTrancheId(void)
</programlisting>
- It is also required to call <function>LWLockInitialize</function> once
- per LWLock, passing the <literal>tranche_id</literal> as argument:
+ Next, initialize each LWLock, passing the new
+ <literal>tranche_id</literal> as an argument:
<programlisting>
void LWLockInitialize(LWLock *lock, int tranche_id)
</programlisting>
- A complete usage example of <function>LWLockNewTrancheId</function>,
- <function>LWLockInitialize</function> and
- <function>LWLockRegisterTranche</function> can be found in
- <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
- <productname>PostgreSQL</productname> source tree.
- </para>
- <para>
- To avoid possible race-conditions, each backend should use the LWLock
- <function>AddinShmemInitLock</function> when connecting to and initializing
- its allocation of shared memory, as shown here:
-<programlisting>
-static mystruct *ptr = NULL;
+ Similar to shared memory, each backend should ensure that only one
+ process allocates a new <literal>tranche_id</literal> and initializes
+ each new LWLock. One way to do this is to only call these functions in
+ your shared memory initialization code with the
+ <function>AddinShmemInitLock</function> held exclusively.
+ </para>
-if (!ptr)
-{
- bool found;
-
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- ptr = ShmemInitStruct("my struct name", size, &found);
- if (!found)
- {
- initialize contents of shmem area;
- acquire any requested LWLocks using:
- ptr->locks = GetNamedLWLockTranche("my tranche name");
- }
- LWLockRelease(AddinShmemInitLock);
-}
+ <para>
+ Finally, each backend using the <literal>tranche_id</literal> should
+ associate it with a <literal>tranche_name</literal> by calling:
+<programlisting>
+void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
</programlisting>
- </para>
- <para>
- It is convenient to use <literal>shmem_startup_hook</literal> which allows
- placing all the code responsible for initializing shared memory in one
- place. When using <literal>shmem_startup_hook</literal> the extension
- still needs to acquire <function>AddinShmemInitLock</function> in order to
- work properly on all the supported platforms.
- </para>
+ </para>
+
+ <para>
+ A complete usage example of <function>LWLockNewTrancheId</function>,
+ <function>LWLockInitialize</function>, and
+ <function>LWLockRegisterTranche</function> can be found in
+ <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
+ <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
</sect2>
<sect2 id="xfunc-addin-wait-events">
--
2.25.1
v6-0002-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From b635798c1d015d9304c3273fd7c522d140d4fd31 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v6 2/3] add dsm registry
---
doc/src/sgml/xfunc.sgml | 49 ++++-
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 174 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 23 +++
src/include/storage/lwlock.h | 2 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 +++
.../expected/test_dsm_registry.out | 14 ++
.../modules/test_dsm_registry/meson.build | 33 ++++
.../sql/test_dsm_registry.sql | 4 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 76 ++++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
21 files changed, 431 insertions(+), 3 deletions(-)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 0ba52b41d4..9d305000f9 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3460,6 +3460,45 @@ LWLockRelease(AddinShmemInitLock);
the <productname>PostgreSQL</productname> source tree.
</para>
</sect3>
+
+ <sect3 id="xfunc-shared-addin-after-startup">
+ <title>Requesting Shared Memory After Startup</title>
+
+ <para>
+ There is another, more flexible method of reserving shared memory that
+ can be done after server startup and outside a
+ <literal>shmem_request_hook</literal>. To do so, each backend that will
+ 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),
+ 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.
+ </para>
+
+ <para>
+ Unlike shared memory reserved at server startup, there is no need to
+ acquire <function>AddinShmemInitLock</function> or otherwise take action
+ to avoid race conditions when reserving shared memory with
+ <function>GetNamedDSMSegment</function>. This function ensures that only
+ one backend allocates and initializes the segment and that all other
+ backends receive a pointer to the fully allocated and initialized
+ segment.
+ </para>
+
+ <para>
+ A complete usage example of <function>GetNamedDSMSegment</function> can
+ be found in
+ <filename>src/test/modules/test_dsm_registry/test_dsm_registry.c</filename>
+ in the <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
</sect2>
<sect2 id="xfunc-addin-lwlocks">
@@ -3469,8 +3508,9 @@ LWLockRelease(AddinShmemInitLock);
<title>Requesting LWLocks at Startup</title>
<para>
- Add-ins can reserve LWLocks on server startup. Like with shared memory,
- the add-in's shared library must be preloaded by specifying it in
+ Add-ins can reserve LWLocks on server startup. Like with shared memory
+ reserved at server startup, the add-in's shared library must be preloaded
+ by specifying it in
<xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
and the shared library should register a
<literal>shmem_request_hook</literal> in its
@@ -3508,7 +3548,10 @@ void LWLockInitialize(LWLock *lock, int tranche_id)
process allocates a new <literal>tranche_id</literal> and initializes
each new LWLock. One way to do this is to only call these functions in
your shared memory initialization code with the
- <function>AddinShmemInitLock</function> held exclusively.
+ <function>AddinShmemInitLock</function> held exclusively. If using
+ <function>GetNamedDSMSegment</function>, calling these functions in the
+ <function>init_callback</function> callback function is sufficient to
+ avoid race conditions.
</para>
<para>
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..7deaa0d6c5
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,174 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char name[64];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * 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.
+ */
+void *
+GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char name_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(name);
+ Assert(size);
+ Assert(found);
+
+ if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM segment name too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(name_padded, name);
+ entry = dshash_find_or_insert(dsm_registry_table, name_padded, found);
+ if (!(*found))
+ {
+ /* Initialize the segment. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing segment. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of an already-attached segment. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e5119ed55d..fbc62b1563 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 08bdc718b8..5a936171f7 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index b4b989ac56..2f2de5a562 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index f625473ad4..6bcb1cca0c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -329,6 +329,7 @@ WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consu
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
WALSummarizer "Waiting to read or update WAL summarization state."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
#
# END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
@@ -367,6 +368,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..8e8a23ba60
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern void *GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 167ae34208..50a65e046d 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 00ff1d77d1..2c3b8d73bc 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..8ffbd343a0
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+\c
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..a4045fea37
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..b3351be0a1
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+\c
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..b46cd6c620
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,76 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+tdr_init_shmem(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+tdr_attach_shmem(void)
+{
+ bool found;
+
+ tdr_state = GetNamedDSMSegment("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ tdr_init_shmem,
+ &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ tdr_attach_shmem();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ tdr_attach_shmem();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f582eb59e7..b7e73736e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2799,6 +2801,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
At 2024-01-12 11:21:52 -0600, nathandbossart@gmail.com wrote:
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 11 Jan 2024 21:55:25 -0600
Subject: [PATCH v6 1/3] reorganize shared memory and lwlocks documentation---
doc/src/sgml/xfunc.sgml | 182 +++++++++++++++++++++++++---------------
1 file changed, 114 insertions(+), 68 deletions(-)diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 89116ae74c..0ba52b41d4 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3397,90 +3397,136 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray </sect2>[…] - from your <literal>shmem_request_hook</literal>. - </para> - <para> - LWLocks are reserved by calling: + Each backend sould obtain a pointer to the reserved shared memory by
sould → should
+ Add-ins can reserve LWLocks on server startup. Like with shared memory,
(Would "As with shared memory" read better? Maybe, but then again maybe
it should be left alone because you also write "Unlike with" elsewhere.)
-- Abhijit
On Fri, Jan 12, 2024 at 11:13:46PM +0530, Abhijit Menon-Sen wrote:
At 2024-01-12 11:21:52 -0600, nathandbossart@gmail.com wrote:
+ Each backend sould obtain a pointer to the reserved shared memory by
sould → should
D'oh. Thanks.
+ Add-ins can reserve LWLocks on server startup. Like with shared memory,
(Would "As with shared memory" read better? Maybe, but then again maybe
it should be left alone because you also write "Unlike with" elsewhere.)
I think "As with shared memory..." sounds better here.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jan 12, 2024 at 01:45:55PM -0600, Nathan Bossart wrote:
On Fri, Jan 12, 2024 at 11:13:46PM +0530, Abhijit Menon-Sen wrote:
At 2024-01-12 11:21:52 -0600, nathandbossart@gmail.com wrote:
+ Each backend sould obtain a pointer to the reserved shared memory by
sould → should
D'oh. Thanks.
+ Add-ins can reserve LWLocks on server startup. Like with shared memory,
(Would "As with shared memory" read better? Maybe, but then again maybe
it should be left alone because you also write "Unlike with" elsewhere.)I think "As with shared memory..." sounds better here.
Here is a new version of the patch set with these changes.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v7-0001-reorganize-shared-memory-and-lwlocks-documentatio.patchtext/x-diff; charset=us-asciiDownload
From b56931edc4488a7376b27ba0e5519cc3a61b4899 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 11 Jan 2024 21:55:25 -0600
Subject: [PATCH v7 1/3] reorganize shared memory and lwlocks documentation
---
doc/src/sgml/xfunc.sgml | 182 +++++++++++++++++++++++++---------------
1 file changed, 114 insertions(+), 68 deletions(-)
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 89116ae74c..82e1dadcca 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3397,90 +3397,136 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
</sect2>
<sect2 id="xfunc-shared-addin">
- <title>Shared Memory and LWLocks</title>
+ <title>Shared Memory</title>
- <para>
- Add-ins can reserve LWLocks and an allocation of shared memory on server
- startup. The add-in's shared library must be preloaded by specifying
- it in
- <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
- The shared library should register a <literal>shmem_request_hook</literal>
- in its <function>_PG_init</function> function. This
- <literal>shmem_request_hook</literal> can reserve LWLocks or shared memory.
- Shared memory is reserved by calling:
+ <sect3 id="xfunc-shared-addin-at-startup">
+ <title>Requesting Shared Memory at Startup</title>
+
+ <para>
+ Add-ins can reserve shared memory on server startup. To do so, the
+ add-in's shared library must be preloaded by specifying it in
+ <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
+ The shared library should also register a
+ <literal>shmem_request_hook</literal> in its
+ <function>_PG_init</function> function. This
+ <literal>shmem_request_hook</literal> can reserve shared memory by
+ calling:
<programlisting>
void RequestAddinShmemSpace(int size)
</programlisting>
- from your <literal>shmem_request_hook</literal>.
- </para>
- <para>
- LWLocks are reserved by calling:
+ Each backend should obtain a pointer to the reserved shared memory by
+ calling:
+<programlisting>
+void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+</programlisting>
+ If this function sets <literal>foundPtr</literal> to
+ <literal>false</literal>, the caller should proceed to initialize the
+ contents of the reserved shared memory. If <literal>foundPtr</literal>
+ is set to <literal>true</literal>, the shared memory was already
+ initialized by another backend, and the caller need not initialize
+ further.
+ </para>
+
+ <para>
+ To avoid race conditions, each backend should use the LWLock
+ <function>AddinShmemInitLock</function> when initializing its allocation
+ of shared memory, as shown here:
+<programlisting>
+static mystruct *ptr = NULL;
+bool found;
+
+LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+ptr = ShmemInitStruct("my struct name", size, &found);
+if (!found)
+{
+ ... initialize contents of shared memory ...
+ ptr->locks = GetNamedLWLockTranche("my tranche name");
+}
+LWLockRelease(AddinShmemInitLock);
+</programlisting>
+ <literal>shmem_startup_hook</literal> provides a convenient place for the
+ initialization code, but it is not strictly required that all such code
+ be placed in this hook. Each backend will execute the registered
+ <literal>shmem_startup_hook</literal> shortly after it attaches to shared
+ memory. Note that add-ins should still acquire
+ <function>AddinShmemInitLock</function> within this hook, as shown in the
+ example above.
+ </para>
+
+ <para>
+ An example of a <literal>shmem_request_hook</literal> and
+ <literal>shmem_startup_hook</literal> can be found in
+ <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in
+ the <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
+ </sect2>
+
+ <sect2 id="xfunc-addin-lwlocks">
+ <title>LWLocks</title>
+
+ <sect3 id="xfunc-addin-lwlocks-at-startup">
+ <title>Requesting LWLocks at Startup</title>
+
+ <para>
+ Add-ins can reserve LWLocks on server startup. As with shared memory,
+ the add-in's shared library must be preloaded by specifying it in
+ <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
+ and the shared library should register a
+ <literal>shmem_request_hook</literal> in its
+ <function>_PG_init</function> function. This
+ <literal>shmem_request_hook</literal> can reserve LWLocks by calling:
<programlisting>
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
</programlisting>
- from your <literal>shmem_request_hook</literal>. This will ensure that an array of
- <literal>num_lwlocks</literal> LWLocks is available under the name
- <literal>tranche_name</literal>. Use <function>GetNamedLWLockTranche</function>
- to get a pointer to this array.
- </para>
- <para>
- An example of a <literal>shmem_request_hook</literal> can be found in
- <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in the
- <productname>PostgreSQL</productname> source tree.
- </para>
- <para>
- There is another, more flexible method of obtaining LWLocks. First,
- allocate a <literal>tranche_id</literal> from a shared counter by
- calling:
+ This ensures that an array of <literal>num_lwlocks</literal> LWLocks is
+ available under the name <literal>tranche_name</literal>. A pointer to
+ this array can be obtained by calling:
<programlisting>
-int LWLockNewTrancheId(void)
+LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
</programlisting>
- Next, each individual process using the <literal>tranche_id</literal>
- should associate it with a <literal>tranche_name</literal> by calling:
+ </para>
+ </sect3>
+
+ <sect3 id="xfunc-addin-lwlocks-after-startup">
+ <title>Requesting LWLocks After Startup</title>
+
+ <para>
+ There is another, more flexible method of obtaining LWLocks that can be
+ done after server startup and outside a
+ <literal>shmem_request_hook</literal>. To do so, first allocate a
+ <literal>tranche_id</literal> by calling:
<programlisting>
-void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
+int LWLockNewTrancheId(void)
</programlisting>
- It is also required to call <function>LWLockInitialize</function> once
- per LWLock, passing the <literal>tranche_id</literal> as argument:
+ Next, initialize each LWLock, passing the new
+ <literal>tranche_id</literal> as an argument:
<programlisting>
void LWLockInitialize(LWLock *lock, int tranche_id)
</programlisting>
- A complete usage example of <function>LWLockNewTrancheId</function>,
- <function>LWLockInitialize</function> and
- <function>LWLockRegisterTranche</function> can be found in
- <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
- <productname>PostgreSQL</productname> source tree.
- </para>
- <para>
- To avoid possible race-conditions, each backend should use the LWLock
- <function>AddinShmemInitLock</function> when connecting to and initializing
- its allocation of shared memory, as shown here:
-<programlisting>
-static mystruct *ptr = NULL;
+ Similar to shared memory, each backend should ensure that only one
+ process allocates a new <literal>tranche_id</literal> and initializes
+ each new LWLock. One way to do this is to only call these functions in
+ your shared memory initialization code with the
+ <function>AddinShmemInitLock</function> held exclusively.
+ </para>
-if (!ptr)
-{
- bool found;
-
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- ptr = ShmemInitStruct("my struct name", size, &found);
- if (!found)
- {
- initialize contents of shmem area;
- acquire any requested LWLocks using:
- ptr->locks = GetNamedLWLockTranche("my tranche name");
- }
- LWLockRelease(AddinShmemInitLock);
-}
+ <para>
+ Finally, each backend using the <literal>tranche_id</literal> should
+ associate it with a <literal>tranche_name</literal> by calling:
+<programlisting>
+void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
</programlisting>
- </para>
- <para>
- It is convenient to use <literal>shmem_startup_hook</literal> which allows
- placing all the code responsible for initializing shared memory in one
- place. When using <literal>shmem_startup_hook</literal> the extension
- still needs to acquire <function>AddinShmemInitLock</function> in order to
- work properly on all the supported platforms.
- </para>
+ </para>
+
+ <para>
+ A complete usage example of <function>LWLockNewTrancheId</function>,
+ <function>LWLockInitialize</function>, and
+ <function>LWLockRegisterTranche</function> can be found in
+ <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
+ <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
</sect2>
<sect2 id="xfunc-addin-wait-events">
--
2.25.1
v7-0002-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From bb4941207d5b64e9e5641a6f0ad234e0d199e498 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v7 2/3] add dsm registry
---
doc/src/sgml/xfunc.sgml | 49 ++++-
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 174 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 23 +++
src/include/storage/lwlock.h | 2 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 +++
.../expected/test_dsm_registry.out | 14 ++
.../modules/test_dsm_registry/meson.build | 33 ++++
.../sql/test_dsm_registry.sql | 4 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 76 ++++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
21 files changed, 431 insertions(+), 3 deletions(-)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 82e1dadcca..fbeac2fd1d 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3460,6 +3460,45 @@ LWLockRelease(AddinShmemInitLock);
the <productname>PostgreSQL</productname> source tree.
</para>
</sect3>
+
+ <sect3 id="xfunc-shared-addin-after-startup">
+ <title>Requesting Shared Memory After Startup</title>
+
+ <para>
+ There is another, more flexible method of reserving shared memory that
+ can be done after server startup and outside a
+ <literal>shmem_request_hook</literal>. To do so, each backend that will
+ 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),
+ 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.
+ </para>
+
+ <para>
+ Unlike shared memory reserved at server startup, there is no need to
+ acquire <function>AddinShmemInitLock</function> or otherwise take action
+ to avoid race conditions when reserving shared memory with
+ <function>GetNamedDSMSegment</function>. This function ensures that only
+ one backend allocates and initializes the segment and that all other
+ backends receive a pointer to the fully allocated and initialized
+ segment.
+ </para>
+
+ <para>
+ A complete usage example of <function>GetNamedDSMSegment</function> can
+ be found in
+ <filename>src/test/modules/test_dsm_registry/test_dsm_registry.c</filename>
+ in the <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
</sect2>
<sect2 id="xfunc-addin-lwlocks">
@@ -3469,8 +3508,9 @@ LWLockRelease(AddinShmemInitLock);
<title>Requesting LWLocks at Startup</title>
<para>
- Add-ins can reserve LWLocks on server startup. As with shared memory,
- the add-in's shared library must be preloaded by specifying it in
+ Add-ins can reserve LWLocks on server startup. As with shared memory
+ reserved at server startup, the add-in's shared library must be preloaded
+ by specifying it in
<xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
and the shared library should register a
<literal>shmem_request_hook</literal> in its
@@ -3508,7 +3548,10 @@ void LWLockInitialize(LWLock *lock, int tranche_id)
process allocates a new <literal>tranche_id</literal> and initializes
each new LWLock. One way to do this is to only call these functions in
your shared memory initialization code with the
- <function>AddinShmemInitLock</function> held exclusively.
+ <function>AddinShmemInitLock</function> held exclusively. If using
+ <function>GetNamedDSMSegment</function>, calling these functions in the
+ <function>init_callback</function> callback function is sufficient to
+ avoid race conditions.
</para>
<para>
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..7deaa0d6c5
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,174 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char name[64];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * 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.
+ */
+void *
+GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char name_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(name);
+ Assert(size);
+ Assert(found);
+
+ if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM segment name too long");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(name_padded, name);
+ entry = dshash_find_or_insert(dsm_registry_table, name_padded, found);
+ if (!(*found))
+ {
+ /* Initialize the segment. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing segment. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of an already-attached segment. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e5119ed55d..fbc62b1563 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 08bdc718b8..5a936171f7 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index b4b989ac56..2f2de5a562 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index f625473ad4..6bcb1cca0c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -329,6 +329,7 @@ WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consu
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
WALSummarizer "Waiting to read or update WAL summarization state."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
#
# END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
@@ -367,6 +368,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..8e8a23ba60
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern void *GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 167ae34208..50a65e046d 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 00ff1d77d1..2c3b8d73bc 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..8ffbd343a0
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+\c
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..a4045fea37
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..b3351be0a1
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+\c
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..b46cd6c620
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,76 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+tdr_init_shmem(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+tdr_attach_shmem(void)
+{
+ bool found;
+
+ tdr_state = GetNamedDSMSegment("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ tdr_init_shmem,
+ &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ tdr_attach_shmem();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ tdr_attach_shmem();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f582eb59e7..b7e73736e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2799,6 +2801,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
v7-0003-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From c914c3850c5ffed043fa360150cdb312947bfa7a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v7 3/3] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 46 +++++++++++---------------------
1 file changed, 15 insertions(+), 31 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 9085a409db..9ea6c2252a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -32,12 +32,12 @@
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
-#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +95,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +138,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +750,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+apw_init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +770,10 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = GetNamedDSMSegment("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ apw_init_state,
+ &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
On Sun, Jan 14, 2024 at 3:11 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Here is a new version of the patch set with these changes.
Thanks. Here are some comments on v7-0002.
1.
+GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+
+ Assert(name);
+ Assert(size);
+ Assert(found);
I've done some input validation to GetNamedDSMSegment():
With an empty key name (""), it works, but do we need that in
practice? Can't we error out saying the name can't be empty?
With a size 0, an assertion is fine, but in production (without the
assertion), I'm seeing the following errors.
2024-01-16 04:49:28.961 UTC client backend[864369]
pg_regress/test_dsm_registry ERROR: could not resize shared memory
segment "/PostgreSQL.3701090278" to 0 bytes: Invalid argument
2024-01-16 04:49:29.264 UTC postmaster[864357] LOG: server process
(PID 864370) was terminated by signal 11: Segmentation fault
I think it's better for GetNamedDSMSegment() to error out on empty
'name' and size 0. This makes the user-facing function
GetNamedDSMSegment more concrete.
2.
+void *
+GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+ Assert(found);
Why is input parameter 'found' necessary to be passed by the caller?
Neither the test module added, nor the pg_prewarm is using the found
variable. The function will anyway create the DSM segment if one with
the given name isn't found. IMO, found is an optional parameter for
the caller. So, the assert(found) isn't necessary.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Tue, Jan 16, 2024 at 10:28:29AM +0530, Bharath Rupireddy wrote:
I think it's better for GetNamedDSMSegment() to error out on empty
'name' and size 0. This makes the user-facing function
GetNamedDSMSegment more concrete.
Agreed, thanks for the suggestion.
+void * +GetNamedDSMSegment(const char *name, size_t size, + void (*init_callback) (void *ptr), bool *found)+ Assert(found);
Why is input parameter 'found' necessary to be passed by the caller?
Neither the test module added, nor the pg_prewarm is using the found
variable. The function will anyway create the DSM segment if one with
the given name isn't found. IMO, found is an optional parameter for
the caller. So, the assert(found) isn't necessary.
The autoprewarm change (0003) does use this variable. I considered making
it optional (i.e., you could pass in NULL if you didn't want it), but I
didn't feel like the extra code in GetNamedDSMSegment() to allow this was
worth it so that callers could avoid creating a single bool.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v8-0001-reorganize-shared-memory-and-lwlocks-documentatio.patchtext/x-diff; charset=us-asciiDownload
From 402eaf87776fb6a9d212da66947f47c63bd53f2a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 11 Jan 2024 21:55:25 -0600
Subject: [PATCH v8 1/3] reorganize shared memory and lwlocks documentation
---
doc/src/sgml/xfunc.sgml | 184 +++++++++++++++++++++++++---------------
1 file changed, 115 insertions(+), 69 deletions(-)
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 89116ae74c..ede2a5dea6 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3397,90 +3397,136 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
</sect2>
<sect2 id="xfunc-shared-addin">
- <title>Shared Memory and LWLocks</title>
+ <title>Shared Memory</title>
- <para>
- Add-ins can reserve LWLocks and an allocation of shared memory on server
- startup. The add-in's shared library must be preloaded by specifying
- it in
- <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
- The shared library should register a <literal>shmem_request_hook</literal>
- in its <function>_PG_init</function> function. This
- <literal>shmem_request_hook</literal> can reserve LWLocks or shared memory.
- Shared memory is reserved by calling:
+ <sect3 id="xfunc-shared-addin-at-startup">
+ <title>Requesting Shared Memory at Startup</title>
+
+ <para>
+ Add-ins can reserve shared memory on server startup. To do so, the
+ add-in's shared library must be preloaded by specifying it in
+ <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>.
+ The shared library should also register a
+ <literal>shmem_request_hook</literal> in its
+ <function>_PG_init</function> function. This
+ <literal>shmem_request_hook</literal> can reserve shared memory by
+ calling:
<programlisting>
-void RequestAddinShmemSpace(int size)
+void RequestAddinShmemSpace(Size size)
</programlisting>
- from your <literal>shmem_request_hook</literal>.
- </para>
- <para>
- LWLocks are reserved by calling:
+ Each backend should obtain a pointer to the reserved shared memory by
+ calling:
+<programlisting>
+void *ShmemInitStruct(const char *name, Size size, bool *foundPtr)
+</programlisting>
+ If this function sets <literal>foundPtr</literal> to
+ <literal>false</literal>, the caller should proceed to initialize the
+ contents of the reserved shared memory. If <literal>foundPtr</literal>
+ is set to <literal>true</literal>, the shared memory was already
+ initialized by another backend, and the caller need not initialize
+ further.
+ </para>
+
+ <para>
+ To avoid race conditions, each backend should use the LWLock
+ <function>AddinShmemInitLock</function> when initializing its allocation
+ of shared memory, as shown here:
+<programlisting>
+static mystruct *ptr = NULL;
+bool found;
+
+LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+ptr = ShmemInitStruct("my struct name", size, &found);
+if (!found)
+{
+ ... initialize contents of shared memory ...
+ ptr->locks = GetNamedLWLockTranche("my tranche name");
+}
+LWLockRelease(AddinShmemInitLock);
+</programlisting>
+ <literal>shmem_startup_hook</literal> provides a convenient place for the
+ initialization code, but it is not strictly required that all such code
+ be placed in this hook. Each backend will execute the registered
+ <literal>shmem_startup_hook</literal> shortly after it attaches to shared
+ memory. Note that add-ins should still acquire
+ <function>AddinShmemInitLock</function> within this hook, as shown in the
+ example above.
+ </para>
+
+ <para>
+ An example of a <literal>shmem_request_hook</literal> and
+ <literal>shmem_startup_hook</literal> can be found in
+ <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in
+ the <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
+ </sect2>
+
+ <sect2 id="xfunc-addin-lwlocks">
+ <title>LWLocks</title>
+
+ <sect3 id="xfunc-addin-lwlocks-at-startup">
+ <title>Requesting LWLocks at Startup</title>
+
+ <para>
+ Add-ins can reserve LWLocks on server startup. As with shared memory,
+ the add-in's shared library must be preloaded by specifying it in
+ <xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
+ and the shared library should register a
+ <literal>shmem_request_hook</literal> in its
+ <function>_PG_init</function> function. This
+ <literal>shmem_request_hook</literal> can reserve LWLocks by calling:
<programlisting>
void RequestNamedLWLockTranche(const char *tranche_name, int num_lwlocks)
</programlisting>
- from your <literal>shmem_request_hook</literal>. This will ensure that an array of
- <literal>num_lwlocks</literal> LWLocks is available under the name
- <literal>tranche_name</literal>. Use <function>GetNamedLWLockTranche</function>
- to get a pointer to this array.
- </para>
- <para>
- An example of a <literal>shmem_request_hook</literal> can be found in
- <filename>contrib/pg_stat_statements/pg_stat_statements.c</filename> in the
- <productname>PostgreSQL</productname> source tree.
- </para>
- <para>
- There is another, more flexible method of obtaining LWLocks. First,
- allocate a <literal>tranche_id</literal> from a shared counter by
- calling:
+ This ensures that an array of <literal>num_lwlocks</literal> LWLocks is
+ available under the name <literal>tranche_name</literal>. A pointer to
+ this array can be obtained by calling:
<programlisting>
-int LWLockNewTrancheId(void)
+LWLockPadded *GetNamedLWLockTranche(const char *tranche_name)
</programlisting>
- Next, each individual process using the <literal>tranche_id</literal>
- should associate it with a <literal>tranche_name</literal> by calling:
+ </para>
+ </sect3>
+
+ <sect3 id="xfunc-addin-lwlocks-after-startup">
+ <title>Requesting LWLocks After Startup</title>
+
+ <para>
+ There is another, more flexible method of obtaining LWLocks that can be
+ done after server startup and outside a
+ <literal>shmem_request_hook</literal>. To do so, first allocate a
+ <literal>tranche_id</literal> by calling:
<programlisting>
-void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
+int LWLockNewTrancheId(void)
</programlisting>
- It is also required to call <function>LWLockInitialize</function> once
- per LWLock, passing the <literal>tranche_id</literal> as argument:
+ Next, initialize each LWLock, passing the new
+ <literal>tranche_id</literal> as an argument:
<programlisting>
void LWLockInitialize(LWLock *lock, int tranche_id)
</programlisting>
- A complete usage example of <function>LWLockNewTrancheId</function>,
- <function>LWLockInitialize</function> and
- <function>LWLockRegisterTranche</function> can be found in
- <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
- <productname>PostgreSQL</productname> source tree.
- </para>
- <para>
- To avoid possible race-conditions, each backend should use the LWLock
- <function>AddinShmemInitLock</function> when connecting to and initializing
- its allocation of shared memory, as shown here:
-<programlisting>
-static mystruct *ptr = NULL;
+ Similar to shared memory, each backend should ensure that only one
+ process allocates a new <literal>tranche_id</literal> and initializes
+ each new LWLock. One way to do this is to only call these functions in
+ your shared memory initialization code with the
+ <function>AddinShmemInitLock</function> held exclusively.
+ </para>
-if (!ptr)
-{
- bool found;
-
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- ptr = ShmemInitStruct("my struct name", size, &found);
- if (!found)
- {
- initialize contents of shmem area;
- acquire any requested LWLocks using:
- ptr->locks = GetNamedLWLockTranche("my tranche name");
- }
- LWLockRelease(AddinShmemInitLock);
-}
+ <para>
+ Finally, each backend using the <literal>tranche_id</literal> should
+ associate it with a <literal>tranche_name</literal> by calling:
+<programlisting>
+void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
</programlisting>
- </para>
- <para>
- It is convenient to use <literal>shmem_startup_hook</literal> which allows
- placing all the code responsible for initializing shared memory in one
- place. When using <literal>shmem_startup_hook</literal> the extension
- still needs to acquire <function>AddinShmemInitLock</function> in order to
- work properly on all the supported platforms.
- </para>
+ </para>
+
+ <para>
+ A complete usage example of <function>LWLockNewTrancheId</function>,
+ <function>LWLockInitialize</function>, and
+ <function>LWLockRegisterTranche</function> can be found in
+ <filename>contrib/pg_prewarm/autoprewarm.c</filename> in the
+ <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
</sect2>
<sect2 id="xfunc-addin-wait-events">
--
2.25.1
v8-0002-add-dsm-registry.patchtext/x-diff; charset=us-asciiDownload
From 202d5199597a7b35a2c9948ade49be43efdac722 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Wed, 11 Oct 2023 22:07:26 -0500
Subject: [PATCH v8 2/3] add dsm registry
---
doc/src/sgml/xfunc.sgml | 49 ++++-
src/backend/storage/ipc/Makefile | 1 +
src/backend/storage/ipc/dsm_registry.c | 178 ++++++++++++++++++
src/backend/storage/ipc/ipci.c | 3 +
src/backend/storage/ipc/meson.build | 1 +
src/backend/storage/lmgr/lwlock.c | 4 +
src/backend/storage/lmgr/lwlocknames.txt | 1 +
.../utils/activity/wait_event_names.txt | 3 +
src/include/storage/dsm_registry.h | 23 +++
src/include/storage/lwlock.h | 2 +
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_dsm_registry/.gitignore | 4 +
src/test/modules/test_dsm_registry/Makefile | 23 +++
.../expected/test_dsm_registry.out | 14 ++
.../modules/test_dsm_registry/meson.build | 33 ++++
.../sql/test_dsm_registry.sql | 4 +
.../test_dsm_registry--1.0.sql | 10 +
.../test_dsm_registry/test_dsm_registry.c | 76 ++++++++
.../test_dsm_registry.control | 4 +
src/tools/pgindent/typedefs.list | 3 +
21 files changed, 435 insertions(+), 3 deletions(-)
create mode 100644 src/backend/storage/ipc/dsm_registry.c
create mode 100644 src/include/storage/dsm_registry.h
create mode 100644 src/test/modules/test_dsm_registry/.gitignore
create mode 100644 src/test/modules/test_dsm_registry/Makefile
create mode 100644 src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
create mode 100644 src/test/modules/test_dsm_registry/meson.build
create mode 100644 src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.c
create mode 100644 src/test/modules/test_dsm_registry/test_dsm_registry.control
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index ede2a5dea6..0ad9f38e90 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3460,6 +3460,45 @@ LWLockRelease(AddinShmemInitLock);
the <productname>PostgreSQL</productname> source tree.
</para>
</sect3>
+
+ <sect3 id="xfunc-shared-addin-after-startup">
+ <title>Requesting Shared Memory After Startup</title>
+
+ <para>
+ There is another, more flexible method of reserving shared memory that
+ can be done after server startup and outside a
+ <literal>shmem_request_hook</literal>. To do so, each backend that will
+ 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),
+ 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.
+ </para>
+
+ <para>
+ Unlike shared memory reserved at server startup, there is no need to
+ acquire <function>AddinShmemInitLock</function> or otherwise take action
+ to avoid race conditions when reserving shared memory with
+ <function>GetNamedDSMSegment</function>. This function ensures that only
+ one backend allocates and initializes the segment and that all other
+ backends receive a pointer to the fully allocated and initialized
+ segment.
+ </para>
+
+ <para>
+ A complete usage example of <function>GetNamedDSMSegment</function> can
+ be found in
+ <filename>src/test/modules/test_dsm_registry/test_dsm_registry.c</filename>
+ in the <productname>PostgreSQL</productname> source tree.
+ </para>
+ </sect3>
</sect2>
<sect2 id="xfunc-addin-lwlocks">
@@ -3469,8 +3508,9 @@ LWLockRelease(AddinShmemInitLock);
<title>Requesting LWLocks at Startup</title>
<para>
- Add-ins can reserve LWLocks on server startup. As with shared memory,
- the add-in's shared library must be preloaded by specifying it in
+ Add-ins can reserve LWLocks on server startup. As with shared memory
+ reserved at server startup, the add-in's shared library must be preloaded
+ by specifying it in
<xref linkend="guc-shared-preload-libraries"/><indexterm><primary>shared_preload_libraries</primary></indexterm>,
and the shared library should register a
<literal>shmem_request_hook</literal> in its
@@ -3508,7 +3548,10 @@ void LWLockInitialize(LWLock *lock, int tranche_id)
process allocates a new <literal>tranche_id</literal> and initializes
each new LWLock. One way to do this is to only call these functions in
your shared memory initialization code with the
- <function>AddinShmemInitLock</function> held exclusively.
+ <function>AddinShmemInitLock</function> held exclusively. If using
+ <function>GetNamedDSMSegment</function>, calling these functions in the
+ <function>init_callback</function> callback function is sufficient to
+ avoid race conditions.
</para>
<para>
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 6d5b921038..d8a1653eb6 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -12,6 +12,7 @@ OBJS = \
barrier.o \
dsm.o \
dsm_impl.o \
+ dsm_registry.o \
ipc.o \
ipci.o \
latch.o \
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
new file mode 100644
index 0000000000..89f7d670e4
--- /dev/null
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -0,0 +1,178 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.c
+ *
+ * Functions for interfacing with the dynamic shared memory registry. This
+ * provides a way for libraries to use shared memory without needing to
+ * request it at startup time via a shmem_request_hook.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/storage/ipc/dsm_registry.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "lib/dshash.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+
+typedef struct DSMRegistryCtxStruct
+{
+ dsa_handle dsah;
+ dshash_table_handle dshh;
+} DSMRegistryCtxStruct;
+
+static DSMRegistryCtxStruct *DSMRegistryCtx;
+
+typedef struct DSMRegistryEntry
+{
+ char name[64];
+ dsm_handle handle;
+} DSMRegistryEntry;
+
+static const dshash_parameters dsh_params = {
+ offsetof(DSMRegistryEntry, handle),
+ sizeof(DSMRegistryEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_DSM_REGISTRY_HASH
+};
+
+static dsa_area *dsm_registry_dsa;
+static dshash_table *dsm_registry_table;
+
+Size
+DSMRegistryShmemSize(void)
+{
+ return MAXALIGN(sizeof(DSMRegistryCtxStruct));
+}
+
+void
+DSMRegistryShmemInit(void)
+{
+ bool found;
+
+ DSMRegistryCtx = (DSMRegistryCtxStruct *)
+ ShmemInitStruct("DSM Registry Data",
+ DSMRegistryShmemSize(),
+ &found);
+
+ if (!found)
+ {
+ DSMRegistryCtx->dsah = DSA_HANDLE_INVALID;
+ DSMRegistryCtx->dshh = DSHASH_HANDLE_INVALID;
+ }
+}
+
+/*
+ * Initialize or attach to the dynamic shared hash table that stores the DSM
+ * registry entries, if not already done. This must be called before accessing
+ * the table.
+ */
+static void
+init_dsm_registry(void)
+{
+ /* Quick exit if we already did this. */
+ if (dsm_registry_table)
+ return;
+
+ /* Otherwise, use a lock to ensure only one process creates the table. */
+ LWLockAcquire(DSMRegistryLock, LW_EXCLUSIVE);
+
+ if (DSMRegistryCtx->dshh == DSHASH_HANDLE_INVALID)
+ {
+ /* Initialize dynamic shared hash table for registry. */
+ dsm_registry_dsa = dsa_create(LWTRANCHE_DSM_REGISTRY_DSA);
+ dsa_pin(dsm_registry_dsa);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_create(dsm_registry_dsa, &dsh_params, 0);
+
+ /* Store handles in shared memory for other backends to use. */
+ DSMRegistryCtx->dsah = dsa_get_handle(dsm_registry_dsa);
+ DSMRegistryCtx->dshh = dshash_get_hash_table_handle(dsm_registry_table);
+ }
+ else
+ {
+ /* Attach to existing dynamic shared hash table. */
+ dsm_registry_dsa = dsa_attach(DSMRegistryCtx->dsah);
+ dsa_pin_mapping(dsm_registry_dsa);
+ dsm_registry_table = dshash_attach(dsm_registry_dsa, &dsh_params,
+ DSMRegistryCtx->dshh, 0);
+ }
+
+ LWLockRelease(DSMRegistryLock);
+}
+
+/*
+ * 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.
+ */
+void *
+GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr), bool *found)
+{
+ DSMRegistryEntry *entry;
+ MemoryContext oldcontext;
+ char name_padded[offsetof(DSMRegistryEntry, handle)] = {0};
+ void *ret;
+
+ Assert(found);
+
+ if (!name || *name == '\0')
+ elog(ERROR, "DSM segment name cannot be empty");
+
+ if (strlen(name) >= offsetof(DSMRegistryEntry, handle))
+ elog(ERROR, "DSM segment name too long");
+
+ if (size == 0)
+ elog(ERROR, "DSM segment size must be nonzero");
+
+ /* Be sure any local memory allocated by DSM/DSA routines is persistent. */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Connect to the registry. */
+ init_dsm_registry();
+
+ strcpy(name_padded, name);
+ entry = dshash_find_or_insert(dsm_registry_table, name_padded, found);
+ if (!(*found))
+ {
+ /* Initialize the segment. */
+ dsm_segment *seg = dsm_create(size, 0);
+
+ dsm_pin_segment(seg);
+ dsm_pin_mapping(seg);
+ entry->handle = dsm_segment_handle(seg);
+ ret = dsm_segment_address(seg);
+
+ if (init_callback)
+ (*init_callback) (ret);
+ }
+ else if (!dsm_find_mapping(entry->handle))
+ {
+ /* Attach to existing segment. */
+ dsm_segment *seg = dsm_attach(entry->handle);
+
+ dsm_pin_mapping(seg);
+ ret = dsm_segment_address(seg);
+ }
+ else
+ {
+ /* Return address of an already-attached segment. */
+ ret = dsm_segment_address(dsm_find_mapping(entry->handle));
+ }
+
+ dshash_release_lock(dsm_registry_table, entry);
+ MemoryContextSwitchTo(oldcontext);
+
+ return ret;
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index e5119ed55d..fbc62b1563 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -40,6 +40,7 @@
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
@@ -115,6 +116,7 @@ CalculateShmemSize(int *num_semaphores)
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof(ShmemIndexEnt)));
size = add_size(size, dsm_estimate_size());
+ size = add_size(size, DSMRegistryShmemSize());
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, PredicateLockShmemSize());
@@ -289,6 +291,7 @@ CreateOrAttachShmemStructs(void)
InitShmemIndex();
dsm_shmem_init();
+ DSMRegistryShmemInit();
/*
* Set up xlog, clog, and buffers
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 08bdc718b8..5a936171f7 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -4,6 +4,7 @@ backend_sources += files(
'barrier.c',
'dsm.c',
'dsm_impl.c',
+ 'dsm_registry.c',
'ipc.c',
'ipci.c',
'latch.c',
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index b4b989ac56..2f2de5a562 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,10 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_DSM_REGISTRY_DSA: */
+ "DSMRegistryDSA",
+ /* LWTRANCHE_DSM_REGISTRY_HASH: */
+ "DSMRegistryHash",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..ef8542de46 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -55,3 +55,4 @@ WrapLimitsVacuumLock 46
NotifyQueueTailLock 47
WaitEventExtensionLock 48
WALSummarizerLock 49
+DSMRegistryLock 50
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index f625473ad4..6bcb1cca0c 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -329,6 +329,7 @@ WrapLimitsVacuum "Waiting to update limits on transaction id and multixact consu
NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message storage."
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
WALSummarizer "Waiting to read or update WAL summarization state."
+DSMRegistry "Waiting to read or update the dynamic shared memory registry."
#
# END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
@@ -367,6 +368,8 @@ PgStatsHash "Waiting for stats shared memory hash table access."
PgStatsData "Waiting for shared memory stats data access."
LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic shared memory allocator."
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
+DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
+DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
#
# Wait Events - Lock
diff --git a/src/include/storage/dsm_registry.h b/src/include/storage/dsm_registry.h
new file mode 100644
index 0000000000..8e8a23ba60
--- /dev/null
+++ b/src/include/storage/dsm_registry.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * dsm_registry.h
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/dsm_registry.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef DSM_REGISTRY_H
+#define DSM_REGISTRY_H
+
+extern void *GetNamedDSMSegment(const char *name, size_t size,
+ void (*init_callback) (void *ptr),
+ bool *found);
+
+extern Size DSMRegistryShmemSize(void);
+extern void DSMRegistryShmemInit(void);
+
+#endif /* DSM_REGISTRY_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 167ae34208..50a65e046d 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,8 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_DSM_REGISTRY_DSA,
+ LWTRANCHE_DSM_REGISTRY_HASH,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 5d33fa6a9a..f656032589 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -18,6 +18,7 @@ SUBDIRS = \
test_custom_rmgrs \
test_ddl_deparse \
test_dsa \
+ test_dsm_registry \
test_extensions \
test_ginpostinglist \
test_integerset \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 00ff1d77d1..2c3b8d73bc 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -15,6 +15,7 @@ subdir('test_copy_callbacks')
subdir('test_custom_rmgrs')
subdir('test_ddl_deparse')
subdir('test_dsa')
+subdir('test_dsm_registry')
subdir('test_extensions')
subdir('test_ginpostinglist')
subdir('test_integerset')
diff --git a/src/test/modules/test_dsm_registry/.gitignore b/src/test/modules/test_dsm_registry/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_dsm_registry/Makefile b/src/test/modules/test_dsm_registry/Makefile
new file mode 100644
index 0000000000..b13e99a354
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_dsm_registry/Makefile
+
+MODULE_big = test_dsm_registry
+OBJS = \
+ $(WIN32RES) \
+ test_dsm_registry.o
+PGFILEDESC = "test_dsm_registry - test code for the DSM registry"
+
+EXTENSION = test_dsm_registry
+DATA = test_dsm_registry--1.0.sql
+
+REGRESS = test_dsm_registry
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_dsm_registry
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
new file mode 100644
index 0000000000..8ffbd343a0
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/expected/test_dsm_registry.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+ set_val_in_shmem
+------------------
+
+(1 row)
+
+\c
+SELECT get_val_in_shmem();
+ get_val_in_shmem
+------------------
+ 1236
+(1 row)
+
diff --git a/src/test/modules/test_dsm_registry/meson.build b/src/test/modules/test_dsm_registry/meson.build
new file mode 100644
index 0000000000..a4045fea37
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_dsm_registry_sources = files(
+ 'test_dsm_registry.c',
+)
+
+if host_system == 'windows'
+ test_dsm_registry_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_dsm_registry',
+ '--FILEDESC', 'test_dsm_registry - test code for the DSM registry',])
+endif
+
+test_dsm_registry = shared_module('test_dsm_registry',
+ test_dsm_registry_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_dsm_registry
+
+test_install_data += files(
+ 'test_dsm_registry.control',
+ 'test_dsm_registry--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_dsm_registry',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'test_dsm_registry',
+ ],
+ },
+}
diff --git a/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
new file mode 100644
index 0000000000..b3351be0a1
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/sql/test_dsm_registry.sql
@@ -0,0 +1,4 @@
+CREATE EXTENSION test_dsm_registry;
+SELECT set_val_in_shmem(1236);
+\c
+SELECT get_val_in_shmem();
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
new file mode 100644
index 0000000000..8c55b0919b
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql
@@ -0,0 +1,10 @@
+/* src/test/modules/test_dsm_registry/test_dsm_registry--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_dsm_registry" to load this file. \quit
+
+CREATE FUNCTION set_val_in_shmem(val INT) RETURNS VOID
+ AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION get_val_in_shmem() RETURNS INT
+ AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.c b/src/test/modules/test_dsm_registry/test_dsm_registry.c
new file mode 100644
index 0000000000..b46cd6c620
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.c
@@ -0,0 +1,76 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_dsm_registry.c
+ * Test the DSM registry
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_dsm_registry/test_dsm_registry.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "storage/dsm_registry.h"
+#include "storage/lwlock.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct TestDSMRegistryStruct
+{
+ int val;
+ LWLock lck;
+} TestDSMRegistryStruct;
+
+static TestDSMRegistryStruct *tdr_state;
+
+static void
+tdr_init_shmem(void *ptr)
+{
+ TestDSMRegistryStruct *state = (TestDSMRegistryStruct *) ptr;
+
+ LWLockInitialize(&state->lck, LWLockNewTrancheId());
+ state->val = 0;
+}
+
+static void
+tdr_attach_shmem(void)
+{
+ bool found;
+
+ tdr_state = GetNamedDSMSegment("test_dsm_registry",
+ sizeof(TestDSMRegistryStruct),
+ tdr_init_shmem,
+ &found);
+ LWLockRegisterTranche(tdr_state->lck.tranche, "test_dsm_registry");
+}
+
+PG_FUNCTION_INFO_V1(set_val_in_shmem);
+Datum
+set_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ tdr_attach_shmem();
+
+ LWLockAcquire(&tdr_state->lck, LW_EXCLUSIVE);
+ tdr_state->val = PG_GETARG_UINT32(0);
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(get_val_in_shmem);
+Datum
+get_val_in_shmem(PG_FUNCTION_ARGS)
+{
+ int ret;
+
+ tdr_attach_shmem();
+
+ LWLockAcquire(&tdr_state->lck, LW_SHARED);
+ ret = tdr_state->val;
+ LWLockRelease(&tdr_state->lck);
+
+ PG_RETURN_UINT32(ret);
+}
diff --git a/src/test/modules/test_dsm_registry/test_dsm_registry.control b/src/test/modules/test_dsm_registry/test_dsm_registry.control
new file mode 100644
index 0000000000..813f099889
--- /dev/null
+++ b/src/test/modules/test_dsm_registry/test_dsm_registry.control
@@ -0,0 +1,4 @@
+comment = 'Test code for the DSM registry'
+default_version = '1.0'
+module_pathname = '$libdir/test_dsm_registry'
+relocatable = true
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f582eb59e7..b7e73736e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -610,6 +610,8 @@ DropSubscriptionStmt
DropTableSpaceStmt
DropUserMappingStmt
DropdbStmt
+DSMRegistryCtxStruct
+DSMRegistryEntry
DumpComponents
DumpId
DumpOptions
@@ -2799,6 +2801,7 @@ Tcl_NotifierProcs
Tcl_Obj
Tcl_Time
TempNamespaceStatus
+TestDSMRegistryStruct
TestDecodingData
TestDecodingTxnData
TestSpec
--
2.25.1
v8-0003-use-dsm-registry-for-pg_prewarm.patchtext/x-diff; charset=us-asciiDownload
From 52b125878476285b8992cd3cc070f87b2b3bb80f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Tue, 26 Dec 2023 22:25:45 -0600
Subject: [PATCH v8 3/3] use dsm registry for pg_prewarm
---
contrib/pg_prewarm/autoprewarm.c | 46 +++++++++++---------------------
1 file changed, 15 insertions(+), 31 deletions(-)
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 9085a409db..9ea6c2252a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -32,12 +32,12 @@
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
-#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
+#include "storage/dsm_registry.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -95,8 +95,6 @@ static void apw_start_database_worker(void);
static bool apw_init_shmem(void);
static void apw_detach_shmem(int code, Datum arg);
static int apw_compare_blockinfo(const void *p, const void *q);
-static void autoprewarm_shmem_request(void);
-static shmem_request_hook_type prev_shmem_request_hook = NULL;
/* Pointer to shared-memory state. */
static AutoPrewarmSharedState *apw_state = NULL;
@@ -140,26 +138,11 @@ _PG_init(void)
MarkGUCPrefixReserved("pg_prewarm");
- prev_shmem_request_hook = shmem_request_hook;
- shmem_request_hook = autoprewarm_shmem_request;
-
/* Register autoprewarm worker, if enabled. */
if (autoprewarm)
apw_start_leader_worker();
}
-/*
- * Requests any additional shared memory required for autoprewarm.
- */
-static void
-autoprewarm_shmem_request(void)
-{
- if (prev_shmem_request_hook)
- prev_shmem_request_hook();
-
- RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
-}
-
/*
* Main entry point for the leader autoprewarm process. Per-database workers
* have a separate entry point.
@@ -767,6 +750,16 @@ autoprewarm_dump_now(PG_FUNCTION_ARGS)
PG_RETURN_INT64((int64) num_blocks);
}
+static void
+apw_init_state(void *ptr)
+{
+ AutoPrewarmSharedState *state = (AutoPrewarmSharedState *) ptr;
+
+ LWLockInitialize(&state->lock, LWLockNewTrancheId());
+ state->bgworker_pid = InvalidPid;
+ state->pid_using_dumpfile = InvalidPid;
+}
+
/*
* Allocate and initialize autoprewarm related shared memory, if not already
* done, and set up backend-local pointer to that state. Returns true if an
@@ -777,19 +770,10 @@ apw_init_shmem(void)
{
bool found;
- LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
- apw_state = ShmemInitStruct("autoprewarm",
- sizeof(AutoPrewarmSharedState),
- &found);
- if (!found)
- {
- /* First time through ... */
- LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
- apw_state->bgworker_pid = InvalidPid;
- apw_state->pid_using_dumpfile = InvalidPid;
- }
- LWLockRelease(AddinShmemInitLock);
-
+ apw_state = GetNamedDSMSegment("autoprewarm",
+ sizeof(AutoPrewarmSharedState),
+ apw_init_state,
+ &found);
LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");
return found;
--
2.25.1
On Tue, Jan 16, 2024 at 9:37 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
The autoprewarm change (0003) does use this variable. I considered making
it optional (i.e., you could pass in NULL if you didn't want it), but I
didn't feel like the extra code in GetNamedDSMSegment() to allow this was
worth it so that callers could avoid creating a single bool.
I'm okay with it.
The v8 patches look good to me.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Wed, Jan 17, 2024 at 08:00:00AM +0530, Bharath Rupireddy wrote:
The v8 patches look good to me.
Committed. Thanks everyone for reviewing!
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Nathan Bossart <nathandbossart@gmail.com> writes:
Committed. Thanks everyone for reviewing!
Coverity complained about this:
*** CID 1586660: Null pointer dereferences (NULL_RETURNS)
/srv/coverity/git/pgsql-git/postgresql/src/backend/storage/ipc/dsm_registry.c: 185 in GetNamedDSMSegment()
179 }
180 else if (!dsm_find_mapping(entry->handle))
181 {
182 /* Attach to existing segment. */
183 dsm_segment *seg = dsm_attach(entry->handle);
184
CID 1586660: Null pointer dereferences (NULL_RETURNS)
Dereferencing a pointer that might be "NULL" "seg" when calling "dsm_pin_mapping".
185 dsm_pin_mapping(seg);
186 ret = dsm_segment_address(seg);
187 }
188 else
189 {
190 /* Return address of an already-attached segment. */
I think it's right --- the comments for dsm_attach explicitly
point out that a NULL return is possible. You need to handle
that scenario in some way other than SIGSEGV.
regards, tom lane
On Sun, Jan 21, 2024 at 11:21:46AM -0500, Tom Lane wrote:
Coverity complained about this:
*** CID 1586660: Null pointer dereferences (NULL_RETURNS)
/srv/coverity/git/pgsql-git/postgresql/src/backend/storage/ipc/dsm_registry.c: 185 in GetNamedDSMSegment()
179 }
180 else if (!dsm_find_mapping(entry->handle))
181 {
182 /* Attach to existing segment. */
183 dsm_segment *seg = dsm_attach(entry->handle);
184CID 1586660: Null pointer dereferences (NULL_RETURNS)
Dereferencing a pointer that might be "NULL" "seg" when calling "dsm_pin_mapping".185 dsm_pin_mapping(seg);
186 ret = dsm_segment_address(seg);
187 }
188 else
189 {
190 /* Return address of an already-attached segment. */I think it's right --- the comments for dsm_attach explicitly
point out that a NULL return is possible. You need to handle
that scenario in some way other than SIGSEGV.
Oops. I've attached an attempt at fixing this. I took the opportunity to
clean up the surrounding code a bit.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v1-0001-fix-coverity-complaint.patchtext/x-diff; charset=us-asciiDownload
From f4c1c7a7ce8bccf7251e384f895f34beb33f839e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Sun, 21 Jan 2024 16:05:16 -0600
Subject: [PATCH v1 1/1] fix coverity complaint
---
src/backend/storage/ipc/dsm_registry.c | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/backend/storage/ipc/dsm_registry.c b/src/backend/storage/ipc/dsm_registry.c
index ac11f51375..c178173653 100644
--- a/src/backend/storage/ipc/dsm_registry.c
+++ b/src/backend/storage/ipc/dsm_registry.c
@@ -177,19 +177,22 @@ GetNamedDSMSegment(const char *name, size_t size,
(errmsg("requested DSM segment size does not match size of "
"existing segment")));
}
- else if (!dsm_find_mapping(entry->handle))
+ else
{
- /* Attach to existing segment. */
- dsm_segment *seg = dsm_attach(entry->handle);
+ dsm_segment *seg = dsm_find_mapping(entry->handle);
+
+ /* If the existing segment is not already attached, attach it now. */
+ if (seg == NULL)
+ {
+ seg = dsm_attach(entry->handle);
+ if (seg == NULL)
+ elog(ERROR, "could not map dynamic shared memory segment");
+
+ dsm_pin_mapping(seg);
+ }
- dsm_pin_mapping(seg);
ret = dsm_segment_address(seg);
}
- else
- {
- /* Return address of an already-attached segment. */
- ret = dsm_segment_address(dsm_find_mapping(entry->handle));
- }
dshash_release_lock(dsm_registry_table, entry);
MemoryContextSwitchTo(oldcontext);
--
2.25.1
On Sun, Jan 21, 2024 at 04:13:20PM -0600, Nathan Bossart wrote:
Oops. I've attached an attempt at fixing this. I took the opportunity to
clean up the surrounding code a bit.
Thanks for the patch. Your proposed attempt looks correct to me with
an ERROR when no segments are found..
--
Michael
On Mon, Jan 22, 2024 at 3:43 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Oops. I've attached an attempt at fixing this. I took the opportunity to
clean up the surrounding code a bit.
The code looks cleaner and readable with the patch. All the call sites
are taking care of dsm_attach returning NULL value. So, the attached
patch looks good to me.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 22, 2024 at 05:00:48PM +0530, Bharath Rupireddy wrote:
On Mon, Jan 22, 2024 at 3:43 AM Nathan Bossart <nathandbossart@gmail.com> wrote:
Oops. I've attached an attempt at fixing this. I took the opportunity to
clean up the surrounding code a bit.The code looks cleaner and readable with the patch. All the call sites
are taking care of dsm_attach returning NULL value. So, the attached
patch looks good to me.
Committed. Thanks for the report and the reviews.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com