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
>From b63c28303384636699f2f514e71b62829346be4b Mon Sep 17 00:00:00 2001 From: Nathan Bossart <nat...@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
>From 43c37b8dab039cb31efb8e65d67f1131b15c72b6 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <nat...@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