On 26/08/2026 08:59, Ayush Tiwari wrote:
This approach looks good to me overall. I built the v2 series with
assertions and injection points enabled, and all five tests passed.
I had one question about the lifetime of the options copy. It is still
allocated in TopMemoryContext, while the after-startup path no longer calls
pfree(request->options). The PG_FINALLY block clears
pending_shmem_requests, but could that leave the options allocated until
backend exit? The impact seems small, but perhaps the options should be
freed before clearing the list, or allocated in the same context as the
requests?
You're right. I changed it to use TopMemoryContext like you had it
originally. That seems more clear, after all.
One minor test nit:
- The comment says "A failure in the requesting shared memory", but the
injection point triggers in test_shmem_init(), so would "initializing
shared memory" be more accurate?
Yeah. I did some other cleanups in the test too, and merged the tests
into the commits with the code fixes. Here's a new version, if you want
to have one final look.
- Heikki
From 9c9a8d076b6c253a6f458cdad056057d6cee0153 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 26 Aug 2026 15:10:49 +0300
Subject: [PATCH v3 1/2] Fix backend state after a failed after-startup shmem
request
RegisterShmemCallbacks() left the backend in a bad state, if an error
occurred in the callbacks or if an allocation failed. Firstly,
'shmem_request_state' was left in wrong state, causing a subsequent
call to RegisterShmemCallbacks() to wrongly take the postmaster
startup codepath or assertion failures in some other functions.
Secondly, the 'pending_shmem_requests' list was not properly cleaned
up, causing a subsequent RegisterShmemCallbacks() to try to process
the stale, already-freed requests.
To fix, add a PG_TRY() block to clean those things up on error.
Author: Ayush Tiwari <[email protected]>
Reviewed-by: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAJTYsWVRRWH48=pcuao_2y4ap6m0qrmzxguffknrtdwk74l...@mail.gmail.com
Backpatch-through: 19
---
src/backend/storage/ipc/shmem.c | 72 +++++++++++++------
.../test_shmem/t/001_late_shmem_alloc.pl | 28 ++++++++
src/test/modules/test_shmem/test_shmem.c | 28 +++++++-
3 files changed, 104 insertions(+), 24 deletions(-)
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 98aaee00bfd..f88642e6b8b 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -158,7 +158,9 @@ static List *registered_shmem_callbacks;
/*
* In the shmem request phase, all the shmem areas requested with the
- * ShmemRequest*() functions are accumulated here.
+ * ShmemRequest*() functions are accumulated in the 'pending_shmem_requests'
+ * list. The List, the ShmemRequest structs, and the 'options' are all
+ * allocated in TopMemoryContext.
*/
typedef struct
{
@@ -166,7 +168,7 @@ typedef struct
ShmemRequestKind kind;
} ShmemRequest;
-static List *pending_shmem_requests;
+static List *pending_shmem_requests; /* List of ShmemRequests */
/*
* Per-process state machine, for sanity checking that we do things in the
@@ -274,6 +276,7 @@ typedef struct
static bool firstNumaTouch = true;
static void CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks);
+static void ProcessShmemRequestsAfterStartup(const ShmemCallbacks *callbacks);
static void InitShmemIndexEntry(ShmemRequest *request);
static bool AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok);
@@ -335,6 +338,7 @@ ShmemRequestStructWithOpts(const ShmemStructOpts *options)
void
ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
{
+ MemoryContext oldcontext;
ShmemRequest *request;
/* Check the options */
@@ -374,10 +378,12 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
}
/* Request looks valid, remember it */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
request = palloc_object(ShmemRequest);
request->options = options;
request->kind = kind;
pending_shmem_requests = lappend(pending_shmem_requests, request);
+ MemoryContextSwitchTo(oldcontext);
}
/*
@@ -903,26 +909,49 @@ RegisterShmemCallbacks(const ShmemCallbacks *callbacks)
static void
CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks)
{
- bool found_any;
- bool notfound_any;
-
Assert(shmem_request_state == SRS_DONE);
- shmem_request_state = SRS_REQUESTING;
-
- /*
- * Call the request callback first. The callback makes ShmemRequest*()
- * calls for each shmem area, adding them to pending_shmem_requests.
- */
Assert(pending_shmem_requests == NIL);
- if (callbacks->request_fn)
- callbacks->request_fn(callbacks->opaque_arg);
- shmem_request_state = SRS_AFTER_STARTUP_ATTACH_OR_INIT;
- if (pending_shmem_requests == NIL)
+ PG_TRY();
+ {
+ shmem_request_state = SRS_REQUESTING;
+
+ /*
+ * Call the request callback first. The callback makes
+ * ShmemRequest*() calls for each shmem area, adding them to
+ * pending_shmem_requests.
+ */
+ if (callbacks->request_fn)
+ callbacks->request_fn(callbacks->opaque_arg);
+
+ /* Process all the requests */
+ shmem_request_state = SRS_AFTER_STARTUP_ATTACH_OR_INIT;
+ if (pending_shmem_requests != NIL)
+ ProcessShmemRequestsAfterStartup(callbacks);
+ }
+ PG_FINALLY();
{
+ foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+ pfree(request->options);
+ list_free_deep(pending_shmem_requests);
+ pending_shmem_requests = NIL;
+
shmem_request_state = SRS_DONE;
- return;
}
+ PG_END_TRY();
+}
+
+static void
+ProcessShmemRequestsAfterStartup(const ShmemCallbacks *callbacks)
+{
+ bool found_any;
+ bool notfound_any;
+
+ /* There should be some requests to process */
+ Assert(pending_shmem_requests != NIL);
+
+ /* Caller manages the global state variable */
+ Assert(shmem_request_state == SRS_AFTER_STARTUP_ATTACH_OR_INIT);
/*
* Hold ShmemIndexLock while we allocate all the shmem entries and run all
@@ -940,7 +969,11 @@ CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks)
found_any = notfound_any = false;
foreach_ptr(ShmemRequest, request, pending_shmem_requests)
{
- if (hash_search(ShmemIndex, request->options->name, HASH_FIND, NULL))
+ ShmemIndexEnt *index_entry;
+
+ index_entry = (ShmemIndexEnt *)
+ hash_search(ShmemIndex, request->options->name, HASH_FIND, NULL);
+ if (index_entry)
found_any = true;
else
notfound_any = true;
@@ -958,11 +991,7 @@ CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks)
AttachShmemIndexEntry(request, false);
else
InitShmemIndexEntry(request);
-
- pfree(request->options);
}
- list_free_deep(pending_shmem_requests);
- pending_shmem_requests = NIL;
/* Finish by calling the appropriate subsystem-specific callback */
if (found_any)
@@ -977,7 +1006,6 @@ CallShmemCallbacksAfterStartup(const ShmemCallbacks *callbacks)
}
LWLockRelease(ShmemIndexLock);
- shmem_request_state = SRS_DONE;
}
/*
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 546d6a92abe..6ea409f3c63 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
@@ -78,5 +78,33 @@ else
);
}
+# clean up
$node->stop;
+$node->adjust_conf('postgresql.conf', "shared_preload_libraries", undef);
+
+###
+# Test "out of shared memory" in an after-startup request
+###
+$node->start;
+my $session = $node->background_psql('postgres', on_error_stop => 0);
+
+# make the request larger than the memory reserved for after-startup
+# requests.
+$session->query(q[SET test_shmem.area_size = '128kB';]);
+
+$session->query("SELECT get_test_shmem_attach_count();");
+like(
+ $session->{stderr},
+ qr/not enough shared memory/,
+ "an after-startup request larger than the reserve fails");
+
+# The server and the backend keep running. Since only one area was
+# requested, it gets cleaned up on allocation failure. Verify that a
+# request for a smaller area succeeds in the same session.
+$session->{stderr} = '';
+$session->query("SET test_shmem.area_size = default;");
+$session->query_safe("SELECT get_test_shmem_attach_count();");
+$session->quit;
+$node->stop;
+
done_testing();
diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c
index 9bd4012b435..231ad9a0027 100644
--- a/src/test/modules/test_shmem/test_shmem.c
+++ b/src/test/modules/test_shmem/test_shmem.c
@@ -20,20 +20,28 @@
#include "fmgr.h"
#include "miscadmin.h"
#include "storage/shmem.h"
+#include "utils/guc.h"
+#include "utils/injection_point.h"
PG_MODULE_MAGIC;
typedef struct TestShmemData
{
- int value;
bool initialized;
int attach_count;
+ char dummy_data[FLEXIBLE_ARRAY_MEMBER];
} TestShmemData;
static TestShmemData *TestShmem;
+#define MIN_TEST_AREA_BYTES sizeof(TestShmemData)
+#define DEFAULT_TEST_AREA_BYTES MIN_TEST_AREA_BYTES
+#define MAX_TEST_AREA_BYTES 1000000
+
static bool attached_or_initialized = false;
+static int test_shmem_area_size = MIN_TEST_AREA_BYTES;
+static bool test_shmem_guc_defined = false;
static void test_shmem_request(void *arg);
static void test_shmem_init(void *arg);
@@ -52,7 +60,7 @@ test_shmem_request(void *arg)
elog(LOG, "test_shmem_request callback called");
ShmemRequestStruct(.name = "test_shmem area",
- .size = sizeof(TestShmemData),
+ .size = test_shmem_area_size,
.ptr = (void **) &TestShmem);
}
@@ -86,6 +94,22 @@ void
_PG_init(void)
{
elog(LOG, "test_shmem module's _PG_init called");
+
+ if (!test_shmem_guc_defined)
+ {
+ DefineCustomIntVariable("test_shmem.area_size",
+ "Size of the shmem area to request.",
+ NULL,
+ &test_shmem_area_size,
+ DEFAULT_TEST_AREA_BYTES,
+ MIN_TEST_AREA_BYTES,
+ MAX_TEST_AREA_BYTES,
+ PGC_USERSET,
+ GUC_UNIT_BYTE,
+ NULL, NULL, NULL);
+ MarkGUCPrefixReserved("test_shmem");
+ test_shmem_guc_defined = true;
+ }
RegisterShmemCallbacks(&TestShmemCallbacks);
}
--
2.47.3
From 455b592a0db627322e1e8765bc26f8a288a20b35 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 26 Aug 2026 15:20:43 +0300
Subject: [PATCH v3 2/2] Track which shmem areas have been fully initialized
If SHMEM_CALLBACKS_ALLOW_AFTER_STARTUP is used to allocate shared
memory after startup, but the initialization fails half-way through,
the shmem area is left in an indeterminate state. Furthermore, if
multiple shmem areas are registered in one RegisterShmemCallbacks()
call, some might be allocated while others are not.
This commit adds an explicit 'initialized' flag to each shmem area.
We still leave behind an uninitialized area on error, but at least
they are now clearly marked, and you get a slightly nicer error
message if you try to re-register them. It'd be nice to clean up more
thoroughly and support actually retrying the allocations, but in
practice, the most likely reason for a shmem allocation or
initialization to fail is that you are out of shared memory and
retrying wouldn't help with that.
This isn't exactly a new problem, the old ShmemInitStruct() interface
had similar issues if the initialization code failed, or if you
allocated multiple structs and some allocations failed. It was just
left to the calling code to deal with it.
Author: Ayush Tiwari <[email protected]>
Reviewed-by: Ashutosh Bapat <[email protected]>
Discussion: https://www.postgresql.org/message-id/CAJTYsWVRRWH48=pcuao_2y4ap6m0qrmzxguffknrtdwk74l...@mail.gmail.com
Backpatch-through: 19
---
doc/src/sgml/xfunc.sgml | 5 +-
src/backend/storage/ipc/shmem.c | 67 +++++++++++++++++--
src/test/modules/test_shmem/Makefile | 3 +
src/test/modules/test_shmem/meson.build | 3 +
.../test_shmem/t/001_late_shmem_alloc.pl | 50 ++++++++++++--
src/test/modules/test_shmem/test_shmem.c | 3 +
6 files changed, 120 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 97f3cb625e2..a90dba0662f 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3740,7 +3740,10 @@ my_shmem_init(void *arg)
on whether the requested memory areas were already initialized by
another backend. The callbacks will be called while holding an internal
lock (ShmemIndexLock), which prevents the race condition of two backends
- trying to initialize the memory area at the same time.
+ trying to initialize the memory area at the same time. If the
+ allocation or initialization fails for any reason, the shared memory
+ areas are left in an abandoned state and any attempt to attach or
+ re-initialize them will fail until the server is restarted.
</para>
</sect3>
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index f88642e6b8b..f971ee24192 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -144,6 +144,8 @@
#include "utils/builtins.h"
#include "utils/tuplestore.h"
+typedef struct ShmemIndexEnt ShmemIndexEnt;
+
/*
* Registered callbacks.
*
@@ -166,6 +168,9 @@ typedef struct
{
ShmemStructOpts *options;
ShmemRequestKind kind;
+
+ /* InitShmemIndexEntry() sets this pointer when the area is allocated */
+ ShmemIndexEnt *index_entry;
} ShmemRequest;
static List *pending_shmem_requests; /* List of ShmemRequests */
@@ -264,12 +269,13 @@ static HTAB *ShmemIndex;
#define SHMEM_INDEX_ADDITIONAL_SIZE (128)
/* this is a hash bucket in the shmem index table */
-typedef struct
+typedef struct ShmemIndexEnt
{
char key[SHMEM_INDEX_KEYSIZE]; /* string name */
void *location; /* location in shared mem */
Size size; /* # bytes requested for the structure */
Size allocated_size; /* # bytes actually allocated */
+ bool initialized; /* has the init callback been run? */
} ShmemIndexEnt;
/* To get reliable results for NUMA inquiry we need to "touch pages" once */
@@ -382,6 +388,7 @@ ShmemRequestInternal(ShmemStructOpts *options, ShmemRequestKind kind)
request = palloc_object(ShmemRequest);
request->options = options;
request->kind = kind;
+ request->index_entry = NULL;
pending_shmem_requests = lappend(pending_shmem_requests, request);
MemoryContextSwitchTo(oldcontext);
}
@@ -441,10 +448,7 @@ ShmemInitRequested(void)
foreach_ptr(ShmemRequest, request, pending_shmem_requests)
{
InitShmemIndexEntry(request);
- pfree(request->options);
}
- list_free_deep(pending_shmem_requests);
- pending_shmem_requests = NIL;
/*
* Call the subsystem-specific init callbacks to finish initialization of
@@ -456,6 +460,15 @@ ShmemInitRequested(void)
callbacks->init_fn(callbacks->opaque_arg);
}
+ /* Now we can mark all the areas as initialized and free the requests */
+ foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+ {
+ request->index_entry->initialized = true;
+ pfree(request->options);
+ }
+ list_free_deep(pending_shmem_requests);
+ pending_shmem_requests = NIL;
+
shmem_request_state = SRS_DONE;
}
@@ -557,7 +570,12 @@ InitShmemIndexEntry(ShmemRequest *request)
index_entry->allocated_size = allocated_size;
index_entry->location = structPtr;
- /* Initialize depending on the kind of shmem area it is */
+ /*
+ * The area is considered fully initialized only after the subsystem's
+ * init callback has been called. For now, perform only basic
+ * initialization based on the kind of shmem area it is.
+ */
+ index_entry->initialized = false;
switch (request->kind)
{
case SHMEM_KIND_STRUCT:
@@ -571,6 +589,9 @@ InitShmemIndexEntry(ShmemRequest *request)
shmem_slru_init(structPtr, request->options);
break;
}
+
+ /* return the pointer to the entry to the caller */
+ request->index_entry = index_entry;
}
/*
@@ -600,6 +621,20 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
return false;
}
+ /*
+ * If it was previously allocated but not fully initialized, error out.
+ * There is currently no way of retrying or cleaning up an uninitialized
+ * entry, it just lingers until the server is shut down. But this can
+ * only happen when allocating areas after postmaster startup, and it's
+ * unlikely that you could successfully retry anyway. The most likely
+ * reason for failed initialization is that you are out of shared memory
+ * and retrying won't help with that.
+ */
+ if (!index_entry->initialized)
+ ereport(ERROR,
+ (errmsg("cannot attach to shared memory struct \"%s\" because it was not fully initialized",
+ request->options->name)));
+
/* Check that the size in the index matches the request */
if (index_entry->size != request->options->size &&
request->options->size != SHMEM_ATTACH_UNKNOWN_SIZE)
@@ -628,6 +663,8 @@ AttachShmemIndexEntry(ShmemRequest *request, bool missing_ok)
break;
}
+ request->index_entry = index_entry;
+
return true;
}
@@ -738,6 +775,7 @@ InitShmemAllocator(PGShmemHeader *seghdr)
result->size = ShmemAllocator->index_size;
result->allocated_size = ShmemAllocator->index_size;
result->location = ShmemAllocator->index;
+ result->initialized = true;
}
}
@@ -974,7 +1012,17 @@ ProcessShmemRequestsAfterStartup(const ShmemCallbacks *callbacks)
index_entry = (ShmemIndexEnt *)
hash_search(ShmemIndex, request->options->name, HASH_FIND, NULL);
if (index_entry)
+ {
+ /*
+ * Check for a half-initialized area. (See also similar check in
+ * AttachShmemIndexEntry())
+ */
+ if (!index_entry->initialized)
+ ereport(ERROR,
+ (errmsg("cannot attach to shared memory struct \"%s\" because it was not fully initialized",
+ request->options->name)));
found_any = true;
+ }
else
notfound_any = true;
}
@@ -1005,6 +1053,11 @@ ProcessShmemRequestsAfterStartup(const ShmemCallbacks *callbacks)
callbacks->init_fn(callbacks->opaque_arg);
}
+ foreach_ptr(ShmemRequest, request, pending_shmem_requests)
+ {
+ request->index_entry->initialized = true;
+ }
+
LWLockRelease(ShmemIndexLock);
}
@@ -1069,7 +1122,11 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr)
/* Initialize it if not found */
if (!*foundPtr)
+ {
InitShmemIndexEntry(&request);
+ /* no additional initialization needed */
+ request.index_entry->initialized = true;
+ }
LWLockRelease(ShmemIndexLock);
diff --git a/src/test/modules/test_shmem/Makefile b/src/test/modules/test_shmem/Makefile
index 2407f7462fe..fed8e29c8f5 100644
--- a/src/test/modules/test_shmem/Makefile
+++ b/src/test/modules/test_shmem/Makefile
@@ -2,6 +2,9 @@
PGFILEDESC = "test_shmem - test code for shmem allocations"
+EXTRA_INSTALL = src/test/modules/injection_points
+export enable_injection_points
+
MODULE_big = test_shmem
OBJS = \
$(WIN32RES) \
diff --git a/src/test/modules/test_shmem/meson.build b/src/test/modules/test_shmem/meson.build
index fb4bf328b8f..8f98f2c4e31 100644
--- a/src/test/modules/test_shmem/meson.build
+++ b/src/test/modules/test_shmem/meson.build
@@ -26,6 +26,9 @@ tests += {
'sd': meson.current_source_dir(),
'bd': meson.current_build_dir(),
'tap': {
+ 'env': {
+ 'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
+ },
'tests': [
't/001_late_shmem_alloc.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 6ea409f3c63..a7126ebce3f 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
@@ -7,17 +7,20 @@ 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.
+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
###
-my $node = PostgreSQL::Test::Cluster->new('main');
-$node->init;
$node->start;
-
-$node->safe_psql("postgres", "CREATE EXTENSION test_shmem;");
-
# Check that the attach counter is incremented on a new connection
my $attach_count1 =
$node->safe_psql("postgres", "SELECT get_test_shmem_attach_count();");
@@ -25,6 +28,7 @@ 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;
###
@@ -82,6 +86,42 @@ else
$node->stop;
$node->adjust_conf('postgresql.conf', "shared_preload_libraries", undef);
+###
+# Test a failure in initializing the shared memory area
+###
+SKIP:
+{
+ skip "injection points not supported by this build",
+ if $ENV{enable_injection_points} ne 'yes';
+ $node->start;
+ $node->safe_psql("postgres", "CREATE EXTENSION injection_points;");
+ $node->safe_psql("postgres",
+ "SELECT injection_points_attach('test-shmem-init', 'error');");
+
+ # Try to load the extension library. It will hit the injected
+ # error in the init callback.
+ my (undef, undef, $stderr) =
+ $node->psql("postgres", "SELECT get_test_shmem_attach_count();");
+ like(
+ $stderr,
+ qr/error triggered for injection point test-shmem-init/,
+ "failure in initialization is reported");
+ $node->safe_psql("postgres",
+ "SELECT injection_points_detach('test-shmem-init');");
+
+ # The error leaves the shared memory area in a broken state.
+ # Attempting to initialize or attach it again will fail, until the
+ # server is restarted.
+ (undef, undef, $stderr) =
+ $node->psql("postgres", "SELECT get_test_shmem_attach_count();");
+ like(
+ $stderr,
+ qr/cannot attach to shared memory/,
+ "post-init extension creation fails");
+
+ $node->stop;
+}
+
###
# Test "out of shared memory" in an after-startup request
###
diff --git a/src/test/modules/test_shmem/test_shmem.c b/src/test/modules/test_shmem/test_shmem.c
index 231ad9a0027..6cf47dc8968 100644
--- a/src/test/modules/test_shmem/test_shmem.c
+++ b/src/test/modules/test_shmem/test_shmem.c
@@ -68,6 +68,9 @@ static void
test_shmem_init(void *arg)
{
elog(LOG, "init callback called");
+
+ INJECTION_POINT("test-shmem-init", NULL);
+
if (TestShmem->initialized)
elog(ERROR, "shmem area already initialized");
TestShmem->initialized = true;
--
2.47.3