1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Allow unknown-size shmem attachments in single-user mode

ShmemRequestInternal() rejected SHMEM_ATTACH_UNKNOWN_SIZE whenever
IsUnderPostmaster was false.  That also rejected an after-startup
attachment in a standalone backend, even if the area exists.

To fix, check whether the shared memory has been initialized
(ShmemIndex == NULL) rather than IsUnderPostmaster.

Refactor the tests, and add more test cases for after-startup
attachment, with SHMEM_ATTACH_UNKNOWN_SIZE and a mismatching size, and
for this single-user case.

Author: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Discusssion: https://www.postgresql.org/message-id/CAJTYsWXREGXCnWdZJsXt8pT2UY_Y2GSx+HOUu2X3s8YDCLj0_A@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
Heikki Linnakangas
2026-09-30 14:25:17 +03:00
parent 9510a826e4
commit c712b0d49b
4 changed files with 190 additions and 77 deletions

View File

@@ -348,33 +348,27 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
MemoryContext oldcontext;
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");
if (IsUnderPostmaster)
if (options->size == SHMEM_ATTACH_UNKNOWN_SIZE)
{
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);
}
else
{
if (options->size == SHMEM_ATTACH_UNKNOWN_SIZE)
if (ShmemIndex == NULL)
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);
}
else if (options->size <= 0)
elog(ERROR, "invalid size %zd for shared memory request for \"%s\"",
options->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);
/* 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 that it's not already registered in this process */
foreach_ptr(ShmemRequest, existing, pending_shmem_requests)
{
@@ -790,6 +784,8 @@ void
ResetShmemAllocator(void)
{
Assert(!IsUnderPostmaster);
ShmemAllocator = NULL;
ShmemIndex = NULL;
shmem_request_state = SRS_INITIAL;
pending_shmem_requests = NIL;

View File

@@ -7,71 +7,18 @@ use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
# Initialize a cluster with the extension installed. The tests will
# call the function that comes with the extension to load it.
# Initialize a cluster with the extension installed. Some tests below will
# load the library via shared_preload_libraries, while others will load it
# after startup by calling the extension's functions.
my $node = PostgreSQL::Test::Cluster->new('main');
$node->init;
$node->start;
$node->safe_psql("postgres", "CREATE EXTENSION test_shmem");
$node->stop;
###
# Test allocating memory after startup, i.e. when the library is not
# in shared_preload_libraries
###
$node->start;
# Check that the attach counter is incremented on a new connection
my $attach_count1 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
my $attach_count2 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
cmp_ok($attach_count2, '>', $attach_count1,
"attach callback is called in each backend");
$node->stop;
###
# Test that trying to allocate a new shmem area with size =
# SHMEM_ATTACH_UNKNOWN_SIZE (-1) fails.
###
$node->append_conf('postgresql.conf', "test_shmem.area_size = -1");
$node->start;
my (undef, undef, $stderr) =
$node->psql("postgres", "SELECT get_test_shmem_attach_count();");
like(
$stderr,
qr/cannot attach to shared memory struct "test_shmem area" because it does not exist/,
"unknown size request for a nonexistent area fails");
$node->stop;
$node->adjust_conf('postgresql.conf', 'test_shmem.area_size', undef);
###
# Test allocating memory after startup in single-user mode
###
SKIP:
{
# Skip the test on Windows, as single-user mode would fail on permission
# failure with privileged accounts.
skip 'single-user test is not supported by this platform', 1
if $windows_os;
my $query = "SELECT get_test_shmem_attach_count();\n";
my $result = run_log(
[
'postgres', '--single', '-F',
'-c' => 'exit_on_error=true',
'-D' => $node->data_dir,
'postgres'
],
'<' => \$query);
ok($result, "shmem area is initialized in single-user mode");
}
###
# Test that loading via shared_preload_libraries also works
# Test that loading via shared_preload_libraries works
###
$node->append_conf('postgresql.conf',
"shared_preload_libraries = 'test_shmem'");
@@ -81,9 +28,9 @@ $node->start;
# called or not, depending on whether this is an EXEC_BACKEND build.
my $exec_backend =
$node->safe_psql("postgres", "SHOW debug_exec_backend;") eq 'on';
$attach_count1 =
my $attach_count1 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
$attach_count2 =
my $attach_count2 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
if ($exec_backend)
@@ -99,16 +46,70 @@ else
);
}
# clean up
$node->stop;
$node->adjust_conf('postgresql.conf', "shared_preload_libraries", undef);
$node->adjust_conf('postgresql.conf', 'shared_preload_libraries', undef);
###
# Test allocating memory after startup, i.e. when the library is not
# in shared_preload_libraries
###
$node->start;
# This first call to the function after startup loads the library
# and initializes the shmem area.
$attach_count1 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
# Check that the attach counter is incremented on a new connection
$attach_count2 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
cmp_ok($attach_count2, '>', $attach_count1,
"attach callback is called in each backend");
# Allocate another shmem area, after the library has been loaded
my $res = $node->safe_psql("postgres",
"SELECT test_shmem_register('test_shmem after startup', 20, 1);");
is($res, 0, 'allocate after startup');
# Test attaching to it again
$res = $node->safe_psql("postgres",
"SELECT test_shmem_register('test_shmem after startup', 20, 2);");
is($res, 1, 'attach after startup');
# If the size doesn't match when attaching, you get an error
my (undef, undef, $stderr) =
$node->psql("postgres",
"SELECT test_shmem_register('test_shmem after startup', 25, 3);");
like(
$stderr,
qr/ERROR: shared memory struct "test_shmem after startup" was created with different size: existing 20, requested 25/,
"attaching with different size fails");
# Test attaching with SHMEM_ATTACH_UNKNOWN_SIZE
$res =
$node->safe_psql("postgres",
"SELECT test_shmem_register('test_shmem after startup', -1, 4);");
is($res, 2, 'attach with SHMEM_ATTACH_UNKNOWN_SIZE');
# Cannot use SHMEM_ATTACH_UNKNOWN_SIZE with a segment that doesn't exist
(undef, undef, $stderr) =
$node->psql("postgres",
"SELECT test_shmem_register('test_shmem missing area', -1, 5);");
like(
$stderr,
qr/ERROR: cannot attach to shared memory struct "test_shmem missing area" because it does not exist/,
"unknown-size request for a nonexistent area fails");
$node->stop;
###
# Test a failure in initializing the shared memory area
###
SKIP:
{
skip "injection points not supported by this build",
skip "injection points not supported by this build", 2
if $ENV{enable_injection_points} ne 'yes';
$node->start;
$node->safe_psql("postgres", "CREATE EXTENSION injection_points;");
@@ -139,9 +140,11 @@ SKIP:
$node->stop;
}
###
# Test "out of shared memory" in an after-startup request
###
# Huge pages round up the main shared memory segment, which can leave more
# unused space than the request below. Disable them to make the test reliable.
$node->append_conf('postgresql.conf', 'huge_pages = off');
@@ -167,4 +170,59 @@ $session->query_safe("SELECT get_test_shmem_attach_count();");
$session->quit;
$node->stop;
###
# Test shmem allocations in single-user mode
###
SKIP:
{
# Skip the test on Windows, as single-user mode would fail on permission
# failure with privileged accounts.
skip 'single-user test is not supported by this platform', 4
if $windows_os;
my @command = (
'postgres', '--single', '-F',
'-c' => 'exit_on_error=true',
'-D' => $node->data_dir);
# The call to get_test_shmem_attach_count() loads the library and
# allocates the shmem area
my $queries = "SELECT get_test_shmem_attach_count();\n";
my $result = run_log([ @command, 'postgres' ], '<' => \$queries);
ok($result, "shmem area is initialized in single-user mode");
# Test allocate and attach requests in a single-user session
$queries = qq{
-- allocate
SELECT test_shmem_register('test_shmem after startup', 25, 1);
-- attach
SELECT test_shmem_register('test_shmem after startup', 25, 2);
-- attach with SHMEM_ATTACH_UNKNOWN_SIZE
SELECT test_shmem_register('test_shmem after startup', -1, 3);
};
$result = run_log([ @command, 'postgres' ], '<' => \$queries);
ok($result, "allocate and attach in single-user mode");
# Cannot use SHMEM_ATTACH_UNKNOWN_SIZE with a segment that doesn't exist.
# We tested this in the after-startup tests already, but test it at startup,
# in single-user mode, too.
my $startup_stderr;
$result = run_log(
[
@command,
'-c' => 'shared_preload_libraries=test_shmem',
'-c' => 'test_shmem.area_size=-1',
'postgres'
],
'<' => \$queries,
'2>' => \$startup_stderr);
ok(!$result,
"unknown-size request during single-user startup is rejected");
like(
$startup_stderr,
qr/SHMEM_ATTACH_UNKNOWN_SIZE cannot be used during startup/,
"error message on rejected unknown-size request");
}
done_testing();

View File

@@ -7,3 +7,7 @@
CREATE FUNCTION get_test_shmem_attach_count()
RETURNS pg_catalog.int4 STRICT
AS 'MODULE_PATHNAME' LANGUAGE C;
CREATE FUNCTION test_shmem_register(pg_catalog.text, pg_catalog.int8, pg_catalog.int4)
RETURNS pg_catalog.int4 STRICT
AS 'MODULE_PATHNAME' LANGUAGE C;

View File

@@ -20,6 +20,7 @@
#include "fmgr.h"
#include "miscadmin.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/injection_point.h"
@@ -129,3 +130,57 @@ get_test_shmem_attach_count(PG_FUNCTION_ARGS)
elog(ERROR, "shmem area not yet initialized");
PG_RETURN_INT32(TestShmem->attach_count);
}
/*
* Callback for test_shmem_register(). test_shmem_register() provides the
* options, we just pass them through to ShmemRequestStruct.
*/
static void
test_shmem_after_startup_request(void *arg)
{
ShmemStructOpts *opts = (ShmemStructOpts *) arg;
elog(LOG, "test_shmem_after_startup_request callback called");
ShmemRequestStructWithOpts(opts);
}
/*
* Allocate or attach to a shmem structure, with the caller-supplied name and
* size.
*
* The given integer 'new_value' is stored in the area, and the old value
* is returned.
*/
PG_FUNCTION_INFO_V1(test_shmem_register);
Datum
test_shmem_register(PG_FUNCTION_ARGS)
{
char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
int64 size = PG_GETARG_INT64(1);
int new_value = PG_GETARG_INT32(2);
int old_value;
int *attached = NULL;
ShmemStructOpts opts = {
.name = name,
.size = size,
.ptr = (void **) &attached,
};
ShmemCallbacks callbacks = {
.flags = SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP,
.request_fn = test_shmem_after_startup_request,
.opaque_arg = &opts,
};
RegisterShmemCallbacks(&callbacks);
if (attached == NULL)
elog(ERROR, "could not attach to shared memory");
old_value = *attached;
*attached = new_value;
PG_RETURN_INT32(old_value);
}