From 6553283560eb43c48c471e627b0ac982259d1ca9 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Thu, 6 Aug 2026 14:34:43 +0530 Subject: [PATCH 3/3] Reject legacy shmem allocation from shmem callbacks After-startup init and attach callbacks run with ShmemIndexLock held. ShmemInitStruct() takes the same lock, and LWLocks are not reentrant, so calling it from one of those callbacks waits for the calling process itself. ShmemInitHash() has the same problem through its call to ShmemInitStruct(). This is easy to do when porting code from shmem_startup_hook, where ShmemInitStruct() was the normal interface and no lock was held. In an assert build the misuse currently trips an unrelated-looking state assertion; in a production build it hangs indefinitely. Detect that ShmemIndexLock is already held and report the misuse. Document that all required areas must instead be requested from the request callback. Author: Ayush Tiwari --- doc/src/sgml/xfunc.sgml | 6 ++++++ src/backend/storage/ipc/shmem.c | 3 +++ .../modules/test_shmem/t/001_late_shmem_alloc.pl | 6 ++++++ src/test/modules/test_shmem/test_shmem.c | 15 +++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 2b8a11e7ad0..25e17ac0e98 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3742,6 +3742,12 @@ my_shmem_init(void *arg) lock (ShmemIndexLock), which prevents the race condition of two backends trying to initialize the memory area at the same time. + + The legacy ShmemInitStruct() and + ShmemInitHash() functions cannot be used from these + callbacks because they take the same lock. Request all required areas + from request_fn instead. + diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index c117955e49b..23be8817c9e 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -1097,6 +1097,9 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr) }; ShmemRequest request = {&options, SHMEM_KIND_STRUCT}; + if (LWLockHeldByMe(ShmemIndexLock)) + elog(ERROR, "cannot call ShmemInitStruct() while holding ShmemIndexLock"); + Assert(shmem_request_state == SRS_DONE || shmem_request_state == SRS_INITIALIZING || shmem_request_state == SRS_REQUESTING); 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 5915f060a0c..b4da7de86e9 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 @@ -66,6 +66,12 @@ is( $node->safe_psql( ), '0', 'a partly oversized batch creates no areas'); + +my ($legacy_ret, $legacy_out, $legacy_err) = + $node->psql('postgres', 'SELECT test_shmem_failure(3);'); +isnt($legacy_ret, 0, 'legacy allocation from an init callback is rejected'); +like($legacy_err, qr/cannot call ShmemInitStruct\(\) while holding ShmemIndexLock/, + 'legacy allocation reports a clear error instead of hanging'); $node->stop; ### diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c index 6adf2405270..da5090a9d51 100644 --- a/src/test/modules/test_shmem/test_shmem.c +++ b/src/test/modules/test_shmem/test_shmem.c @@ -39,6 +39,7 @@ static void test_shmem_request(void *arg); static void test_shmem_init(void *arg); static void test_shmem_attach(void *arg); static void test_shmem_failure_request(void *arg); +static void test_shmem_failure_init(void *arg); static const ShmemCallbacks TestShmemCallbacks = { .flags = SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP, @@ -50,6 +51,7 @@ static const ShmemCallbacks TestShmemCallbacks = { static const ShmemCallbacks TestShmemFailureCallbacks = { .flags = SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP, .request_fn = test_shmem_failure_request, + .init_fn = test_shmem_failure_init, }; static int failure_mode; @@ -114,11 +116,24 @@ test_shmem_failure_request(void *arg) .size = (Size) 1024 * 1024 * 1024, .ptr = &ptr1); break; + case 3: + ShmemRequestStruct(.name = "test_shmem legacy caller area", + .size = 1024, .ptr = &ptr1); + break; default: elog(ERROR, "unrecognized test_shmem failure mode: %d", failure_mode); } } +static void +test_shmem_failure_init(void *arg) +{ + bool found; + + if (failure_mode == 3) + (void) ShmemInitStruct("test_shmem legacy target area", 1024, &found); +} + void _PG_init(void) { -- 2.34.1