commit efe0f3f5ef3109867382e73410fd802ae2a0e294 Author: Ashutosh Bapat Date: Tue Feb 17 16:51:20 2026 +0530 WIP: resizable shared memory structures diff --git a/configure.ac b/configure.ac index 2342780359a..7f2cab9d105 100644 --- a/configure.ac +++ b/configure.ac @@ -1895,6 +1895,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 ]) +# 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 ]) +AC_CHECK_DECLS([MADV_REMOVE], [], [], [#include ]) + AC_REPLACE_FUNCS(m4_normalize([ explicit_bzero getopt diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml index 2ebec6928d5..31cd2dabb54 100644 --- a/doc/src/sgml/system-views.sgml +++ b/doc/src/sgml/system-views.sgml @@ -4243,8 +4243,39 @@ 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 size and allocated_size 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. + + + + + + maximum_size int8 + + + Maximum size in bytes that the allocation can grow upto. For fixed-size allocations + allocated_size and + maxium_size are same. For anonymous + allocations, no information about maximum size is available, so the + size and maximum_size columns will + always be equal. Maximum size is not meaningful for free memory, so the + columns will be equal in that case also. + + + + + + allocated_space int8 + + + Address space, as against the memory, allocated for this allocation in + terms of bytes for resizable structures. It is greater than or equal to + allocated_size for these structures. It also + includes padding, if any. For fixed-size allocations, anonymous + allocations, and free memory this is same as + allocated_size. diff --git a/meson.build b/meson.build index 8b134b28a69..5310a9789aa 100644 --- a/meson.build +++ b/meson.build @@ -2837,6 +2837,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..2e71a6823f0 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,53 @@ PGSharedMemoryDetach(void) AnonymousShmem = NULL; } } + +#ifdef HAVE_RESIZABLE_SHMEM +/* + * 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) +{ + 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)); + Assert(size > 0); + + if (madvise(addr, size, MADV_REMOVE) == -1) + ereport(ERROR, + (errmsg("could not free shared memory: %m"))); +} + +/* + * 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) +{ + 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)); + Assert(size > 0); + + if (madvise(addr, size, MADV_POPULATE_WRITE) == -1) + ereport(ERROR, + (errmsg("could not allocate shared memory: %m"))); +} +#endif /* HAVE_RESIZABLE_SHMEM */ diff --git a/src/backend/port/win32_shmem.c b/src/backend/port/win32_shmem.c index 794e4fcb2ad..dc2ee018845 100644 --- a/src/backend/port/win32_shmem.c +++ b/src/backend/port/win32_shmem.c @@ -648,3 +648,26 @@ 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; +} diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index dbb6684ac10..0c974ce1125 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -170,6 +170,13 @@ typedef struct ShmemAreaKind kind; } ShmemRequest; +#ifdef HAVE_RESIZABLE_SHMEM +#define SHMEM_REQUEST_SPACE_SIZE(request) \ + ((request)->options->maximum_size > 0 ? (request)->options->maximum_size : (request)->options->size) +#else +#define SHMEM_REQUEST_SPACE_SIZE(request) ((request)->options->size) +#endif + static List *pending_shmem_requests; /* @@ -271,6 +278,10 @@ typedef struct void *location; /* location in shared mem */ Size size; /* # bytes requested for the structure */ Size allocated_size; /* # bytes actually allocated */ +#ifdef HAVE_RESIZABLE_SHMEM + Size maximum_size; /* the maximum size a structure can grow to */ + Size allocated_space; /* the total address space allocated */ +#endif } ShmemIndexEnt; /* To get reliable results for NUMA inquiry we need to "touch pages" once */ @@ -278,6 +289,9 @@ static bool firstNumaTouch = true; static bool AttachOrInitShmemIndexEntry(ShmemRequest *request, bool may_init, bool may_attach); +#ifdef HAVE_RESIZABLE_SHMEM +static Size EstimateAllocatedSize(ShmemIndexEnt *entry); +#endif Datum pg_numa_available(PG_FUNCTION_ARGS); @@ -349,6 +363,11 @@ ShmemRequestInternal(ShmemStructDesc *desc, ShmemStructOpts *options, 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); +#ifdef HAVE_RESIZABLE_SHMEM + 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); +#endif } else { @@ -357,8 +376,24 @@ ShmemRequestInternal(ShmemStructDesc *desc, ShmemStructOpts *options, if (options->size <= 0) elog(ERROR, "invalid size %zd for shared memory request for \"%s\"", options->size, options->name); +#ifdef HAVE_RESIZABLE_SHMEM + 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); +#endif } +#ifdef HAVE_RESIZABLE_SHMEM + 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->maximum_size > 0 && shared_memory_type != SHMEM_TYPE_MMAP) + elog(ERROR, "resizable shared memory requires shared_memory_type = mmap"); +#endif + if (shmem_request_state != SRS_REQUESTING) elog(ERROR, "ShmemRequestStruct can only be called from a shmem_request callback"); @@ -379,8 +414,12 @@ ShmemRequestInternal(ShmemStructDesc *desc, ShmemStructOpts *options, } /* - * ShmemGetRequestedSize() --- estimate the total size of all registered shared - * memory structures. + * ShmemGetRequestedSize() --- estimate the total size of all registered shared + * memory structures. + * + * When maximum_size is specified for a request, we use that instead of the + * initial size for the estimation, to ensure that enough memory is reserved for + * resizable structures. * * This is called once at postmaster startup, before the shared memory segment * has been created. @@ -397,7 +436,7 @@ ShmemGetRequestedSize(void) /* memory needed for all the requested areas */ foreach_ptr(ShmemRequest, request, pending_shmem_requests) { - size = add_size(size, request->options->size); + size = add_size(size, SHMEM_REQUEST_SPACE_SIZE(request)); size = add_size(size, request->options->extra_size); size = add_size(size, request->options->alignment); } @@ -583,13 +622,18 @@ AttachOrInitShmemIndexEntry(ShmemRequest *request, { /* * We inserted the entry to the shared memory index. Allocate - * requested amount of shared memory for it, and do basic - * initializion. + * requested amount of address space in the shared memory segment for + * it, and do basic initializion. The memory gets mapped 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. */ size_t allocated_size; void *structPtr; - structPtr = ShmemAllocRaw(request->options->size, request->options->alignment, &allocated_size); + structPtr = ShmemAllocRaw(SHMEM_REQUEST_SPACE_SIZE(request), request->options->alignment, &allocated_size); if (structPtr == NULL) { /* out of memory; remove the failed ShmemIndex entry */ @@ -601,11 +645,29 @@ AttachOrInitShmemIndexEntry(ShmemRequest *request, desc->name, request->options->size))); } index_entry->size = request->options->size; +#ifdef HAVE_RESIZABLE_SHMEM + index_entry->maximum_size = SHMEM_REQUEST_SPACE_SIZE(request); + index_entry->allocated_space = allocated_size; + if (request->options->maximum_size > 0) + { + /* Resizable structure. */ + index_entry->allocated_size = EstimateAllocatedSize(index_entry); + } + else + { + /* Fixed-size structure. */ + index_entry->allocated_size = allocated_size; + } +#else index_entry->allocated_size = allocated_size; +#endif index_entry->location = structPtr; desc->ptr = index_entry->location; desc->size = index_entry->size; +#ifdef HAVE_RESIZABLE_SHMEM + desc->maximum_size = index_entry->maximum_size; +#endif /* * Re-establish the caller's pointer variable, or do other actions to @@ -629,6 +691,102 @@ AttachOrInitShmemIndexEntry(ShmemRequest *request, return found; } +#ifdef HAVE_RESIZABLE_SHMEM +/* + * Estimate the actual memory allocated for a resizable 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->allocated_space >= entry->maximum_size); + + if (align_end >= floor_max_end) + { + /* + * A resizable structure which ends on the same page irrespective of + * its size. The structure will be allocated maximum memory at the + * beginning. + */ + return entry->allocated_space; + } + else + { + /* + * The maximal structure spans multiple pages. At the beginning the + * pages between the page where this structure, with its initial size, + * ends and the page where the next structure starts will not be + * allocated. + */ + return entry->allocated_space - (floor_max_end - align_end); + } +} + +void +ShmemResizeRegistered(const ShmemStructDesc *desc, Size new_size) +{ + ShmemIndexEnt *result; + bool found; + Size page_size = GetOSPageSize(); + char *new_end; + + Assert(new_size > 0); + + if (desc->maximum_size <= 0) + elog(ERROR, "shared memory struct \"%s\" is not resizable", desc->name); + + /* look it up in the shmem index */ + LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE); + result = (ShmemIndexEnt *) hash_search(ShmemIndex, desc->name, HASH_FIND, &found); + if (!found) + elog(ERROR, "shmem struct \"%s\" is not initialized", desc->name); + + Assert(result); + + if (result->maximum_size != desc->maximum_size) + elog(ERROR, "shmem struct \"%s\" has corrupted descriptor", desc->name); + + if (result->maximum_size < new_size) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("not enough address space is reserved for resizing structure \"%s\"", desc->name))); + + /* + * 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); + Size free_size = max_end - new_end; + + if (free_size > 0) + PGSharedMemoryEnsureFreed(new_end, free_size); + } + else if (new_size > result->size) + { + char *struct_start = (char *) TYPEALIGN_DOWN(page_size, (char *) result->location); + Size alloc_size = new_end - struct_start; + + if (alloc_size > 0) + PGSharedMemoryEnsureAllocated(struct_start, alloc_size); + } + + /* Update shmem index entry. */ + result->size = new_size; + result->allocated_size = EstimateAllocatedSize(result); + + LWLockRelease(ShmemIndexLock); +} +#endif /* HAVE_RESIZABLE_SHMEM */ + /* * InitShmemAllocator() --- set up basic pointers to shared memory. * @@ -737,6 +895,10 @@ InitShmemAllocator(PGShmemHeader *seghdr) Assert(!found); result->size = size; result->allocated_size = size; +#ifdef HAVE_RESIZABLE_SHMEM + result->maximum_size = size; + result->allocated_space = size; +#endif result->location = ShmemAllocator->index; } } @@ -1028,7 +1190,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 6 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; HASH_SEQ_STATUS hstat; ShmemIndexEnt *ent; @@ -1050,7 +1212,23 @@ 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); +#ifdef HAVE_RESIZABLE_SHMEM + values[4] = Int64GetDatum(ent->maximum_size); + values[5] = Int64GetDatum(ent->allocated_space); + + /* + * Keep track of the total allocated 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->allocated_space; +#else + values[4] = Int64GetDatum(ent->size); + values[5] = Int64GetDatum(ent->allocated_size); + named_allocated += ent->allocated_size; +#endif tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); @@ -1061,6 +1239,8 @@ 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]; tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); /* output as-of-yet unused shared memory */ @@ -1069,6 +1249,8 @@ 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]; tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); LWLockRelease(ShmemIndexLock); @@ -1256,23 +1438,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 0a862693fcd..0c5eb36d9e6 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -1210,6 +1210,13 @@ max => '1000.0', }, +{ name => 'have_resizable_shmem', 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_shmem_enabled', + boot_val => 'HAVE_RESIZABLE_SHMEM_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 1e14b7b4af0..91db51c18b1 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -646,6 +646,13 @@ static bool assert_enabled = DEFAULT_ASSERT_ENABLED; #endif static bool exec_backend_enabled = EXEC_BACKEND_ENABLED; +#ifdef HAVE_RESIZABLE_SHMEM +#define HAVE_RESIZABLE_SHMEM_ENABLED true +#else +#define HAVE_RESIZABLE_SHMEM_ENABLED false +#endif +static bool have_resizable_shmem_enabled = HAVE_RESIZABLE_SHMEM_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 3579cec5744..75d380256af 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8664,8 +8664,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}', proargmodes => '{o,o,o,o,o,o}', + proargnames => '{name,off,size,allocated_size,maximum_size,allocated_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 d8d61918aff..554b5ab78d7 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..78d2b7a63c4 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 anonymous mmap and 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..3d5aceba59c 100644 --- a/src/include/storage/pg_shmem.h +++ b/src/include/storage/pg_shmem.h @@ -89,6 +89,11 @@ extern PGShmemHeader *PGSharedMemoryCreate(Size size, PGShmemHeader **shim); extern bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2); extern void PGSharedMemoryDetach(void); +#ifdef HAVE_RESIZABLE_SHMEM +extern void PGSharedMemoryEnsureFreed(void *addr, Size size); +extern void PGSharedMemoryEnsureAllocated(void *addr, Size size); +#endif 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 4939130aab1..5e25de2e7c3 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -43,6 +43,9 @@ typedef struct ShmemStructDesc void *ptr; size_t size; +#ifdef HAVE_RESIZABLE_SHMEM + size_t maximum_size; +#endif } ShmemStructDesc; #define SHMEM_ATTACH_UNKNOWN_SIZE (-1) @@ -76,6 +79,18 @@ typedef struct ShmemStructOpts */ size_t extra_size; +#ifdef HAVE_RESIZABLE_SHMEM + + /* + * 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 contiguous + * memory. + */ + size_t maximum_size; +#endif + /* * 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 @@ -220,6 +235,9 @@ extern void *ShmemHashAlloc(Size size, void *alloc_arg); extern bool ShmemAddrIsValid(const void *addr); extern void RegisterShmemCallbacks(const ShmemCallbacks *callbacks); +#ifdef HAVE_RESIZABLE_SHMEM +extern void ShmemResizeRegistered(const ShmemStructDesc *desc, Size new_size); +#endif extern void ShmemRequestInternal(ShmemStructDesc *desc, ShmemStructOpts *options, ShmemAreaKind kind); diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 62fab9f3c2f..3ef8228851a 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -14,6 +14,7 @@ SUBDIRS = \ libpq_pipeline \ oauth_validator \ plsample \ + resizable_shmem \ spgist_name_ops \ test_aio \ test_binaryheap \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 6799ba11e11..5244b4bdb8f 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -13,6 +13,7 @@ subdir('libpq_pipeline') subdir('nbtree') subdir('oauth_validator') subdir('plsample') +subdir('resizable_shmem') subdir('spgist_name_ops') subdir('ssl_passphrase_callback') subdir('test_aio') diff --git a/src/test/modules/resizable_shmem/Makefile b/src/test/modules/resizable_shmem/Makefile new file mode 100644 index 00000000000..f3bd8ac0c7f --- /dev/null +++ b/src/test/modules/resizable_shmem/Makefile @@ -0,0 +1,23 @@ +# src/test/modules/resizable_shmem/Makefile + +MODULES = resizable_shmem +TAP_TESTS = 1 + +EXTENSION = resizable_shmem +DATA = resizable_shmem--1.0.sql +PGFILEDESC = "resizable_shmem - test module for resizable shared memory" + +# This test requires library to be loaded at the server start, so disable +# installcheck +NO_INSTALLCHECK = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/resizable_shmem +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/src/makefiles/pgxs.mk +endif diff --git a/src/test/modules/resizable_shmem/meson.build b/src/test/modules/resizable_shmem/meson.build new file mode 100644 index 00000000000..493bbbc95c3 --- /dev/null +++ b/src/test/modules/resizable_shmem/meson.build @@ -0,0 +1,36 @@ +# src/test/modules/resizable_shmem/meson.build + +resizable_shmem_sources = files( + 'resizable_shmem.c', +) + +if host_system == 'windows' + resizable_shmem_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'resizable_shmem', + '--FILEDESC', 'resizable_shmem - test module for resizable shared memory',]) +endif + +resizable_shmem = shared_module('resizable_shmem', + resizable_shmem_sources, + kwargs: pg_test_mod_args, +) +test_install_libs += resizable_shmem + +test_install_data += files( + 'resizable_shmem.control', + 'resizable_shmem--1.0.sql', +) + +tests += { + 'name': 'resizable_shmem', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_resizable_shmem.pl', + ], + # This test requires library to be loaded at the server start, so disable + # installcheck + 'runningcheck': false, + }, +} diff --git a/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql b/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql new file mode 100644 index 00000000000..c1bcb6117b6 --- /dev/null +++ b/src/test/modules/resizable_shmem/resizable_shmem--1.0.sql @@ -0,0 +1,37 @@ +/* src/test/modules/resizable_shmem/resizable_shmem--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION resizable_shmem" to load this file. \quit + +-- Function to resize the test structure in the shared memory +CREATE FUNCTION resizable_shmem_resize(new_entries integer) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- 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 integer) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- 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 integer, entry_value integer) +RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- Function to report memory usage statistics of the calling backend +CREATE FUNCTION resizable_shmem_usage(OUT rss_anon bigint, OUT rss_file bigint, OUT rss_shmem bigint, OUT vm_size bigint) +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +-- Function to get the shared memory page size +CREATE FUNCTION resizable_shmem_pagesize() +RETURNS integer +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; diff --git a/src/test/modules/resizable_shmem/resizable_shmem.c b/src/test/modules/resizable_shmem/resizable_shmem.c new file mode 100644 index 00000000000..2657150531f --- /dev/null +++ b/src/test/modules/resizable_shmem/resizable_shmem.c @@ -0,0 +1,281 @@ +/* ------------------------------------------------------------------------- + * + * resizable_shmem.c + * Test module for PostgreSQL's resizable shared memory functionality + * + * This module demonstrates and tests the resizable shared memory API + * provided by shmem.c/shmem.h. + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/shmem.h" +#include "storage/spin.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/timestamp.h" +#include "access/htup_details.h" + +#include + +PG_MODULE_MAGIC; + +/* + * Default amount of shared buffers and hence the amount of shared memory + * allocated by default is in hundreds of MBs. The memory allocated to the test + * structure will be noticeable only when it's in the same order. + */ +#define TEST_INITIAL_ENTRIES (25 * 1024 * 1024) /* Initial number of + * entries (100MB) */ +#define TEST_MAX_ENTRIES (100 * 1024 * 1024) /* Maximum number of + * entries (400MB, 4x + * initial) */ +#define TEST_ENTRY_SIZE sizeof(int32) /* Size of each entry */ + +/* + * Resizable test data structure stored in shared memory. + * + * We do not use any locks. The test performs resizing, reads and writes none of + * which are concurrent to keep the code and the test simple. + */ +typedef struct TestResizableShmemStruct +{ + /* Metadata */ + int32 num_entries; /* Number of entries that can fit */ + + /* Data area - variable size */ + int32 data[FLEXIBLE_ARRAY_MEMBER]; +} TestResizableShmemStruct; + +static ShmemStructDesc testShmemDesc; + +/* Global pointer to our shared memory structure */ +static TestResizableShmemStruct *resizable_shmem = NULL; + +static void resizable_shmem_request(void *arg); +static void resizable_shmem_shmem_init(void *arg); + +static const ShmemCallbacks pgss_shmem_callbacks = { + .request_fn = resizable_shmem_request, + .init_fn = resizable_shmem_shmem_init, +}; + +/* SQL-callable functions */ +PG_FUNCTION_INFO_V1(resizable_shmem_resize); +PG_FUNCTION_INFO_V1(resizable_shmem_write); +PG_FUNCTION_INFO_V1(resizable_shmem_read); +PG_FUNCTION_INFO_V1(resizable_shmem_usage); +PG_FUNCTION_INFO_V1(resizable_shmem_pagesize); + +/* + * Module load callback + */ +void +_PG_init(void) +{ + /* + * The module needs to be loaded via shared_preload_libraries to register + * shared memory structure. But if that's not the case, don't throw an + * error. The SQL functions check for existence of the shared memory data + * structure. + */ + if (!process_shared_preload_libraries_in_progress) + return; + + RegisterShmemCallbacks(&pgss_shmem_callbacks); +} + +/* + * Request shared memory resources + */ +static void +resizable_shmem_request(void *arg) +{ + /* Register our resizable shared memory structure */ + ShmemRequestStruct(&testShmemDesc, + .name = "resizable_shmem", + .size = offsetof(TestResizableShmemStruct, data) + (TEST_INITIAL_ENTRIES * TEST_ENTRY_SIZE), +#ifdef HAVE_RESIZABLE_SHMEM + .maximum_size = offsetof(TestResizableShmemStruct, data) + (TEST_MAX_ENTRIES * TEST_ENTRY_SIZE), +#endif + .ptr = (void **) &resizable_shmem, + ); +} + +/* + * Initialize shared memory structure + */ +static void +resizable_shmem_shmem_init(void *arg) +{ + /* + * Shared memory structure should have been allocated with the requested + * size. Initialize the metadata. + */ + Assert(resizable_shmem != NULL); + Assert(testShmemDesc.size >= offsetof(TestResizableShmemStruct, data) + (TEST_INITIAL_ENTRIES * TEST_ENTRY_SIZE)); +#ifdef HAVE_RESIZABLE_SHMEM + Assert(testShmemDesc.maximum_size >= offsetof(TestResizableShmemStruct, data) + (TEST_MAX_ENTRIES * TEST_ENTRY_SIZE)); +#endif + + resizable_shmem->num_entries = TEST_INITIAL_ENTRIES; + memset(resizable_shmem->data, 0, TEST_INITIAL_ENTRIES * TEST_ENTRY_SIZE); +} + +/* + * Resize the shared memory structure to accommodate the specified number of + * entries. + */ +Datum +resizable_shmem_resize(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_RESIZABLE_SHMEM + int32 new_entries = PG_GETARG_INT32(0); + Size new_size; + + if (!resizable_shmem) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("resizable_shmem is not initialized"))); + + new_size = offsetof(TestResizableShmemStruct, data) + (new_entries * TEST_ENTRY_SIZE); + ShmemResizeRegistered(&testShmemDesc, new_size); + resizable_shmem->num_entries = new_entries; + + PG_RETURN_VOID(); +#else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("resizable shared memory is not supported on this platform"))); +#endif +} + +/* + * Write the given integer value to all entries in the data array. + */ +Datum +resizable_shmem_write(PG_FUNCTION_ARGS) +{ + int32 entry_value = PG_GETARG_INT32(0); + int32 i; + + if (!resizable_shmem) + 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 < resizable_shmem->num_entries; i++) + resizable_shmem->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. + */ +Datum +resizable_shmem_read(PG_FUNCTION_ARGS) +{ + int32 entry_count = PG_GETARG_INT32(0); + int32 entry_value = PG_GETARG_INT32(1); + int32 i; + + if (resizable_shmem == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("resizable_shmem is not initialized"))); + + /* Validate entry_count */ + if (entry_count < 0 || entry_count > resizable_shmem->num_entries) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("entry_count %d is out of range (0..%d)", entry_count, resizable_shmem->num_entries))); + + /* Check if first entry_count entries have the expected value */ + for (i = 0; i < entry_count; i++) + { + if (resizable_shmem->data[i] != entry_value) + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); +} + +/* + * Report multiple memory usage statistics of the calling backend process + * as reported by the kernel. + * Returns RssAnon, RssFile, RssShmem, VmSize from /proc/self/status as a record. + * + * TODO: See TODO note in SQL definition of this function. + */ +Datum +resizable_shmem_usage(PG_FUNCTION_ARGS) +{ + FILE *f; + char line[256]; + int64 rss_anon_kb = -1; + int64 rss_file_kb = -1; + int64 rss_shmem_kb = -1; + int64 vm_size_kb = -1; + int found = 0; + TupleDesc tupdesc; + Datum values[4]; + bool nulls[4]; + HeapTuple tuple; + + /* Open /proc/self/status to read memory information */ + f = fopen("/proc/self/status", "r"); + if (f == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open /proc/self/status: %m"))); + + /* Look for the memory usage lines */ + while (fgets(line, sizeof(line), f) != NULL && found < 4) + { + if (rss_anon_kb == -1 && sscanf(line, "RssAnon: %ld kB", &rss_anon_kb) == 1) + found++; + else if (rss_file_kb == -1 && sscanf(line, "RssFile: %ld kB", &rss_file_kb) == 1) + found++; + else if (rss_shmem_kb == -1 && sscanf(line, "RssShmem: %ld kB", &rss_shmem_kb) == 1) + found++; + else if (vm_size_kb == -1 && sscanf(line, "VmSize: %ld kB", &vm_size_kb) == 1) + found++; + } + + fclose(f); + + /* Build tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept a record"))); + + /* Build the result tuple */ + values[0] = Int64GetDatum(rss_anon_kb >= 0 ? rss_anon_kb * 1024 : 0); + values[1] = Int64GetDatum(rss_file_kb >= 0 ? rss_file_kb * 1024 : 0); + values[2] = Int64GetDatum(rss_shmem_kb >= 0 ? rss_shmem_kb * 1024 : 0); + values[3] = Int64GetDatum(vm_size_kb >= 0 ? vm_size_kb * 1024 : 0); + + nulls[0] = nulls[1] = nulls[2] = nulls[3] = false; + + tuple = heap_form_tuple(tupdesc, values, nulls); + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + +/* + * resizable_shmem_pagesize() - Get the shared memory page size + */ +Datum +resizable_shmem_pagesize(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(pg_get_shmem_pagesize()); +} diff --git a/src/test/modules/resizable_shmem/resizable_shmem.control b/src/test/modules/resizable_shmem/resizable_shmem.control new file mode 100644 index 00000000000..1ce2c5ea21a --- /dev/null +++ b/src/test/modules/resizable_shmem/resizable_shmem.control @@ -0,0 +1,5 @@ +# resizable_shmem extension test module +comment = 'test module for testing resizable shared memory structure functionality' +default_version = '1.0' +module_pathname = '$libdir/resizable_shmem' +relocatable = true diff --git a/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl b/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl new file mode 100644 index 00000000000..c08b1e66c65 --- /dev/null +++ b/src/test/modules/resizable_shmem/t/001_resizable_shmem.pl @@ -0,0 +1,136 @@ +#!/usr/bin/perl +# Copyright (c) 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 + +my $node = PostgreSQL::Test::Cluster->new('resizable_shmem'); + +$node->init; +# Need to configure for resizable_shmem when the server starts +$node->append_conf('postgresql.conf', 'shared_preload_libraries = resizable_shmem'); +# Reduce the shared memory usage as much as possible so that resizable_shmem +# structure dominates the shared memory usage and it's easy to detect any +# digressions in the expected shared memory usage because of this structure. +$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'); +$node->start; + +# Create extension +$node->safe_psql('postgres', 'CREATE EXTENSION resizable_shmem;'); + +# Detect whether resizable shared memory is supported on this platform +my $have_resizable_shmem = + $node->safe_psql('postgres', 'SHOW have_resizable_shmem;') eq 'on'; + +my $num_entries = 25 * 1024 * 1024; # Initial number of entries in resizable shared memory +my $max_entries = 100 * 1024 * 1024; # Maximum number of entries allowed + +# 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', 'data read after write successful'); + +if ($have_resizable_shmem) +{ + # Query string variables for reuse + my $total_alloc_query = "SELECT sum(allocated_size) FROM pg_shmem_allocations;"; + # Currently only one structure is resizable + my $fixed_struct_query = "SELECT count(*) FROM pg_shmem_allocations WHERE name <> 'resizable_shmem' and size <> maximum_size;"; + + my $page_size = $node->safe_psql('postgres', "SELECT resizable_shmem_pagesize();"); + + # Create background sessions for testing + my $session1 = $node->background_psql('postgres'); + my $session2 = $node->background_psql('postgres'); + + # Verify that RssShmem does not exceed the total allocated shared memory. + # Allocated shared memory should be mostly the memory allocated to the + # resizable_shmem structure. Any large increase in expected RssShmem + # reflects the unepxected increase in memory allocated to the + # resizable_shmem structure. + sub check_shmem_usage + { + my ($session, $label) = @_; + + my $rss_shmem = $session->query_safe('SELECT rss_shmem FROM resizable_shmem_usage();', verbose => 0); + my $total_alloc = $node->safe_psql('postgres', $total_alloc_query); + + note "$label: RssShmem=$rss_shmem, sum(allocated_size)=$total_alloc"; + ok($rss_shmem <= $total_alloc, "$label: RssShmem does not exceed total allocated size"); + } + + $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', 'data read after write successful'); + check_shmem_usage($session1, 'initial write (session 1)'); + check_shmem_usage($session2, 'initial write (session 2)'); + is($node->safe_psql('postgres', $fixed_struct_query), '0', 'initial fixed sized structures'); + + # Helper to test a resize operation: resize, verify old data, write new + # data, verify new data, check shmem usage and fixed-size structures. + sub test_resize + { + my ($new_num_entries, $new_value, $resize_session, $label) = @_; + + my $old_num_entries = $num_entries; + $num_entries = $new_num_entries; + + $resize_session->query_safe("SELECT resizable_shmem_resize($num_entries);", verbose => 0); + + # Old data should still be intact in the (possibly smaller) area + my $readable_entries = ($num_entries < $old_num_entries) ? $num_entries : $old_num_entries; + is($session1->query_safe("SELECT resizable_shmem_read($readable_entries, $value);", verbose => 0), 't', "old data readable after $label"); + + $value = $new_value; + $session2->query_safe("SELECT resizable_shmem_write($value);", verbose => 0); + is($session1->query_safe("SELECT resizable_shmem_read($num_entries, $value);", verbose => 0), 't', "new data readable after $label"); + + check_shmem_usage($session1, "$label (session 1)"); + check_shmem_usage($session2, "$label (session 2)"); + is($node->safe_psql('postgres', $fixed_struct_query), '0', "fixed sized structures after $label"); + note $node->safe_psql('postgres', "SELECT * FROM pg_shmem_allocations WHERE name <> 'resizable_shmem' and size <> maximum_size;"); + } + + # Resize to maximum + test_resize($max_entries, 500, $session1, 'resize to maximum'); + + # Shrink to smaller size + test_resize(75 * 1024 * 1024, 999, $session2, 'shrinking'); + + # Resize to the same size (no-op) + test_resize($num_entries, 1999, $session2, '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/, 'Resize beyond maximum fails'); + + # Cleanup sessions + $session1->quit; + $session2->quit; +} +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, 'resize fails on unsupported platform'); + like($stderr, qr/not supported/, 'resize error mentions not supported'); +} + +# Cleanup +$node->stop; + +done_testing(); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..24b5ba58ad1 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1770,8 +1770,10 @@ 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, + maximum_size, + allocated_space + FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size, maximum_size, allocated_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 8563d6d2c97..d110240e2eb 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3137,6 +3137,7 @@ TestDSMRegistryHashEntry TestDSMRegistryStruct TestDecodingData TestDecodingTxnData +TestResizableShmemStruct TestShmemData TestSpec TestValueType