From 62d7a2a841494c12d1814eae5ab5788dfeb9d5c1 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent Date: Tue, 7 Apr 2026 20:59:36 +0200 Subject: [PATCH v20260407 3/4] Various adjustments to resizable shmem accounting Instead of continuously falling back onto .size when .maximum_size is 0, update the copied(!) options' .maximum_size with the value of .size. This saves a compare operation per SHMEM_REQUEST_SPACE_SIZE() evaluation, and simplifies which field to access for bound checks. Similarly, .minimum_size is updated with an input of 0 meaning to default to .size. A substitute Zero value of SHMEM_RESIZE_TO_ZERO is used to allow users to resize the segment's memory usage to zero bytes used. It also reorders and deduplicates some error condition checks. --- src/backend/storage/ipc/shmem.c | 111 ++++++++++++++++++-------------- src/include/storage/shmem.h | 20 +++--- 2 files changed, 75 insertions(+), 56 deletions(-) diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 61808c7a8e5..12de6344cac 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -108,14 +108,19 @@ * 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]. + * startup, whilst the backing memory is allocated or freed as the structure + * grows or shrinks respectively. ShmemRequestStructOpts::size should be set + * to the initial size of the structure at startup. Optionally, + * ShmemRequestStructOpts::minimum_size can be set to the minimum size that + * the structure can shrink to. A sentinel value of SHMEM_RESIZE_TO_ZERO can + * be used used to indicate the struct can scale its memory usage down to 0 + * bytes; the natural 0 is assumed to be uninitialized and so will cause the + * minimum size to default to the value of ShmemRequestStructOpts::size, like + * ShmemRequestStructOpts::maximum_size. + * 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. @@ -192,8 +197,7 @@ typedef struct * 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) +#define SHMEM_REQUEST_SPACE_SIZE(request) ((request)->options->maximum_size) static List *pending_shmem_requests; @@ -371,70 +375,81 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind) { ShmemRequest *request; + /* 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"); + /* Check the options */ if (options->name == NULL) elog(ERROR, "shared memory request is missing 'name' option"); + /* + * Sanitize the options input by populating min/max with their actual values. + * + * Note that minimum_size's zero-initialized value "not specified" conflicts + * with a natural value for "resize to zero", so "resize to zero" has its own + * sentinel value with SHMEM_RESIZE_TO_ZERO. + */ + if (options->maximum_size == 0) + { + options->maximum_size = options->size; + Assert(options->minimum_size == 0); + } + + if (options->minimum_size == 0) + options->minimum_size = options->size; + else if (options->minimum_size == SHMEM_RESIZE_TO_ZERO) + options->minimum_size = 0; + + /* resizing shmem segment */ + if (options->maximum_size != options->minimum_size) + { #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"); + if (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 + + if (!IsUnderPostmaster) { if (options->size == SHMEM_ATTACH_UNKNOWN_SIZE) elog(ERROR, "SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup"); - 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->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); + 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 && + if (options->minimum_size != SHMEM_ATTACH_UNKNOWN_SIZE && options->maximum_size != SHMEM_ATTACH_UNKNOWN_SIZE && 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"); + if (options->minimum_size != SHMEM_ATTACH_UNKNOWN_SIZE && 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 != SHMEM_ATTACH_UNKNOWN_SIZE && options->size != SHMEM_ATTACH_UNKNOWN_SIZE && + options->size > options->maximum_size) + elog(ERROR, "resizable shared memory structure \"%s\" should have size (%zd) less than or equal to maximum size (%zd)", + options->name, options->size, options->maximum_size); /* Check that it's not already registered in this process */ foreach_ptr(ShmemRequest, existing, pending_shmem_requests) diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index f8ddb0dd7c0..2682704b3ef 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -58,18 +58,21 @@ typedef struct ShmemStructOpts size_t alignment; /* - * Minimum size this structure can shrink to. Should be set to 0 for - * fixed-size structures. + * Minimum size this structure can shrink to. + * When initialized to 0, it defaults to the value of the .size field; use + * SHMEM_RESIZE_TO_ZERO instead if you want your shmem allocation to be + * able to shrink to 'no memory usage'. */ 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. + * Maximum size this structure can grow up to in the future. The memory not + * required for .size 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, this should be used for large resizable + * structures which need several pages worth of contiguous memory. + * + * When set to 0, it defaults to the value of the .size field. */ ssize_t maximum_size; @@ -83,6 +86,7 @@ typedef struct ShmemStructOpts } ShmemStructOpts; #define SHMEM_ATTACH_UNKNOWN_SIZE (-1) +#define SHMEM_RESIZE_TO_ZERO (-2) /* * Options for ShmemRequestHash() -- 2.50.1 (Apple Git-155)