On Tue, Apr 7, 2026 at 3:36 PM Ashutosh Bapat <[email protected]> wrote: > > On Mon, Apr 6, 2026 at 7:23 PM Ashutosh Bapat > <[email protected]> wrote: > > > > I have kept these two patches separate from the main patch so that I > > can remove them if others feel they are not worth including in the > > feature. > > Here are patches rebased on the latest HEAD. No conflicts just rebase. > > Here are differences from the previous patchset. > > o. There are two patches in this patchset now. a. 0001 which supports > resizable shared memory and is equivalent to 0001 + 0002 + 0004 + 0005 > from the previous patchset. b. 0002 which is 0006 from the previous > patchset and adds support for protecting resizable shared memory > structures. 0003, which added diagnostics to investigate CFBot > failure, from the previous patchset is not required anymore since all > tests pass with CFBot. > > o. I have merged 0002 into 0001 from the previous patchset since with > that patch all platforms are green on CFBot. The resizable shared > memory test now uses /proc/self/smaps instead of /proc/self/status to > find the amount of memory allocated in the main shared memory segment > of PostgreSQL. > > o. Merged 0004, which supported minimum_size, into 0001. Minimum_size > would be useful to protect against accidental shrinkage of the > resizable structures. It will help additional support for minimum > sizes of GUCs like shared_buffers. It also makes it easy and intuitive > to distinguish between fixed-size and resizable structures, and will > be useful to find the minimum size of the shared memory segment. > > o. Merged 0005, which allows ABI compatibility between the binaries > which support resizable shared memory and those which don't, into > 0001. Apart from ABI compatibility, the code has lesser #ifdef blocks > and thus easier to read and maintain. > > I didn't find it useful to keep 0004 and 0005 separate since they were > interdependent and made review complicated and have higher chances of > being acceptable. > > o. 0006 is still separate since I am not sure whether the > functionality is absolutely needed at this time. In an offlist > discussion, Andres mentioned that it is not strictly needed. The > subsystem that uses the resizable shared memory can implement their > own protection if required and integrate it in the subsystems specific > synchronization. But Matthias thinks different. The API to add > protection is platform dependent, so it's better to abstract it via > shmem.c. If we decide to accept this patch, we should merge it into > 0001 before committing. > > Also did some more cleanups and changed the name of the GUC > have_resizable_shmem to have_resizable_shared_memory since shmem is an > internal phrase. > > I am looking at merging the resizable_shmem module into test_shmem module > next.
Here are patches with the test modules merged. The merged module looks a bit rough to me and so does 0006. For example, I am not sure whether calling ShmemStructProtect() from init_fn is a good idea. See [1] for example. But init_fn is the last chance for the subsystem to touch and setup the resizable structure before it's opened to the wild. So, in the current infrastructure, I don't see any better place to call ShmemStructProtect() either. If you run tests after applying patch 0006, you will need to apply patch attached to [1] as well; otherwise the test will hang. [1] https://www.postgresql.org/message-id/CAExHW5uMQGvQH6GKaBZVtH4S9O13TwN+_0Vy1gUpAW=_t_a...@mail.gmail.com -- Best Wishes, Ashutosh Bapat
From b4fd2ea31b670b9beac51b275cabc91e465eebe7 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> Date: Mon, 6 Apr 2026 19:00:14 +0530 Subject: [PATCH v20260407 2/3] Add support to protect unused resizable_shmem structure Add APIs to make the portion of resizable_shmem structure beyond its current size inaccessible. Author: Ashutosh Bapat <[email protected]> Suggested-by: Matthias van de Meent <[email protected]> --- doc/src/sgml/xfunc.sgml | 4 +- src/backend/port/sysv_shmem.c | 55 ++++++++++++++++++++++ src/backend/port/win32_shmem.c | 8 ++++ src/backend/storage/ipc/shmem.c | 60 ++++++++++++++++++++++++ src/include/storage/pg_shmem.h | 1 + src/include/storage/shmem.h | 1 + src/test/modules/test_shmem/test_shmem.c | 43 +++++++++++++++++ 7 files changed, 171 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 22f953db9d7..04b312fcf94 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3780,7 +3780,9 @@ my_shmem_init(void *arg) additional synchronization between the resizing process and the processes using the shared structure. Also it needs to implement additional protection to prevent access to the part of the address space beyond the size of the - structure when resizing it. + structure when resizing it. <function>ShmemProtectStruct</function> can be + called from every backend that may access the resizable structure for the + same. </para> <para> diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index bb2a81417c6..14b6fa7f7e6 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -1065,8 +1065,63 @@ PGSharedMemoryEnsureAllocated(void *addr, Size size) Assert(addr == (void *) TYPEALIGN(GetOSPageSize(), addr)); Assert(size == TYPEALIGN(GetOSPageSize(), size)); + /* + * Ensure that MADV_POPULATE_WRITE can initialize the newly allocated + * pages. + */ + if (mprotect(addr, size, PROT_READ | PROT_WRITE) != 0) + ereport(ERROR, + (errmsg("could not protect shared memory: %m"))); + if (madvise(addr, size, MADV_POPULATE_WRITE) == -1) ereport(ERROR, (errmsg("could not allocate shared memory: %m"))); #endif } + +/* + * Set memory protection on the given region of shared memory. + * + * Makes [rw_start, rw_end) readable and writable, and [rw_end, prot_end) + * inaccessible. + * + * All addresses are expected to be page aligned. + * + * Only supported on platforms that support resizable shared memory. + */ +void +PGSharedMemoryProtect(void *rw_start, void *rw_end, void *prot_end) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + + if (!AnonymousShmem) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only anonymous shared memory can be protected at runtime"))); + + Assert(rw_start == (void *) TYPEALIGN(GetOSPageSize(), rw_start)); + Assert(rw_end == (void *) TYPEALIGN(GetOSPageSize(), rw_end)); + Assert(prot_end == (void *) TYPEALIGN(GetOSPageSize(), prot_end)); + Assert(rw_end >= rw_start); + + if (rw_end > rw_start) + { + if (mprotect(rw_start, (char *) rw_end - (char *) rw_start, + PROT_READ | PROT_WRITE) != 0) + ereport(ERROR, + (errmsg("could not protect shared memory: %m"))); + } + + if (prot_end > rw_end) + { + if (mprotect(rw_end, (char *) prot_end - (char *) rw_end, + PROT_NONE) != 0) + ereport(ERROR, + (errmsg("could not protect shared memory: %m"))); + } +#endif +} diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c index c1f30665e66..b5396e4a5e8 100644 --- a/src/backend/port/win32_shmem.c +++ b/src/backend/port/win32_shmem.c @@ -693,3 +693,11 @@ PGSharedMemoryEnsureAllocated(void *addr, Size size) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("resizable shared memory is not supported on this platform"))); } + +void +PGSharedMemoryProtect(void *rw_start, void *rw_end, void *prot_end) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +} diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 8f006967790..61808c7a8e5 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -875,6 +875,66 @@ ShmemResizeStruct(const char *name, Size new_size) #endif } +/* + * ShmemProtectStruct() --- protect the unused portion of a resizable structure. + * + * Makes the region beyond the current size up to maximum_size inaccessible, and + * ensures the region up to the current size is readable and writable. Depending + * upon the platform, the protection honours the page boundaries. So it may be + * more permissible than strictly needed. + * + * Only works for resizable structures. Should be called in every backend that + * may access the resizable structure while resizing it. + */ +void +ShmemProtectStruct(const char *name) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + ShmemIndexEnt *result; + bool found; + Size page_size = GetOSPageSize(); + char *rw_start; + char *rw_end; + char *prot_end; + + LWLockAcquire(ShmemIndexLock, LW_SHARED); + result = (ShmemIndexEnt *) hash_search(ShmemIndex, name, HASH_FIND, &found); + if (!found) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("shmem struct \"%s\" is not initialized", name))); + + if (result->minimum_size == result->maximum_size) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("shared memory struct \"%s\" is not resizable", name))); + + /* Resizable structures are only supported with mmap-based shared memory. */ + Assert(shared_memory_type == SHMEM_TYPE_MMAP); + + /* Make at least [location, location+size) readable and writable */ + rw_start = (char *) TYPEALIGN_DOWN(page_size, result->location); + rw_end = (char *) TYPEALIGN(page_size, + (char *) result->location + result->size); + + /* + * Make remaining portion inaccessible while making sure that the portion + * after maximum_size is not affected since it may be used by other + * structures. + */ + prot_end = (char *) TYPEALIGN_DOWN(page_size, + (char *) result->location + result->maximum_size); + + LWLockRelease(ShmemIndexLock); + + PGSharedMemoryProtect(rw_start, rw_end, prot_end); +#endif +} + /* * InitShmemAllocator() --- set up basic pointers to shared memory. * diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h index f0efbf2aec1..5165b815cc1 100644 --- a/src/include/storage/pg_shmem.h +++ b/src/include/storage/pg_shmem.h @@ -91,6 +91,7 @@ extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2); extern void PGSharedMemoryDetach(void); extern void PGSharedMemoryEnsureFreed(void *addr, Size size); extern void PGSharedMemoryEnsureAllocated(void *addr, Size size); +extern void PGSharedMemoryProtect(void *rw_start, void *rw_end, void *prot_end); extern void GetHugePageSize(Size *hugepagesize, int *mmap_flags); extern Size GetOSPageSize(void); diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 0e6d5a63f28..f8ddb0dd7c0 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -185,6 +185,7 @@ typedef struct ShmemCallbacks extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks); extern bool ShmemAddrIsValid(const void *addr); extern void ShmemResizeStruct(const char *name, Size new_size); +extern void ShmemProtectStruct(const char *name); /* * These macros provide syntactic sugar for calling the underlying functions diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c index 72004df4083..e95225bf40a 100644 --- a/src/test/modules/test_shmem/test_shmem.c +++ b/src/test/modules/test_shmem/test_shmem.c @@ -133,6 +133,12 @@ test_shmem_init(void *arg) /* Resizable structure should have been already allocated. Initialize it. */ Assert(ResizableShmem != NULL); + +#ifdef HAVE_RESIZABLE_SHMEM + /* Protect the shared memory structure in this backend. */ + ShmemProtectStruct("resizable_shmem"); +#endif + ResizableShmem->num_entries = initial_entries; memset(ResizableShmem->data, 0, mul_size(initial_entries, TEST_ENTRY_SIZE)); } @@ -148,6 +154,16 @@ test_shmem_attach(void *arg) if (attached_or_initialized) elog(ERROR, "attach or initialize already called in this process"); attached_or_initialized = true; + + /* + * Shared memory structure should have been already allocated. Initialize + * it. + */ + Assert(ResizableShmem != NULL); + +#ifdef HAVE_RESIZABLE_SHMEM + ShmemProtectStruct("resizable_shmem"); +#endif } void @@ -259,6 +275,7 @@ resizable_shmem_resize(PG_FUNCTION_ARGS) new_size = add_size(offsetof(TestResizableData, data), mul_size(new_entries, TEST_ENTRY_SIZE)); ShmemResizeStruct("resizable_shmem", new_size); + ShmemProtectStruct("resizable_shmem"); ResizableShmem->num_entries = new_entries; PG_RETURN_VOID(); @@ -280,6 +297,19 @@ resizable_shmem_write(PG_FUNCTION_ARGS) (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("resizable_shmem is not initialized"))); +#ifdef HAVE_RESIZABLE_SHMEM + + /* + * Ideally the structure should be protected through a synchronization + * cycle across all the backends that may access the structure. But we + * don't implement any such synchronization in this test module to keep it + * simple. Given that ProcSignalBarrier mechanism is not extensible, we + * may not be able to do that as well here. Hence add protect just before + * accessing the structure. + */ + ShmemProtectStruct("resizable_shmem"); +#endif + /* Write the value to all current entries */ for (i = 0; i < ResizableShmem->num_entries; i++) ResizableShmem->data[i] = entry_value; @@ -309,6 +339,19 @@ resizable_shmem_read(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("entry_count %d is out of range (0..%d)", entry_count, ResizableShmem->num_entries))); +#ifdef HAVE_RESIZABLE_SHMEM + + /* + * Ideally the structure should be protected through a synchronization + * cycle across all the backends that may access the structure. But we + * don't implement any such synchronization in this test module to keep it + * simple. Given that ProcSignalBarrier mechanism is not extensible, we + * may not be able to do that as well here. Hence add protect just before + * accessing the structure. + */ + ShmemProtectStruct("resizable_shmem"); +#endif + for (i = 0; i < entry_count; i++) { if (ResizableShmem->data[i] != entry_value) -- 2.34.1
From 5cb135abc8f90dc7463d7a467729849fe19bf1dd Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> Date: Tue, 17 Feb 2026 16:51:20 +0530 Subject: [PATCH v20260407 1/3] resizable shared memory structures Resizable shared memory structures can be allocated by specifying a new member ShmemStructOpts::maximum_size. At the startup or when the structure is created, we reserve address space worth maximum_size in the shared memory segment. It is expected that the subsystem which creates the structure would initialize only the initial size worth of memory when creating it. In an mmap'ed memory, this should allocate memory worth the initial size. It should not allocate maximum_size worth of memory initially. As the structure is resized using ShmemResizeStruct() memory is freed or allocated in chunks of memory pages when shrinking and expanding the structure respectively. Resizable shared memory feature depends upon existence of function madvise() and constants MADV_REMOVE and MADV_WRITE_POPULATE. On the platforms which do not have these, we disable this feature at compile time. The commit introduces a compile time flag HAVE_RESIZABLE_SHMEM which is defined if MADV_REMOVE and MADV_WRITE_POPULATE exist. We don't check existence of madvise separately, since existence of the constants implies existence of the function. HAVE_RESIZABLE_SHMEM is not defined in EXEC_BACKEND builds since that's largely used for Windows where the APIs to free and allocate memory from and to a given address space are not known to the author right now. Given that PostgreSQL is used widely on Linux, providing this feature on Linux covers benefits most of its users. Once we figure out the required Windows APIs, we will support this feature on Windows as well. The feature is also not available when Sys-V shared memory is used even on Linux since we do not know whether required Sys-V APIs exist; mostly they don't. Since that combination is only available for development and testing, not supporting the feature there isn't going to impact PostgreSQL users. Using HAVE_RESIZABLE_SHMEM we disable compiling the code related to resizable shared memory structures on the platforms which do not support the feature. But we also have run time checks to disable this feature when Sys-V shared memory is used. In order to know whether a given instance of running server supports resizable structures, we have introduced GUC have_resizable_shmem. Author: Ashutosh Bapat <[email protected]> Reviewed-by: Matthias van de Meent <[email protected]> --- configure.ac | 4 + doc/src/sgml/config.sgml | 15 + doc/src/sgml/system-views.sgml | 42 ++- doc/src/sgml/xfunc.sgml | 54 +++ meson.build | 16 + src/backend/port/sysv_shmem.c | 79 +++++ src/backend/port/win32_shmem.c | 45 +++ src/backend/storage/ipc/ipci.c | 11 + src/backend/storage/ipc/shmem.c | 318 ++++++++++++++++-- src/backend/utils/misc/guc_parameters.dat | 7 + src/backend/utils/misc/guc_tables.c | 7 + src/include/catalog/pg_proc.dat | 4 +- src/include/pg_config.h.in | 8 + src/include/pg_config_manual.h | 9 + src/include/storage/pg_shmem.h | 3 + src/include/storage/shmem.h | 17 + src/test/modules/test_shmem/meson.build | 1 + .../test_shmem/t/001_late_shmem_alloc.pl | 31 ++ .../test_shmem/t/002_resizable_shmem.pl | 240 +++++++++++++ .../modules/test_shmem/test_shmem--1.0.sql | 34 ++ src/test/modules/test_shmem/test_shmem.c | 310 ++++++++++++++++- src/test/regress/expected/rules.out | 7 +- src/tools/pgindent/typedefs.list | 3 +- 23 files changed, 1213 insertions(+), 52 deletions(-) create mode 100644 src/test/modules/test_shmem/t/002_resizable_shmem.pl diff --git a/configure.ac b/configure.ac index 8d176bd3468..99fcdab04e0 100644 --- a/configure.ac +++ b/configure.ac @@ -1913,6 +1913,10 @@ AC_CHECK_DECLS([memset_s], [], [], [#define __STDC_WANT_LIB_EXT1__ 1 # This is probably only present on macOS, but may as well check always AC_CHECK_DECLS(F_FULLFSYNC, [], [], [#include <fcntl.h>]) +# Linux-specific madvise constants needed for resizable shared memory. See similar checks in meson.build for explanation of why these checks are here. +AC_CHECK_DECLS([MADV_POPULATE_WRITE], [], [], [#include <sys/mman.h>]) +AC_CHECK_DECLS([MADV_REMOVE], [], [], [#include <sys/mman.h>]) + AC_REPLACE_FUNCS(m4_normalize([ explicit_bzero getopt diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 3324d2d3c49..9f630b1a074 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -12138,6 +12138,21 @@ dynamic_library_path = '/usr/local/lib/postgresql:$libdir' </listitem> </varlistentry> + <varlistentry id="guc-have-resizable-shared-memory" xreflabel="have_resizable_shared_memory"> + <term><varname>have_resizable_shared_memory</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>have_resizable_shared_memory</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Reports whether <productname>PostgreSQL</productname> has been built + with <literal>HAVE_RESIZABLE_SHMEM</literal> enabled and supports + <link linkend="xfunc-shared-addin-resizable">Resizable shared memory structures</link>. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-huge-pages-status" xreflabel="huge_pages_status"> <term><varname>huge_pages_status</varname> (<type>enum</type>) <indexterm> diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 2ebec6928d5..9bbbfdb37c5 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -4243,8 +4243,46 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx Size of the allocation in bytes including padding. For anonymous allocations, no information about padding is available, so the <literal>size</literal> and <literal>allocated_size</literal> columns - will always be equal. Padding is not meaningful for free memory, so - the columns will be equal in that case also. + will always be equal. Padding is not meaningful for free memory, so the + columns will be equal in that case also. For resizable allocations which + may span multiple memory pages, the padding includes the padding due to + page alignment. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>minimum_size</structfield> <type>int8</type> + </para> + <para> + Minimum size in bytes that the resizable allocation can shrink to. Equals + <structfield>size</structfield>For fixed-size allocations, anonymous + allocations, and free memory. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>maximum_size</structfield> <type>int8</type> + </para> + <para> + Maximum size in bytes that the resizable allocation can grow to. Equals + <structfield>size</structfield> For fixed-size allocations, anonymous + allocations, and free memory. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>reserved_space</structfield> <type>int8</type> + </para> + <para> + Address space reserved for the allocation in bytes. For resizable + structures, this is the total address space reserved to accommodate + growth up to <structfield>maximum_size</structfield>, and is greater + than or equal to <structfield>allocated_size</structfield>. For + fixed-size allocations, anonymous allocations, and free memory this + is same as <structfield>allocated_size</structfield>. </para></entry> </row> </tbody> diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 789cac9fcab..22f953db9d7 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3744,6 +3744,60 @@ my_shmem_init(void *arg) </para> </sect3> + <sect3 id="xfunc-shared-addin-resizable"> + <title>Resizable shared memory structures</title> + + <para> + A resizable memory structure can be requested using + <function>ShmemRequestStruct()</function> by passing + <parameter>maximum_size</parameter> along with + <parameter>size</parameter>. <parameter>maximum_size</parameter> is + maximum size upto which the structure can grow where as + <parameter>size</parameter> is the initial size of the structure. + Optionally, <parameter>minimum_size</parameter> can be set to the minimum + size that the structure can shrink to. While + contiguous address space worth <parameter>maximum_size</parameter> is + allocated to the structure, only memory worth <parameter>size</parameter> + bytes is allocated initially. The <function>init_fn</function> should only + initialize the <parameter>size</parameter> amount of memory. The actual + memory allocated to this structure at any point in time is given by <link + linkend="view-pg-shmem-allocations"><structname>pg_shmem_allocations</structname>.<structfield>allocated_size</structfield></link> + and the address space reserved for this structure is given by <link + linkend="view-pg-shmem-allocations"><structname>pg_shmem_allocations</structname>.<structfield>reserved_space</structfield></link>. + </para> + + <para> + The structure can be resized using <function>ShmemResizeStruct()</function> + by passing it the structure's <parameter>name</parameter> and the new size + which can be anywhere between <parameter>minimum_size</parameter> and + <parameter>maximum_size</parameter>. If the new size is smaller than the + current size of the structure, the memory between the new size and current + size is freed while keeping the contents of the memory upto new size intact. + If the new size is greater than the current size, memory is allocated upto + new size while keeping the current contents of the structure intact. The + starting address of the structure does not change because of resizing + operation. The sybsystem using this feature needs to take care of the + additional synchronization between the resizing process and the processes + using the shared structure. Also it needs to implement additional protection + to prevent access to the part of the address space beyond the size of the + structure when resizing it. + </para> + + <para> + This functionality is available only on the platforms which provide the APIs + necessary to reserve contiguous address space and to allocate or free memory + in that address space on demand. Macro <symbol>HAVE_RESIZABLE_SHMEM</symbol> + is defined on such platforms. It can be used to guard code related to + resizing a shared memory structure. The functionality is available on with + mmap'ed memory, so subsystems which use resizable structures may have to + addtionally disable resizable memory usage when + <symbol>shared_memory_type</symbol> is not <symbol>SHMEM_TYPE_MMAP</symbol>. + A GUC <xref linkend="guc-have-resizable-shared-memory"/> is set to + <literal>on</literal> when this functionality is available in a running + server, <literal>off</literal> otherwise. + </para> + </sect3> + <sect3 id="xfunc-shared-addin-dynamic"> <title>Allocating Dynamic Shared Memory After Startup</title> diff --git a/meson.build b/meson.build index be97e986e5d..c2b86f9104e 100644 --- a/meson.build +++ b/meson.build @@ -2904,6 +2904,22 @@ decl_checks = [ ['timingsafe_bcmp', 'string.h'], ] +# Linux-specific madvise constants needed for resizable shared memory. +# Usually we use AC_CHECK_DECLS to check for function declarations, but in this +# case we are using it to detect existence of constants. These constants are +# used to define HAVE_RESIZABLE_SHMEM which is used in storage/pg_shmem.h as +# well as storage/shmem.h. The first abstracts the APIs to allocate shared +# memory segments from the operating system whereas the second abstracts APIs to +# allocate shared memory to various subsystems. Since they are related but +# orthogonal to each other, including any one of them in the other file doesn't +# make sense. pg_config_manual.h is the only place where HAVE_RESIZABLE_SHMEM +# can be defined and made available to both without including sys/mman.h. But +# for that we need constants that indicate the existence of following defines. +decl_checks += [ + ['MADV_POPULATE_WRITE', 'sys/mman.h'], + ['MADV_REMOVE', 'sys/mman.h'], +] + # Need to check for function declarations for these functions, because # checking for library symbols wouldn't handle deployment target # restrictions on macOS diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index 2e3886cf9fe..bb2a81417c6 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -589,6 +589,27 @@ check_huge_page_size(int *newval, void **extra, GucSource source) return true; } +/* + * Get the page size being used by the shared memory. + * + * The function should be called only after the shared memory has been setup. + */ +Size +GetOSPageSize(void) +{ + Size os_page_size; + + Assert(huge_pages_status != HUGE_PAGES_UNKNOWN); + + os_page_size = sysconf(_SC_PAGESIZE); + + /* If huge pages are actually in use, use huge page size */ + if (huge_pages_status == HUGE_PAGES_ON) + GetHugePageSize(&os_page_size, NULL); + + return os_page_size; +} + /* * Creates an anonymous mmap()ed shared memory segment. * @@ -991,3 +1012,61 @@ PGSharedMemoryDetach(void) AnonymousShmem = NULL; } } + +/* + * Make sure that the memory of given size from the given address is released. + * + * The address and size are expected to be page aligned. + * + * Only supported on platforms that support anonymous shared memory. + */ +void +PGSharedMemoryEnsureFreed(void *addr, Size size) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + if (!AnonymousShmem) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only anonymous shared memory can be freed"))); + + Assert(addr == (void *) TYPEALIGN(GetOSPageSize(), addr)); + Assert(size == TYPEALIGN(GetOSPageSize(), size)); + + if (madvise(addr, size, MADV_REMOVE) == -1) + ereport(ERROR, + (errmsg("could not free shared memory: %m"))); +#endif +} + +/* + * Make sure that the memory of given size from the given address is allocated. + * + * The address and size are expected to be page aligned. + * + * Only supported on platforms that support anonymous shared memory. + */ +void +PGSharedMemoryEnsureAllocated(void *addr, Size size) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + if (!AnonymousShmem) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only anonymous shared memory can be allocated at runtime"))); + + Assert(addr == (void *) TYPEALIGN(GetOSPageSize(), addr)); + Assert(size == TYPEALIGN(GetOSPageSize(), size)); + + if (madvise(addr, size, MADV_POPULATE_WRITE) == -1) + ereport(ERROR, + (errmsg("could not allocate shared memory: %m"))); +#endif +} diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c index 794e4fcb2ad..c1f30665e66 100644 --- a/src/backend/port/win32_shmem.c +++ b/src/backend/port/win32_shmem.c @@ -648,3 +648,48 @@ check_huge_page_size(int *newval, void **extra, GucSource source) } return true; } + +/* + * Get the page size used by the shared memory. + * + * The function should be called only after the shared memory has been setup. + */ +Size +GetOSPageSize(void) +{ + SYSTEM_INFO sysinfo; + Size os_page_size; + + Assert(huge_pages_status != HUGE_PAGES_UNKNOWN); + + GetSystemInfo(&sysinfo); + os_page_size = sysinfo.dwPageSize; + + /* If huge pages are actually in use, use huge page size */ + if (huge_pages_status == HUGE_PAGES_ON) + GetHugePageSize(&os_page_size, NULL); + + return os_page_size; +} + +/* + * PGSharedMemoryEnsureFreed / PGSharedMemoryEnsureAllocated + * + * Not supported on Windows. These are only meaningful on platforms with + * resizable shared memory (mmap + madvise). + */ +void +PGSharedMemoryEnsureFreed(void *addr, Size size) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +} + +void +PGSharedMemoryEnsureAllocated(void *addr, Size size) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +} diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index bf6b81e621b..4c6ece598b1 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -192,6 +192,17 @@ InitializeShmemGUCs(void) Size size_b; Size size_mb; Size hp_size; + bool have_resizable_shmem; + + /* Does this server support resizable shared memory? */ +#ifdef HAVE_RESIZABLE_SHMEM + have_resizable_shmem = (shared_memory_type == SHMEM_TYPE_MMAP); +#else + have_resizable_shmem = false; +#endif + SetConfigOption("have_resizable_shared_memory", + have_resizable_shmem ? "on" : "off", + PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT); /* * Calculate the shared memory size and round up to the nearest megabyte. diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 1ebffe5a32a..8f006967790 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -19,11 +19,11 @@ * methods). The routines in this file are used for allocating and * binding to shared memory data structures. * - * This module provides facilities to allocate fixed-size structures in shared - * memory, for things like variables shared between all backend processes. - * Each such structure has a string name to identify it, specified when it is - * requested. shmem_hash.c provides a shared hash table implementation on top - * of that. + * This module provides facilities to allocate fixed-size as well as resizable + * structures in shared memory, for things like variables shared between all + * backend processes. Each such structure has a string name to identify it, + * specified when it is requested. shmem_hash.c provides a shared hash table + * implementation on top of fixed-size structures. * * Shared memory areas should usually not be allocated after postmaster * startup, although we do allow small allocations later for the benefit of @@ -102,6 +102,24 @@ * (*options->ptr), and calls the attach_fn callback, if any, for additional * per-backend setup. * + * Resizable shared memory structures + * ---------------------------------- + * + * In order to allocate resizable shared memory structures, set + * ShmemRequestStructOpts::maximum_size to the maximum size that the structure + * can grow to. The address space for the maximum size will be reserved at + * startup, but memory is allocated or freed as the structure grows or shrinks + * respectively. ShmemRequestStructOpts::size should be set to the initial size + * of the structure, which is the amount of memory allocated at the startup. + * Optionally, ShmemRequestStructOpts::minimum_size can be set to the minimum + * size that the structure can shrink to. After startup, the structure can be + * resized by calling ShmemResizeStruct() by passing it the ShmemStructDesc for + * the structure and the new size. ShmemResizeStruct() enforces that the new + * size is within [minimum_size, maximum_size]. + * + * While resizable structures can be created after the startup, the memory + * available for them is quite limited. + * * Legacy ShmemInitStruct()/ShmemInitHash() functions * -------------------------------------------------- * @@ -167,6 +185,16 @@ typedef struct ShmemRequestKind kind; } ShmemRequest; +/* + * A convenient macro to get the space required for a shmem request consistently. + * A resizable structure, requested by non-zero maximum_size, requires space for + * its maximum size. Please note that on the platforms that do not support + * resizable shmem, the maximum_size is ensured to be 0 i.e. all the structures + * are treated as fixed-size structures. + */ +#define SHMEM_REQUEST_SPACE_SIZE(request) \ + ((request)->options->maximum_size > 0 ? (request)->options->maximum_size : (request)->options->size) + static List *pending_shmem_requests; /* @@ -269,6 +297,10 @@ typedef struct void *location; /* location in shared mem */ Size size; /* # bytes requested for the structure */ Size allocated_size; /* # bytes actually allocated */ + Size minimum_size; /* the minimum size the structure can shrink + * to */ + Size maximum_size; /* the maximum size the structure can grow to */ + Size reserved_space; /* the total address space reserved */ } ShmemIndexEnt; /* To get reliable results for NUMA inquiry we need to "touch pages" once */ @@ -277,6 +309,7 @@ static bool firstNumaTouch = true; static void CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks); static void InitShmemIndexEntry(ShmemRequest *request); static bool AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok); +static Size EstimateAllocatedSize(ShmemIndexEnt *entry); Datum pg_numa_available(PG_FUNCTION_ARGS); @@ -342,11 +375,25 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind) if (options->name == NULL) elog(ERROR, "shared memory request is missing 'name' option"); +#ifndef HAVE_RESIZABLE_SHMEM + if (options->maximum_size > 0) + elog(ERROR, "resizable shared memory is not supported on this platform"); +#else + if (options->maximum_size > 0 && shared_memory_type != SHMEM_TYPE_MMAP) + elog(ERROR, "resizable shared memory requires shared_memory_type = mmap"); +#endif + if (IsUnderPostmaster) { if (options->size <= 0 && options->size != SHMEM_ATTACH_UNKNOWN_SIZE) elog(ERROR, "invalid size %zd for shared memory request for \"%s\"", options->size, options->name); + if (options->minimum_size < 0 && options->minimum_size != SHMEM_ATTACH_UNKNOWN_SIZE) + elog(ERROR, "invalid minimum_size %zd for shared memory request for \"%s\"", + options->minimum_size, options->name); + if (options->maximum_size < 0 && options->maximum_size != SHMEM_ATTACH_UNKNOWN_SIZE) + elog(ERROR, "invalid maximum_size %zd for shared memory request for \"%s\"", + options->maximum_size, options->name); } else { @@ -355,12 +402,36 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind) if (options->size <= 0) elog(ERROR, "invalid size %zd for shared memory request for \"%s\"", options->size, options->name); + if (options->minimum_size == SHMEM_ATTACH_UNKNOWN_SIZE) + elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup"); + if (options->minimum_size < 0) + elog(ERROR, "invalid minimum_size %zd for shared memory request for \"%s\"", + options->minimum_size, options->name); + if (options->maximum_size == SHMEM_ATTACH_UNKNOWN_SIZE) + elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup"); + if (options->maximum_size < 0) + elog(ERROR, "invalid maximum_size %zd for shared memory request for \"%s\"", + options->maximum_size, options->name); } if (options->alignment != 0 && pg_nextpower2_size_t(options->alignment) != options->alignment) elog(ERROR, "invalid alignment %zu for shared memory request for \"%s\"", options->alignment, options->name); + if (options->minimum_size > 0 && options->size != SHMEM_ATTACH_UNKNOWN_SIZE && + options->minimum_size > options->size) + elog(ERROR, "resizable shared memory structure \"%s\" should have minimum size (%zd) less than or equal to size (%zd)", + options->name, options->minimum_size, options->size); + + if (options->maximum_size > 0 && options->size > options->maximum_size) + elog(ERROR, "resizable shared memory structure \"%s\" should have maximum size (%zd) greater than size (%zd)", + options->name, options->maximum_size, options->size); + + if (options->minimum_size > 0 && options->maximum_size > 0 && + options->minimum_size > options->maximum_size) + elog(ERROR, "resizable shared memory structure \"%s\" should have minimum size (%zd) less than or equal to maximum size (%zd)", + options->name, options->minimum_size, options->maximum_size); + /* Check that we're in the right state */ if (shmem_request_state != SRS_REQUESTING) elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback"); @@ -382,8 +453,8 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind) } /* - * ShmemGetRequestedSize() --- estimate the total size of all registered shared - * memory structures. + * ShmemGetRequestedSize() --- estimate the total size of all registered shared + * memory structures. * * This is called at postmaster startup, before the shared memory segment has * been created. @@ -408,7 +479,7 @@ ShmemGetRequestedSize(void) alignment = PG_CACHE_LINE_SIZE; size = TYPEALIGN(alignment, size); - size = add_size(size, request->options->size); + size = add_size(size, SHMEM_REQUEST_SPACE_SIZE(request)); } return size; @@ -515,6 +586,7 @@ InitShmemIndexEntry(ShmemRequest *request) ShmemIndexEnt *index_entry; bool found; size_t allocated_size; + size_t requested_size; void *structPtr; /* look it up in the shmem index */ @@ -532,10 +604,18 @@ InitShmemIndexEntry(ShmemRequest *request) } /* - * We inserted the entry to the shared memory index. Allocate requested - * amount of shared memory for it, and initialize the index entry. + * We inserted the entry to the shared memory index. Allocate requested + * amount of address space in the shared memory segment for it, and do + * basic initializion. The memory gets allocated during initialization as + * the corresponding memory pages are written to. Allocate enough space + * for a resizable structure to grow to its maximum size. It is expected + * that the initialization callback will use only as much memory as the + * initial size of the resizable structure. (Well, if it doesn't, more + * memory will be allocated initially than expected, no further harm is + * done.) */ - structPtr = ShmemAllocRaw(request->options->size, + requested_size = SHMEM_REQUEST_SPACE_SIZE(request); + structPtr = ShmemAllocRaw(requested_size, request->options->alignment, &allocated_size); if (structPtr == NULL) @@ -544,13 +624,27 @@ InitShmemIndexEntry(ShmemRequest *request) hash_search(ShmemIndex, name, HASH_REMOVE, NULL); ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("not enough shared memory for data structure" + errmsg("not enough shared memory space for data structure" " \"%s\" (%zu bytes requested)", - name, request->options->size))); + name, requested_size))); } index_entry->size = request->options->size; index_entry->allocated_size = allocated_size; index_entry->location = structPtr; + index_entry->reserved_space = allocated_size; + if (request->options->maximum_size > 0) + { + index_entry->minimum_size = request->options->minimum_size; + index_entry->maximum_size = request->options->maximum_size; + + /* Adjust allocated size of a resizable structure. */ + index_entry->allocated_size = EstimateAllocatedSize(index_entry); + } + else + { + index_entry->minimum_size = request->options->size; + index_entry->maximum_size = request->options->size; + } /* Initialize depending on the kind of shmem area it is */ switch (request->kind) @@ -595,7 +689,7 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok) return false; } - /* Check that the size in the index matches the request */ + /* Check that the sizes in the index match the request. */ if (index_entry->size != request->options->size && request->options->size != SHMEM_ATTACH_UNKNOWN_SIZE) { @@ -605,6 +699,40 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok) name, index_entry->size, request->options->size))); } + /* + * For resizable structures, also check that minimum_size and maximum_size + * match. For fixed-size structures, these are derived (set to size) in + * the index entry and not meaningful in the request. + */ + if (request->options->maximum_size != 0) + { + if (index_entry->minimum_size != request->options->minimum_size && + request->options->minimum_size != SHMEM_ATTACH_UNKNOWN_SIZE) + { + ereport(ERROR, + (errmsg("shared memory struct \"%s\" was created with" + " different minimum_size: existing %zu, requested %zu", + name, index_entry->minimum_size, + request->options->minimum_size))); + } + + if (index_entry->maximum_size != request->options->maximum_size && + request->options->maximum_size != SHMEM_ATTACH_UNKNOWN_SIZE) + { + ereport(ERROR, + (errmsg("shared memory struct \"%s\" was created with" + " different maximum_size: existing %zu, requested %zu", + name, index_entry->maximum_size, + request->options->maximum_size))); + } + } + else + { + if (index_entry->minimum_size != index_entry->maximum_size) + elog(ERROR, "shared memory struct \"%s\" was created as resizable, but requested as fixed-size", + name); + } + /* * Re-establish the caller's pointer variable, or do other actions to * attach depending on the kind of shmem area it is. @@ -626,6 +754,127 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok) return true; } +/* + * Estimate the actual memory allocated for a resizable structure. + * + * ... based on the assumption that the memory is allocated in pages. + * + * The memory pages covered by the current size of a resizable structure are + * fully allocated when the currently allocated part of the structure is written + * to. The memory page where the maximal structure ends also hosts the next + * structure, unless the maximal structure ends on a page boundary. Hence that + * page is allocated when the next structure is written to. The memory pages + * between the page where the current structure ends and the page where the next + * structure starts remain unallocated. Thus the memory allocated for a + * resizable structure can be estimated as the total address space reserved for + * the structure minus the unallocated memory pages between the current end and + * the next structure. + */ +static Size +EstimateAllocatedSize(ShmemIndexEnt *entry) +{ + Size page_size = GetOSPageSize(); + char *align_end = (char *) TYPEALIGN(page_size, (char *) entry->location + entry->size); + char *floor_max_end = (char *) TYPEALIGN_DOWN(page_size, (char *) entry->location + entry->maximum_size); + + Assert(entry->maximum_size >= entry->size); + Assert(entry->reserved_space >= entry->maximum_size); + + if (align_end < floor_max_end) + return entry->reserved_space - (floor_max_end - align_end); + + return entry->reserved_space; +} + +/* + * ShmemResizeStruct() --- resize a resizable shared memory structure. + * + * The new size must be within [minimum_size, maximum_size]. If the structure + * is being shrunk, the memory pages that are no longer needed are freed. If + * the structure is being expanded, the memory pages that are needed for the + * new size are allocated. See EstimateAllocatedSize() for explanation of which + * pages are allocated for a resizable structure. + */ +void +ShmemResizeStruct(const char *name, Size new_size) +{ +#ifndef HAVE_RESIZABLE_SHMEM + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#else + ShmemIndexEnt *result; + bool found; + Size page_size = GetOSPageSize(); + char *new_end; + + Assert(new_size > 0); + + /* + * Resizable shared memory structures are only supported with mmap'ed + * memory. + */ + Assert(shared_memory_type == SHMEM_TYPE_MMAP); + + /* look it up in the shmem index */ + LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + result = (ShmemIndexEnt *) hash_search(ShmemIndex, name, HASH_FIND, &found); + if (!found) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("shmem struct \"%s\" is not initialized", name))); + + Assert(result); + + if (result->minimum_size == result->maximum_size) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("shared memory struct \"%s\" is not resizable", name))); + + if (new_size < result->minimum_size) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("cannot shrink shared memory structure \"%s\" below minimum size" + " (requested %zu bytes, minimum %zu bytes)", + name, new_size, result->minimum_size))); + + if (result->maximum_size < new_size) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("not enough address space is reserved for resizing structure \"%s\"" + "(required %zu bytes, reserved %zu bytes)", + name, new_size, result->maximum_size))); + + /* + * When shrinking the memory from the page aligned new end to the start of + * the page containing end of the reserved space is not required. Whereas + * when expanding the memory from the start of the page containing the + * start of the structure to the page aligned new end is required. + */ + new_end = (char *) TYPEALIGN(page_size, (char *) result->location + new_size); + if (new_size < result->size) + { + char *max_end = (char *) TYPEALIGN_DOWN(page_size, (char *) result->location + result->maximum_size); + + if (max_end > new_end) + PGSharedMemoryEnsureFreed(new_end, max_end - new_end); + } + else if (new_size > result->size) + { + char *struct_start = (char *) TYPEALIGN_DOWN(page_size, (char *) result->location); + + if (new_end > struct_start) + PGSharedMemoryEnsureAllocated(struct_start, new_end - struct_start); + } + + /* Update shmem index entry. */ + result->size = new_size; + result->allocated_size = EstimateAllocatedSize(result); + + LWLockRelease(ShmemIndexLock); +#endif +} + /* * InitShmemAllocator() --- set up basic pointers to shared memory. * @@ -732,6 +981,11 @@ InitShmemAllocator(PGShmemHeader *seghdr) Assert(!found); result->size = ShmemAllocator->index_size; result->allocated_size = ShmemAllocator->index_size; +#ifdef HAVE_RESIZABLE_SHMEM + result->minimum_size = result->size; + result->maximum_size = result->size; + result->reserved_space = result->allocated_size; +#endif result->location = ShmemAllocator->index; } } @@ -1075,7 +1329,7 @@ mul_size(Size s1, Size s2) Datum pg_get_shmem_allocations(PG_FUNCTION_ARGS) { -#define PG_GET_SHMEM_SIZES_COLS 4 +#define PG_GET_SHMEM_SIZES_COLS 7 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; HASH_SEQ_STATUS hstat; ShmemIndexEnt *ent; @@ -1097,7 +1351,17 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS) values[1] = Int64GetDatum((char *) ent->location - (char *) ShmemSegHdr); values[2] = Int64GetDatum(ent->size); values[3] = Int64GetDatum(ent->allocated_size); - named_allocated += ent->allocated_size; + values[4] = Int64GetDatum(ent->minimum_size); + values[5] = Int64GetDatum(ent->maximum_size); + values[6] = Int64GetDatum(ent->reserved_space); + + /* + * Keep track of the total reserved space for named shmem areas, to be + * able to calculate the amount of shared memory allocated for + * anonymous areas and the amount of free shared memory at the end of + * the segment. + */ + named_allocated += ent->reserved_space; tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); @@ -1108,6 +1372,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS) nulls[1] = true; values[2] = Int64GetDatum(ShmemAllocator->free_offset - named_allocated); values[3] = values[2]; + values[4] = values[2]; + values[5] = values[2]; + values[6] = values[2]; tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); /* output as-of-yet unused shared memory */ @@ -1116,6 +1383,9 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS) nulls[1] = false; values[2] = Int64GetDatum(ShmemSegHdr->totalsize - ShmemAllocator->free_offset); values[3] = values[2]; + values[4] = values[2]; + values[5] = values[2]; + values[6] = values[2]; tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); LWLockRelease(ShmemIndexLock); @@ -1303,23 +1573,9 @@ pg_get_shmem_allocations_numa(PG_FUNCTION_ARGS) Size pg_get_shmem_pagesize(void) { - Size os_page_size; -#ifdef WIN32 - SYSTEM_INFO sysinfo; - - GetSystemInfo(&sysinfo); - os_page_size = sysinfo.dwPageSize; -#else - os_page_size = sysconf(_SC_PAGESIZE); -#endif - Assert(IsUnderPostmaster); - Assert(huge_pages_status != HUGE_PAGES_UNKNOWN); - - if (huge_pages_status == HUGE_PAGES_ON) - GetHugePageSize(&os_page_size, NULL); - return os_page_size; + return GetOSPageSize(); } Datum diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index fcb6ab80583..22b7e461d3a 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -1219,6 +1219,13 @@ max => '1000.0', }, +{ name => 'have_resizable_shared_memory', type => 'bool', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS', + short_desc => 'Shows whether the running server supports resizable shared memory.', + flags => 'GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE', + variable => 'have_resizable_shared_memory_enabled', + boot_val => 'HAVE_RESIZABLE_SHARED_MEMORY_ENABLED', +}, + { name => 'hba_file', type => 'string', context => 'PGC_POSTMASTER', group => 'FILE_LOCATIONS', short_desc => 'Sets the server\'s "hba" configuration file.', flags => 'GUC_SUPERUSER_ONLY', diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index d9ca13baff9..924f95a4a70 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -653,6 +653,13 @@ static bool assert_enabled = DEFAULT_ASSERT_ENABLED; #endif static bool exec_backend_enabled = EXEC_BACKEND_ENABLED; +#ifdef HAVE_RESIZABLE_SHMEM +#define HAVE_RESIZABLE_SHARED_MEMORY_ENABLED true +#else +#define HAVE_RESIZABLE_SHARED_MEMORY_ENABLED false +#endif +static bool have_resizable_shared_memory_enabled = HAVE_RESIZABLE_SHARED_MEMORY_ENABLED; + static char *recovery_target_timeline_string; static char *recovery_target_string; static char *recovery_target_xid_string; diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 99fa9a6ede2..3a622525dfc 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8709,8 +8709,8 @@ { oid => '5052', descr => 'allocations from the main shared memory segment', proname => 'pg_get_shmem_allocations', prorows => '50', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', - proallargtypes => '{text,int8,int8,int8}', proargmodes => '{o,o,o,o}', - proargnames => '{name,off,size,allocated_size}', + proallargtypes => '{text,int8,int8,int8,int8,int8,int8}', proargmodes => '{o,o,o,o,o,o,o}', + proargnames => '{name,off,size,allocated_size,minimum_size,maximum_size,reserved_space}', prosrc => 'pg_get_shmem_allocations', proacl => '{POSTGRES=X,pg_read_all_stats=X}' }, diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index 4f8113c144b..89c4871532e 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -85,6 +85,14 @@ don't. */ #undef HAVE_DECL_F_FULLFSYNC +/* Define to 1 if you have the declaration of `MADV_POPULATE_WRITE', and to 0 + if you don't. */ +#undef HAVE_DECL_MADV_POPULATE_WRITE + +/* Define to 1 if you have the declaration of `MADV_REMOVE', and to 0 if you + don't. */ +#undef HAVE_DECL_MADV_REMOVE + /* Define to 1 if you have the declaration of `memset_s', and to 0 if you don't. */ #undef HAVE_DECL_MEMSET_S diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index 521b49b8888..b09d6c91324 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -131,6 +131,15 @@ #define EXEC_BACKEND #endif +/* + * HAVE_RESIZABLE_SHMEM indicates whether resizable shared memory structures are + * supported. The implementation requires Linux-specific madvise constants + * (MADV_REMOVE and MADV_POPULATE_WRITE). + */ +#if HAVE_DECL_MADV_REMOVE && HAVE_DECL_MADV_POPULATE_WRITE && !defined(EXEC_BACKEND) +#define HAVE_RESIZABLE_SHMEM +#endif + /* * USE_POSIX_FADVISE controls whether Postgres will attempt to use the * posix_fadvise() kernel call. Usually the automatic configure tests are diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h index 10c7b065861..f0efbf2aec1 100644 --- a/src/include/storage/pg_shmem.h +++ b/src/include/storage/pg_shmem.h @@ -89,6 +89,9 @@ extern PGShmemHeader *PGSharedMemoryCreate(Size size, PGShmemHeader **shim); extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2); extern void PGSharedMemoryDetach(void); +extern void PGSharedMemoryEnsureFreed(void *addr, Size size); +extern void PGSharedMemoryEnsureAllocated(void *addr, Size size); extern void GetHugePageSize(Size *hugepagesize, int *mmap_flags); +extern Size GetOSPageSize(void); #endif /* PG_SHMEM_H */ diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index af7fe893bc4..0e6d5a63f28 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -57,6 +57,22 @@ typedef struct ShmemStructOpts */ size_t alignment; + /* + * Minimum size this structure can shrink to. Should be set to 0 for + * fixed-size structures. + */ + ssize_t minimum_size; + + /* + * Maximum size this structure can grow upto in future. The memory is not + * allocated right away but the corresponding address space is reserved so + * that memory can be mapped to it when the structure grows. Typically + * should be used for large resizable structures which need several pages + * worth of contiguous memory. Should be set to 0 for fixed-size + * structures. + */ + ssize_t maximum_size; + /* * When the shmem area is initialized or attached to, pointer to it is * stored in *ptr. It usually points to a global variable, used to access @@ -168,6 +184,7 @@ typedef struct ShmemCallbacks extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks); extern bool ShmemAddrIsValid(const void *addr); +extern void ShmemResizeStruct(const char *name, Size new_size); /* * These macros provide syntactic sugar for calling the underlying functions diff --git a/src/test/modules/test_shmem/meson.build b/src/test/modules/test_shmem/meson.build index fb4bf328b8f..bf70b32aa1b 100644 --- a/src/test/modules/test_shmem/meson.build +++ b/src/test/modules/test_shmem/meson.build @@ -28,6 +28,7 @@ tests += { 'tap': { 'tests': [ 't/001_late_shmem_alloc.pl', + 't/002_resizable_shmem.pl', ], }, } diff --git a/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl b/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl index c154f57682a..92a8f3b4873 100644 --- a/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl +++ b/src/test/modules/test_shmem/t/001_late_shmem_alloc.pl @@ -45,5 +45,36 @@ else ok($attach_count1 == 0 && $attach_count2 == 0, "attach callback is not called when loaded via shared_preload_libraries"); } +### +# Test that a fixed-size shared memory structure cannot be resized. +# Only relevant on platforms that support resizable shmem. +### +my $have_resizable_shmem = + $node->safe_psql('postgres', 'SHOW have_resizable_shared_memory;') eq 'on'; + +if ($have_resizable_shmem) +{ + # Try expanding the fixed-size structure + my ($ret, $stdout, $stderr) = + $node->psql("postgres", "SELECT test_shmem_resize_fixed(1000);"); + isnt($ret, 0, "expanding a fixed-size structure fails"); + like($stderr, qr/is not resizable/, "expand error message mentions not resizable"); + + # Try shrinking the fixed-size structure + ($ret, $stdout, $stderr) = + $node->psql("postgres", "SELECT test_shmem_resize_fixed(1);"); + isnt($ret, 0, "shrinking a fixed-size structure fails"); + like($stderr, qr/is not resizable/, "shrink error message mentions not resizable"); +} + +### +# Test that minimum_size and maximum_size equal size for a fixed-size structure +# in pg_shmem_allocations. +### +is($node->safe_psql('postgres', + "SELECT minimum_size = size AND maximum_size = size FROM pg_shmem_allocations WHERE name = 'test_shmem area';"), + 't', "fixed-size structure has minimum_size = maximum_size = size"); + $node->stop; + done_testing(); diff --git a/src/test/modules/test_shmem/t/002_resizable_shmem.pl b/src/test/modules/test_shmem/t/002_resizable_shmem.pl new file mode 100644 index 00000000000..1c7a60407d8 --- /dev/null +++ b/src/test/modules/test_shmem/t/002_resizable_shmem.pl @@ -0,0 +1,240 @@ +# Copyright (c) 2025-2026, PostgreSQL Global Development Group + +use strict; +use warnings FATAL => 'all'; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Test resizable shared memory functionality, both when loaded at startup via +# shared_preload_libraries and when loaded after startup (late allocation). + +# Verify that RssShmem does not exceed the total allocated shared memory. +# Allocated shared memory should be mostly the memory allocated to the resizable +# structure. Any large increase in expected RssShmem should reflect the +# unexpected increase in memory allocated to the resizable structure. +sub check_shmem_usage +{ + my ($session, $label, $node) = @_; + + my $rss_shmem = $session->query_safe('SELECT resizable_shmem_usage();', + verbose => 0); + my $total_alloc = $node->safe_psql('postgres', + "SELECT sum(allocated_size) FROM pg_shmem_allocations;"); + + note "$label: RssShmem=$rss_shmem, sum(allocated_size)=$total_alloc"; + ok($rss_shmem <= $total_alloc, "$label: RssShmem does not exceed total allocated size"); +} + +# Test a resize operation: resize, verify old data, write new data, verify +# new data, and check shmem usage. Returns updated ($num_entries, $value). +sub test_resize +{ + my ($node, $prefix, $old_num_entries, $old_value, $new_num_entries, $new_value, $label) = @_; + + $label = "$prefix: $label"; + + my $session1 = $node->background_psql('postgres'); + my $session2 = $node->background_psql('postgres'); + + $session1->query_safe("SELECT resizable_shmem_resize($new_num_entries);", + verbose => 0); + + # Old data should still be intact in the (possibly smaller) area + my $readable_entries = ($new_num_entries < $old_num_entries) ? $new_num_entries : $old_num_entries; + is($session1->query_safe("SELECT resizable_shmem_read($readable_entries, $old_value);", + verbose => 0), + 't', "old data readable after $label"); + + $session2->query_safe("SELECT resizable_shmem_write($new_value);", + verbose => 0); + is($session1->query_safe("SELECT resizable_shmem_read($new_num_entries, $new_value);", + verbose => 0), + 't', "new data readable after $label"); + + check_shmem_usage($session1, "$label (session 1)", $node); + check_shmem_usage($session2, "$label (session 2)", $node); + + $session1->quit; + $session2->quit; + + return ($new_num_entries, $new_value); +} + +# Run the full suite of resizable shared memory tests on the given node. +sub run_resizable_tests +{ + my ($node, $initial_entries, $max_entries, $prefix) = @_; + + my $have_resizable_shmem = $node->safe_psql('postgres', 'SHOW have_resizable_shared_memory;') eq 'on'; + + my $num_entries = $initial_entries; + + # Basic read/write should work on all platforms + my $value = 100; + $node->safe_psql('postgres', "SELECT resizable_shmem_write($value);"); + is($node->safe_psql('postgres', "SELECT resizable_shmem_read($num_entries, $value);"), + 't', "$prefix: data read after write successful"); + + if ($have_resizable_shmem) + { + # Initial structure state + my $session1 = $node->background_psql('postgres'); + my $session2 = $node->background_psql('postgres'); + + $value = 100; + # Write and read the initial set of entries. + $session1->query_safe("SELECT resizable_shmem_write($value);", verbose => 0); + is($session2->query_safe("SELECT resizable_shmem_read($num_entries, $value);", + verbose => 0), + 't', "$prefix: data read after write successful"); + check_shmem_usage($session1, "$prefix: initial write (session 1)", $node); + check_shmem_usage($session2, "$prefix: initial write (session 2)", $node); + $session1->quit; + $session2->quit; + + # Verify no other structure is resizable + is($node->safe_psql('postgres', "SELECT count(*) FROM pg_shmem_allocations WHERE name <> 'resizable_shmem' AND maximum_size <> minimum_size;"), + '0', "$prefix: no other resizable structures"); + + # Resize to maximum + ($num_entries, $value) = test_resize($node, $prefix, $num_entries, $value, + $max_entries, 500, 'resize to maximum'); + + # Shrink to 75% of max + my $shrink_entries = int($max_entries * 3 / 4); + ($num_entries, $value) = test_resize($node, $prefix, $num_entries, $value, + $shrink_entries, 999, 'shrinking'); + + # Resize to the same size (no-op) + ($num_entries, $value) = test_resize($node, $prefix, $num_entries, $value, + $num_entries, 1999, 'no-op resize'); + + # Test resize failure (attempt to resize beyond max - should fail) + my ($ret, $stdout, $stderr) = + $node->psql('postgres', "SELECT resizable_shmem_resize(" . ($max_entries * 2) . ");"); + ok($ret != 0 || $stderr =~ /ERROR/, "$prefix: Resize beyond maximum fails"); + } + else + { + # On unsupported platforms, resizing should fail with a clear error + my ($ret, $stdout, $stderr) = + $node->psql('postgres', "SELECT resizable_shmem_resize($num_entries);"); + ok($ret != 0, "$prefix: resize fails on unsupported platform"); + like($stderr, qr/not supported/, "$prefix: resize error mentions not supported"); + } +} + +### Set up a test node. +# +#Configure minimal shared memory so that the resizable_shmem structure dominates +#and any unexpected increase is easy to detect. +# +# Also disable huge pages so that RssShmem and allocated_size are comparable. +# The latter is already aligned to the default page size. +### +my $node = PostgreSQL::Test::Cluster->new('resizable_shmem'); +$node->init; + +$node->append_conf('postgresql.conf', 'huge_pages = off'); +$node->append_conf('postgresql.conf', 'shared_buffers = 128kB'); +$node->append_conf('postgresql.conf', 'max_connections = 5'); +$node->append_conf('postgresql.conf', 'max_worker_processes = 0'); +$node->append_conf('postgresql.conf', 'max_wal_senders = 0'); +$node->append_conf('postgresql.conf', 'max_prepared_transactions = 0'); +$node->append_conf('postgresql.conf', 'max_locks_per_transaction = 10'); +$node->append_conf('postgresql.conf', 'max_pred_locks_per_transaction = 10'); +$node->append_conf('postgresql.conf', 'wal_buffers = 32kB'); + +### +# Test 1: Startup allocation via shared_preload_libraries +### +my $startup_initial = 25 * 1024 * 1024; +my $startup_max = 100 * 1024 * 1024; + +$node->append_conf('postgresql.conf', 'shared_preload_libraries = test_shmem'); +$node->append_conf('postgresql.conf', "test_shmem.initial_entries = $startup_initial"); +$node->append_conf('postgresql.conf', "test_shmem.max_entries = $startup_max"); +$node->start; +$node->safe_psql('postgres', 'CREATE EXTENSION test_shmem;'); +run_resizable_tests($node, $startup_initial, $startup_max, 'startup'); + +### +# Test 2: Late allocation (loaded after startup, not in shared_preload_libraries). +# Use much smaller sizes since only ~100KB of shared memory is available for +# structures allocated after startup. +### +my $late_initial = 5 * 1024; +my $late_max = 12 * 1024; + +$node->safe_psql('postgres', qq{ + ALTER SYSTEM RESET shared_preload_libraries; + ALTER SYSTEM SET test_shmem.initial_entries = $late_initial; + ALTER SYSTEM SET test_shmem.max_entries = $late_max; +}); +$node->safe_psql('postgres', 'DROP EXTENSION test_shmem;'); +$node->restart; + +$node->safe_psql('postgres', 'CREATE EXTENSION test_shmem;'); +run_resizable_tests($node, $late_initial, $late_max, 'late'); + +### +# Test sysv shared memory does not support resizable shmem. Only relevant on +# platforms that support resizable shmem (HAVE_RESIZABLE_SHMEM), since the +# module only sets maximum_size in that case. +### +my $resizable_shmem_binary = $node->safe_psql('postgres', 'SHOW have_resizable_shared_memory;') eq 'on'; +if ($resizable_shmem_binary) +{ + ### + # Test 3: Verify that CREATE EXTENSION fails with sysv shared memory + # when loaded after startup (not in shared_preload_libraries). + ### + $node->safe_psql('postgres', 'DROP EXTENSION test_shmem;'); + + # Remove settings that would cause the library to auto-load at startup: + # shared_preload_libraries and module-prefixed GUCs. ALTER SYSTEM RESET + # only affects postgresql.auto.conf, so we must use adjust_conf to remove + # from postgresql.conf. + $node->adjust_conf('postgresql.conf', 'shared_preload_libraries', undef); + $node->adjust_conf('postgresql.conf', 'test_shmem.initial_entries', undef); + $node->adjust_conf('postgresql.conf', 'test_shmem.max_entries', undef); + $node->adjust_conf('postgresql.auto.conf', 'shared_preload_libraries', undef); + $node->adjust_conf('postgresql.auto.conf', 'test_shmem.initial_entries', undef); + $node->adjust_conf('postgresql.auto.conf', 'test_shmem.max_entries', undef); + $node->safe_psql('postgres', qq{ + ALTER SYSTEM SET shared_memory_type = 'sysv'; + }); + + $node->restart; + + is($node->safe_psql('postgres', 'SHOW have_resizable_shared_memory'), 'off', + 'have_resizable_shared_memory is off with sysv'); + + my ($ret, $stdout, $stderr) = + $node->psql('postgres', 'CREATE EXTENSION test_shmem;'); + ok($ret != 0, 'CREATE EXTENSION fails with resizable shmem on sysv'); + like($stderr, qr/resizable shared memory requires shared_memory_type = mmap/, + 'CREATE EXTENSION error mentions shared_memory_type = mmap requirement'); + + ### + # Test 4: Verify that resizable structures are also rejected with sysv + # shared memory when loaded at startup via shared_preload_libraries. + ### + $node->safe_psql('postgres', qq{ + ALTER SYSTEM SET shared_preload_libraries = 'test_shmem'; + ALTER SYSTEM SET test_shmem.initial_entries = $startup_initial; + ALTER SYSTEM SET test_shmem.max_entries = $startup_max; + }); + $node->stop; + + ok(!$node->start(fail_ok => 1), + 'server fails to start with resizable shmem on sysv'); + + my $log = slurp_file($node->logfile); + like($log, qr/resizable shared memory requires shared_memory_type = mmap/, + 'log mentions shared_memory_type = mmap requirement'); +} + +done_testing(); diff --git a/src/test/modules/test_shmem/test_shmem--1.0.sql b/src/test/modules/test_shmem/test_shmem--1.0.sql index 2d01fd9256c..a03c90e025b 100644 --- a/src/test/modules/test_shmem/test_shmem--1.0.sql +++ b/src/test/modules/test_shmem/test_shmem--1.0.sql @@ -7,3 +7,37 @@ CREATE FUNCTION get_test_shmem_attach_count() RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION test_shmem_resize_fixed(pg_catalog.int4) +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Function to resize the resizable test structure in the shared memory +CREATE FUNCTION resizable_shmem_resize(new_entries pg_catalog.int4) +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Function to write data to all entries in the test structure in shared memory +-- Writing all the entries makes sure that the memory is actually allocated and +-- mapped to the process, so that we can later measure the memory usage. +CREATE FUNCTION resizable_shmem_write(entry_value pg_catalog.int4) +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Function to verify that specified number of initial entries have expected value. +-- Reading all the entries makes sure that the memory is actually mapped to the +-- process, so that we can later measure the memory usage. +CREATE FUNCTION resizable_shmem_read(entry_count pg_catalog.int4, entry_value pg_catalog.int4) +RETURNS pg_catalog.bool STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Function to report memory mapped against the main shared memory segment in +-- the backend where this function runs. +CREATE FUNCTION resizable_shmem_usage() +RETURNS pg_catalog.int8 STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; + +-- Function to get the shared memory page size +CREATE FUNCTION resizable_shmem_pagesize() +RETURNS pg_catalog.int4 STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c index 9bd4012b435..72004df4083 100644 --- a/src/test/modules/test_shmem/test_shmem.c +++ b/src/test/modules/test_shmem/test_shmem.c @@ -3,9 +3,10 @@ * test_shmem.c * Helpers to test shmem allocation routines * - * Test basic memory allocation in an extension module. One notable feature - * that is not exercised by any other module in the repository is the - * allocating (non-DSM) shared memory after postmaster startup. + * Test shared memory allocation in an extension module. Notably the module + * tests allocating (non-DSM) shared memory after postmaster startup and + * resizable shared memory. These two aspects of shared memory are not tested + * anywhere else. * * Copyright (c) 2020-2026, PostgreSQL Global Development Group * @@ -17,24 +18,58 @@ #include "postgres.h" +#include <limits.h> +#include <stdio.h> + +#include "commands/extension.h" #include "fmgr.h" #include "miscadmin.h" #include "storage/shmem.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" PG_MODULE_MAGIC; -typedef struct TestShmemData +#define TEST_ENTRY_SIZE sizeof(int32) /* Size of each entry */ + +typedef struct TestFixedData + { int value; bool initialized; int attach_count; -} TestShmemData; +} TestFixedData; + +static TestFixedData *FixedShmem; + +/* + * Resizable shared structure + * + * The test performs resizing, reads or writes, only one at a time and never + * concurrently. Hence, there is no need for locks in the test structure. + */ +typedef struct TestResizableData +{ + /* Metadata */ + int32 num_entries; /* Number of entries that can fit */ -static TestShmemData *TestShmem; + /* Data area - variable size */ + int32 data[FLEXIBLE_ARRAY_MEMBER]; +} TestResizableData; + +static TestResizableData *ResizableShmem = NULL; static bool attached_or_initialized = false; +/* GUC variables controlling the size of the resizable structure */ +static int initial_entries; +static int max_entries; + +/* Whether to use SHMEM_ATTACH_UNKNOWN_SIZE when attaching resizable structure. */ +static bool use_unknown_size = false; + static void test_shmem_request(void *arg); static void test_shmem_init(void *arg); static void test_shmem_attach(void *arg); @@ -49,33 +84,66 @@ static const ShmemCallbacks TestShmemCallbacks = { static void test_shmem_request(void *arg) { + Size initial_size = add_size(offsetof(TestResizableData, data), + mul_size(initial_entries, TEST_ENTRY_SIZE)); + +/* + * Create resizable structure on the platforms which support it. Otherwise create + * as a fixed-size structure. Other way would be to conditionally include + * .maximum_size in the call to ShmemRequestStruct(). + */ +#ifdef HAVE_RESIZABLE_SHMEM + Size max_size = add_size(offsetof(TestResizableData, data), + mul_size(max_entries, TEST_ENTRY_SIZE)); + Size min_size = offsetof(TestResizableData, data); + +#else + Size max_size = 0; + Size min_size = 0; +#endif + elog(LOG, "test_shmem_request callback called"); + /* Fixed-size structure */ ShmemRequestStruct(.name = "test_shmem area", - .size = sizeof(TestShmemData), - .ptr = (void **) &TestShmem); + .size = sizeof(TestFixedData), + .ptr = (void **) &FixedShmem); + + /* Resizable structure */ + ShmemRequestStruct(.name = "resizable_shmem", + .size = use_unknown_size ? SHMEM_ATTACH_UNKNOWN_SIZE : initial_size, + .minimum_size = min_size, + .maximum_size = max_size, + .ptr = (void **) &ResizableShmem); } static void test_shmem_init(void *arg) { elog(LOG, "init callback called"); - if (TestShmem->initialized) + + if (FixedShmem->initialized) elog(ERROR, "shmem area already initialized"); - TestShmem->initialized = true; + FixedShmem->initialized = true; if (attached_or_initialized) elog(ERROR, "attach or initialize already called in this process"); attached_or_initialized = true; + + /* Resizable structure should have been already allocated. Initialize it. */ + Assert(ResizableShmem != NULL); + + ResizableShmem->num_entries = initial_entries; + memset(ResizableShmem->data, 0, mul_size(initial_entries, TEST_ENTRY_SIZE)); } static void test_shmem_attach(void *arg) { elog(LOG, "test_shmem_attach callback called"); - if (!TestShmem->initialized) + if (!FixedShmem->initialized) elog(ERROR, "shmem area not yet initialized"); - TestShmem->attach_count++; + FixedShmem->attach_count++; if (attached_or_initialized) elog(ERROR, "attach or initialize already called in this process"); @@ -85,17 +153,231 @@ test_shmem_attach(void *arg) void _PG_init(void) { + int guc_context; + elog(LOG, "test_shmem module's _PG_init called"); + + /* + * Use PGC_POSTMASTER when loaded at startup so the values are fixed once + * the shared memory segment is created. When loaded after startup + * PGC_POSTMASTER is not allowed, so we use PGC_SIGHUP instead. Although + * we do not intend to change these values at config reload, PGC_SIGHUP is + * the least permissive context that allows defining the GUC after startup + * and still prevents it from being changed via SET. + */ + if (process_shared_preload_libraries_in_progress) + guc_context = PGC_POSTMASTER; + else + guc_context = PGC_SIGHUP; + + /* + * Set defaults very low so that the structure can be loaded after startup + * as well when there's only a 100KB extra memory available. + */ + DefineCustomIntVariable("test_shmem.initial_entries", + "Initial number of entries in the test structure.", + NULL, + &initial_entries, + 100, /* ~ 400 bytes */ + 1, + INT_MAX, + guc_context, + 0, + NULL, NULL, NULL); + + DefineCustomIntVariable("test_shmem.max_entries", + "Maximum number of entries in the test structure.", + NULL, + &max_entries, + 200, /* ~ 800 bytes */ + 1, + INT_MAX, + guc_context, + 0, + NULL, NULL, NULL); + + /* + * When loaded after startup by a backend that is not creating the + * extension, the shared memory might have been resized to a size other + * than the initial size. Use SHMEM_ATTACH_UNKNOWN_SIZE to attach without + * knowing the exact size. + */ + if (!process_shared_preload_libraries_in_progress && !creating_extension) + use_unknown_size = true; + RegisterShmemCallbacks(&TestShmemCallbacks); } +/* Fixed-size structure APIs */ PG_FUNCTION_INFO_V1(get_test_shmem_attach_count); Datum get_test_shmem_attach_count(PG_FUNCTION_ARGS) { if (!attached_or_initialized) elog(ERROR, "shmem area not attached or initialized in this process"); - if (!TestShmem->initialized) + if (!FixedShmem->initialized) elog(ERROR, "shmem area not yet initialized"); - PG_RETURN_INT32(TestShmem->attach_count); + PG_RETURN_INT32(FixedShmem->attach_count); +} + +/* + * Attempt to resize the fixed-size shared memory structure. This should + * fail because the structure was not allocated with a maximum_size. + */ +PG_FUNCTION_INFO_V1(test_shmem_resize_fixed); +Datum +test_shmem_resize_fixed(PG_FUNCTION_ARGS) +{ + int32 new_size = PG_GETARG_INT32(0); + + ShmemResizeStruct("test_shmem area", new_size); + PG_RETURN_VOID(); +} + +/* Resizable structure APIs */ + +/* + * Resize the shared memory structure to accommodate the specified number of + * entries. + * + * On the plaforms which do not support resizable shared memory, + * ShmemResizeStruct() will raise an error, so this function will fail if the + * caller tries to resize the structure. + */ +PG_FUNCTION_INFO_V1(resizable_shmem_resize); +Datum +resizable_shmem_resize(PG_FUNCTION_ARGS) +{ + int32 new_entries = PG_GETARG_INT32(0); + Size new_size; + + if (!ResizableShmem) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("resizable_shmem is not initialized"))); + + new_size = add_size(offsetof(TestResizableData, data), + mul_size(new_entries, TEST_ENTRY_SIZE)); + ShmemResizeStruct("resizable_shmem", new_size); + ResizableShmem->num_entries = new_entries; + + PG_RETURN_VOID(); +} + + +/* + * Write the given integer value to all entries in the data array. + */ +PG_FUNCTION_INFO_V1(resizable_shmem_write); +Datum +resizable_shmem_write(PG_FUNCTION_ARGS) +{ + int32 entry_value = PG_GETARG_INT32(0); + int32 i; + + if (!ResizableShmem) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("resizable_shmem is not initialized"))); + + /* Write the value to all current entries */ + for (i = 0; i < ResizableShmem->num_entries; i++) + ResizableShmem->data[i] = entry_value; + + PG_RETURN_VOID(); +} + +/* + * Check whether the first 'entry_count' entries all have the expected 'entry_value'. + * Returns true if all match, false otherwise. + */ +PG_FUNCTION_INFO_V1(resizable_shmem_read); +Datum +resizable_shmem_read(PG_FUNCTION_ARGS) +{ + int32 entry_count = PG_GETARG_INT32(0); + int32 entry_value = PG_GETARG_INT32(1); + int32 i; + + if (ResizableShmem == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("resizable_shmem is not initialized"))); + + if (entry_count < 0 || entry_count > ResizableShmem->num_entries) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("entry_count %d is out of range (0..%d)", entry_count, ResizableShmem->num_entries))); + + for (i = 0; i < entry_count; i++) + { + if (ResizableShmem->data[i] != entry_value) + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} + +/* + * Return the memory mapped against the main shared memory segment in this + * backend. + * + * The VMA containing our resizable_shmem pointer is used to determine the main + * memory segment. RSS + Swap (in bytes) for that VMS from /proc/self/smaps is + * returned. + */ +PG_FUNCTION_INFO_V1(resizable_shmem_usage); +Datum +resizable_shmem_usage(PG_FUNCTION_ARGS) +{ + FILE *f; + char line[256]; + int64 rss_kb = -1; + int64 swap_kb = -1; + uintptr_t target = (uintptr_t) ResizableShmem; + bool in_target_vma = false; + size_t result; + + f = fopen("/proc/self/smaps", "r"); + if (f == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open /proc/self/smaps: %m"))); + + while (fgets(line, sizeof(line), f) != NULL) + { + unsigned long start; + unsigned long end; + + if (sscanf(line, "%lx-%lx", &start, &end) == 2) + { + in_target_vma = (target >= start && target < end); + } + else if (in_target_vma) + { + if (rss_kb == -1) + sscanf(line, "Rss: %ld kB", &rss_kb); + if (swap_kb == -1) + sscanf(line, "Swap: %ld kB", &swap_kb); + if (rss_kb >= 0 && swap_kb >= 0) + break; + } + } + + fclose(f); + + result = rss_kb >= 0 ? mul_size(rss_kb, 1024) : 0; + result = add_size(result, swap_kb >= 0 ? mul_size(swap_kb, 1024) : 0); + + PG_RETURN_INT64(result); +} + +/* + * resizable_shmem_pagesize() - Get the shared memory page size + */ +PG_FUNCTION_INFO_V1(resizable_shmem_pagesize); +Datum +resizable_shmem_pagesize(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(pg_get_shmem_pagesize()); } diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..a882d799133 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1770,8 +1770,11 @@ pg_shadow| SELECT pg_authid.rolname AS usename, pg_shmem_allocations| SELECT name, off, size, - allocated_size - FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size); + allocated_size, + minimum_size, + maximum_size, + reserved_space + FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size, minimum_size, maximum_size, reserved_space); pg_shmem_allocations_numa| SELECT name, numa_node, size diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 637c669a146..c70bee8d837 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3153,7 +3153,8 @@ TestDSMRegistryHashEntry TestDSMRegistryStruct TestDecodingData TestDecodingTxnData -TestShmemData +TestFixedData +TestResizableData TestSpec TestValueType TextFreq base-commit: 55890a919454a2165031a04b175ca92e3ed70e69 -- 2.34.1
