On 21/09/2026 21:56, Bharath Rupireddy wrote:
I found a crash with WAL_DEBUG related code on HEAD. After commit
9b5acad3f40f converted XLOGShmemInit() to the new shared memory
allocation API, the memory context used by wal_debug is created in the
initialization callback, which runs only in the postmaster or in a
standalone backend. In EXEC_BACKEND builds, child processes run the
attach callback instead, and that one was not taught to create the
context. Previously, XLOGShmemInit() itself ran in every child and
created the context before returning early.
As a result, with WAL_DEBUG compiled in and wal_debug turned on, a
child process has nowhere to allocate the description of the record it
is about to insert, and crashes on the first WAL insertion.
Reproduction steps at [1].
Good catch, thanks.
I propose to fix this by creating the context from both callbacks, so
that every process ends up with one, as before. Please find the
attached patch. I think this fix needs to be back-patched through
PG19, where the above commit went in.
Hmm, it's a bit ugly to initialize what is a purely backend-private
thing in the ShmemInit/Attach() functions. It was expedient in the past,
because the ShmemInit() functions happened to run at the right times,
but initializing the memory context was never related to shared memory
in any way. I propose the attached, which calls the InitWalDebug()
function from InitXLogInsert() instead.
Searching for similar cases where we do backend-private initialization
that is not related to shared memory in the shmem callbacks, I found
BufferManagerShmemAttach():
static void
BufferManagerShmemAttach(void *arg)
{
/* Initialize per-backend file flush context */
WritebackContextInit(&BackendWritebackContext,
&backend_flush_after);
}
There's no bug here, but I propose that we also move that to
InitBufferManagerAccess(), per the second attached patch.
- Heikki
From b6a1a71520716a0b8f1c64c9513745d8d4f8ef8c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Oct 2026 12:22:07 +0300
Subject: [PATCH v2 1/2] Fix crash with wal_debug in EXEC_BACKEND child
processes
Converting xlog.c to the new shared memory allocation API left the
creation of the memory context used by wal_debug in the
XLOGShmemInit() callback, which now only runs in the postmaster or in
a standalone backend. Previously, XLOGShmemInit() ran in every child.
As a result, walDebugCtx was left uninitialized in child processes in
EXEC_BACKEND mode, and with wal_debug turned on, the backend crashed
on first WAL insertion.
Fix by moving the walDebugCtx initialization to a separate function
that is called in each backend that wishes to create WAL records. (It
cannot be created lazily on first, because WAL insertion happens in a
critical section, and you cannot create a new memory context in a
critical section.)
Oversight in 9b5acad3f40f.
Co-authored-by:: Bharath Rupireddy <[email protected]>
Discussion: https://postgr.es/m/<message-id>
Backpatch-through: 19
---
src/backend/access/transam/xlog.c | 40 +++++++++++++++----------
src/backend/access/transam/xloginsert.c | 5 ++++
src/include/access/xlog.h | 3 ++
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index afbe068cd0a..63d214782da 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5570,22 +5570,6 @@ XLOGShmemInit(void *arg)
char *allocptr;
int i;
-#ifdef WAL_DEBUG
-
- /*
- * Create a memory context for WAL debugging that's exempt from the normal
- * "no pallocs in critical section" rule. Yes, that can lead to a PANIC if
- * an allocation fails, but wal_debug is not for production use anyway.
- */
- if (walDebugCxt == NULL)
- {
- walDebugCxt = AllocSetContextCreate(TopMemoryContext,
- "WAL Debug",
- ALLOCSET_DEFAULT_SIZES);
- MemoryContextAllowInCriticalSection(walDebugCxt, true);
- }
-#endif
-
memset(XLogCtl, 0, sizeof(XLogCtlData));
/*
@@ -5669,6 +5653,30 @@ XLOGShmemAttach(void *arg)
WALInsertLocks = XLogCtl->Insert.WALInsertLocks;
}
+/*
+ * Initialize process-local state needed by wal_debug.
+ *
+ * This must be called before XLogInsertRecord().
+ */
+#ifdef WAL_DEBUG
+void
+InitWalDebug(void)
+{
+ /*
+ * Create a memory context for WAL debugging that's exempt from the normal
+ * "no pallocs in critical section" rule. Yes, that can lead to a PANIC if
+ * an allocation fails, but wal_debug is not for production use anyway.
+ */
+ if (walDebugCxt == NULL)
+ {
+ walDebugCxt = AllocSetContextCreate(TopMemoryContext,
+ "WAL Debug",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextAllowInCriticalSection(walDebugCxt, true);
+ }
+}
+#endif
+
/*
* This func must be called ONCE on system install. It creates pg_control
* and the initial XLOG segment.
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index c9aff944a2e..2195df85b10 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -1437,4 +1437,9 @@ InitXLogInsert(void)
if (hdr_scratch == NULL)
hdr_scratch = MemoryContextAllocZero(xloginsert_cxt,
HEADER_SCRATCH_SIZE);
+
+ /* Extra initialization for wal_debug */
+#ifdef WAL_DEBUG
+ InitWalDebug();
+#endif
}
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 7a590b7e1ea..2bf5714b51e 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -234,6 +234,9 @@ extern bool XLogBackgroundFlush(void);
extern bool XLogNeedsFlush(XLogRecPtr record);
extern int XLogFileInit(XLogSegNo logsegno, TimeLineID logtli);
extern int XLogFileOpen(XLogSegNo segno, TimeLineID tli);
+#ifdef WAL_DEBUG
+extern void InitWalDebug(void);
+#endif
extern void CheckXLogRemoved(XLogSegNo segno, TimeLineID tli);
extern XLogSegNo XLogGetLastRemovedSegno(void);
--
2.47.3
From 383f8bb806c5925c21832be2abead6b136ebcf2f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Oct 2026 13:00:43 +0300
Subject: [PATCH v2 2/2] refactor: Move BackendWritebackContext initialization
BackendWritebackContext is a purely per-process thing, so it feels
wrong to initialize it in the shmem init/attach functions. Move the
initialization to InitBufferManagerAccess() instead.
---
src/backend/storage/buffer/buf_init.c | 15 ---------------
src/backend/storage/buffer/bufmgr.c | 6 ++++++
src/include/storage/buf_internals.h | 1 -
3 files changed, 6 insertions(+), 16 deletions(-)
diff --git a/src/backend/storage/buffer/buf_init.c b/src/backend/storage/buffer/buf_init.c
index 1407c930c56..9c5f2449cf3 100644
--- a/src/backend/storage/buffer/buf_init.c
+++ b/src/backend/storage/buffer/buf_init.c
@@ -24,17 +24,14 @@
BufferDescPadded *BufferDescriptors;
char *BufferBlocks;
ConditionVariableMinimallyPadded *BufferIOCVArray;
-WritebackContext BackendWritebackContext;
CkptSortItem *CkptBufferIds;
static void BufferManagerShmemRequest(void *arg);
static void BufferManagerShmemInit(void *arg);
-static void BufferManagerShmemAttach(void *arg);
const ShmemCallbacks BufferManagerShmemCallbacks = {
.request_fn = BufferManagerShmemRequest,
.init_fn = BufferManagerShmemInit,
- .attach_fn = BufferManagerShmemAttach,
};
/*
@@ -138,16 +135,4 @@ BufferManagerShmemInit(void *arg)
proclist_init(&buf->lock_waiters);
ConditionVariableInit(BufferDescriptorGetIOCV(buf));
}
-
- /* Initialize per-backend file flush context */
- WritebackContextInit(&BackendWritebackContext,
- &backend_flush_after);
-}
-
-static void
-BufferManagerShmemAttach(void *arg)
-{
- /* Initialize per-backend file flush context */
- WritebackContextInit(&BackendWritebackContext,
- &backend_flush_after);
}
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5c82865a084..f81c7732e68 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -270,6 +270,8 @@ static int PrivateRefCountEntryLast = -1;
static uint32 MaxProportionalPins;
+static WritebackContext BackendWritebackContext;
+
static void ReservePrivateRefCountEntry(void);
static PrivateRefCountEntry *NewPrivateRefCountEntry(Buffer buffer);
static PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool do_move);
@@ -4252,6 +4254,10 @@ InitBufferManagerAccess(void)
PrivateRefCountHash = refcount_create(CurrentMemoryContext, 100, NULL);
+ /* Initialize per-backend file flush context */
+ WritebackContextInit(&BackendWritebackContext,
+ &backend_flush_after);
+
/*
* AtProcExit_Buffers needs LWLock access, and thereby has to be called at
* the corresponding phase of backend shutdown.
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index e4ff5619b79..726ab75dfe9 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -412,7 +412,6 @@ typedef struct WritebackContext
/* in buf_init.c */
extern PGDLLIMPORT BufferDescPadded *BufferDescriptors;
extern PGDLLIMPORT ConditionVariableMinimallyPadded *BufferIOCVArray;
-extern PGDLLIMPORT WritebackContext BackendWritebackContext;
/* in localbuf.c */
extern PGDLLIMPORT BufferDesc *LocalBufferDescriptors;
--
2.47.3