From 5fe1130d412392caa7a22fa75536c58e320fa9ec Mon Sep 17 00:00:00 2001
From: Atsushi Torikoshi <torikoshia@oss.nttdata.com>
Date: Mon, 19 Oct 2026 16:29:56 +0900
Subject: [PATCH v61 2/2] Add request IDs and manage query plan logging
 requests

Return a positive request ID from pg_log_query_plan() and include it in
server log messages so callers can associate a request with its output.
Generate IDs using a server-wide signed 64-bit counter in the range
1 through PG_INT64_MAX, wrapping to 1 after reaching the upper limit.

Since requests are processed asynchronously, store each request in a
shared-memory slot assigned to the target backend so it can retrieve the
request ID after the calling function returns. Allow only one outstanding
request per backend to prevent its ID from being overwritten before
processing completes.
---
 doc/src/sgml/func/func-admin.sgml             |   6 +-
 src/backend/access/transam/xact.c             |   6 +-
 src/backend/commands/explain_running.c        | 359 ++++++++++++++++--
 src/backend/executor/execMain.c               |   7 +-
 src/backend/utils/init/postinit.c             |   8 +
 src/include/catalog/pg_proc.dat               |   2 +-
 src/include/commands/explain_running.h        |   4 +
 src/include/storage/subsystemlist.h           |   1 +
 .../test_misc/t/016_pg_log_query_plan.pl      |   9 +-
 src/test/regress/expected/misc_functions.out  |   6 +-
 src/test/regress/sql/misc_functions.sql       |   2 +-
 11 files changed, 365 insertions(+), 45 deletions(-)

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 462378dc1f0..cde30c13fda 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -190,7 +190,7 @@
          <primary>pg_log_query_plan</primary>
         </indexterm>
         <function>pg_log_query_plan</function> ( <parameter>pid</parameter> <type>integer</type> )
-        <returnvalue>boolean</returnvalue>
+        <returnvalue>bigint</returnvalue>
        </para>
        <para>
         Requests to log the plan of the query currently running on the
@@ -201,6 +201,10 @@
         for more information), but will not be sent to the client
         regardless of <xref linkend="guc-client-min-messages"/>.
        </para>
+       <para>
+        Each accepted request is assigned a positive request identifier, which
+        is returned by the function and is included in the server log message.
+       </para>
        <para>
         The plan is logged when the target backend next reaches a
         point where the running plan can be inspected.  If the backend
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 9fd49f8160b..0445e8a554f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/explain_running.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "common/pg_prng.h"
@@ -2993,8 +2994,9 @@ AbortTransaction(void)
 	/* Reset snapshot export state. */
 	SnapBuildResetExportedSnapshotState();
 
-	/* Reset current QueryDesc. */
+	/* Discard the current QueryDesc and any unfinished plan logging request. */
 	SetCurrentQueryDesc(NULL);
+	AbortLogQueryPlanRequest();
 
 	/*
 	 * If this xact has started any unfinished parallel operation, clean up
@@ -5412,8 +5414,10 @@ AbortSubTransaction(void)
 	 * Reset current QueryDesc. Note that even after this reset, it's still
 	 * possible to obtain the parent transaction's query plans, since they are
 	 * preserved in standard_ExecutorRun().
+	 * Discard any unfinished plan logging request as well.
 	 */
 	SetCurrentQueryDesc(NULL);
+	AbortLogQueryPlanRequest();
 
 	/*
 	 * No need for SnapBuildResetExportedSnapshotState() here, snapshot
diff --git a/src/backend/commands/explain_running.c b/src/backend/commands/explain_running.c
index 6af2cf214a6..cd155b96937 100644
--- a/src/backend/commands/explain_running.c
+++ b/src/backend/commands/explain_running.c
@@ -21,16 +21,270 @@
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/procsignal.h"
+#include "storage/shmem.h"
+#include "storage/spin.h"
+#include "storage/subsystems.h"
 #include "utils/backend_status.h"
 #include "utils/injection_point.h"
 
-/* Is plan node wrapping for query plan logging currently in progress? */
-static bool WrapNodesInProgress = false;
+typedef int64 LogQueryPlanRequestId;
+
+typedef struct LogQueryPlanRequestIdControl
+{
+	slock_t		mutex;
+	LogQueryPlanRequestId next_request_id;
+} LogQueryPlanRequestIdControl;
+
+typedef enum LogQueryPlanRequestState
+{
+	LOG_QUERY_PLAN_REQUEST_IDLE = 0,
+	LOG_QUERY_PLAN_REQUEST_PENDING,	/* Request registered but not yet taken by target backend */
+	LOG_QUERY_PLAN_REQUEST_PROCESSING,	/* Request taken by target backend; processing in progress */
+} LogQueryPlanRequestState;
+
+typedef struct LogQueryPlanRequest
+{
+	LogQueryPlanRequestId request_id;
+	pid_t		target_pid;
+	/* Future versions may add fields here to carry EXPLAIN options. */
+} LogQueryPlanRequest;
+
+typedef struct LogQueryPlanRequestSlot
+{
+	slock_t		mutex;
+	LogQueryPlanRequestState state;
+	LogQueryPlanRequest request;
+} LogQueryPlanRequestSlot;
+
+static LogQueryPlanRequestSlot *LogQueryPlanRequestSlots;
+static LogQueryPlanRequestIdControl *LogQueryPlanRequestIdCtl;
+
+static void LogQueryPlanShmemRequest(void *arg);
+static void LogQueryPlanShmemInit(void *arg);
+static void LogQueryPlanRequestSlotCleanup(int code, Datum arg);
+
+const ShmemCallbacks LogQueryPlanShmemCallbacks = {
+	.request_fn = LogQueryPlanShmemRequest,
+	.init_fn = LogQueryPlanShmemInit,
+};
 
 static bool wrapExecProcNode(PlanState *ps, void *context);
+static LogQueryPlanRequestId GenerateLogQueryPlanRequestId(void);
+static bool ReserveLogQueryPlanRequest(ProcNumber proc_number, pid_t pid,
+									   LogQueryPlanRequestId request_id);
+static void RollbackLogQueryPlanRequest(ProcNumber proc_number, pid_t pid,
+										LogQueryPlanRequestId request_id);
+static LogQueryPlanRequest *TakePendingLogQueryPlanRequest(void);
+static LogQueryPlanRequest *GetProcessingLogQueryPlanRequest(void);
+static void FinishLogQueryPlanRequest(void);
+
+static void
+LogQueryPlanShmemRequest(void *arg)
+{
+	ShmemRequestStruct(.name = "Log Query Plan Request ID",
+					   .size = sizeof(LogQueryPlanRequestIdControl),
+					   .ptr = (void **) &LogQueryPlanRequestIdCtl,
+		);
+
+	ShmemRequestStruct(.name = "Log Query Plan Request Slots",
+					   .size = mul_size(sizeof(LogQueryPlanRequestSlot), MaxBackends),
+					   .ptr = (void **) &LogQueryPlanRequestSlots,
+		);
+}
+
+static void
+LogQueryPlanShmemInit(void *arg)
+{
+	SpinLockInit(&LogQueryPlanRequestIdCtl->mutex);
+	LogQueryPlanRequestIdCtl->next_request_id = 1;
+
+	for (int i = 0; i < MaxBackends; i++)
+	{
+		LogQueryPlanRequestSlot *slot = &LogQueryPlanRequestSlots[i];
+
+		SpinLockInit(&slot->mutex);
+		slot->state = LOG_QUERY_PLAN_REQUEST_IDLE;
+	}
+}
+
+/*
+ * Initialize the request slot assigned to a newly-started process.
+ */
+void
+LogQueryPlanRequestSlotInit(void)
+{
+	LogQueryPlanRequestSlot *slot;
+
+	Assert(MyBackendType == B_BACKEND);
+	slot = &LogQueryPlanRequestSlots[MyProcNumber];
+
+	SpinLockAcquire(&slot->mutex);
+	slot->state = LOG_QUERY_PLAN_REQUEST_IDLE;
+	SpinLockRelease(&slot->mutex);
+
+	on_shmem_exit(LogQueryPlanRequestSlotCleanup, Int32GetDatum(MyProcNumber));
+}
+
+/*
+ * Release a request slot when its process exits.
+ */
+static void
+LogQueryPlanRequestSlotCleanup(int code, Datum arg)
+{
+	ProcNumber	proc_number = DatumGetInt32(arg);
+	LogQueryPlanRequestSlot *slot;
+
+	Assert(proc_number >= 0 && proc_number < MaxBackends);
+	slot = &LogQueryPlanRequestSlots[proc_number];
+
+	SpinLockAcquire(&slot->mutex);
+	slot->state = LOG_QUERY_PLAN_REQUEST_IDLE;
+	SpinLockRelease(&slot->mutex);
+}
+
+/*
+ * Generate an opaque request identifier from a shared counter.
+ *
+ * Keep identifiers positive and representable as SQL bigint, wrapping to 1
+ * after PG_INT64_MAX.  Failed requests consume identifiers, too.
+ */
+static LogQueryPlanRequestId
+GenerateLogQueryPlanRequestId(void)
+{
+	LogQueryPlanRequestId id;
+
+	SpinLockAcquire(&LogQueryPlanRequestIdCtl->mutex);
+	id = LogQueryPlanRequestIdCtl->next_request_id;
+	LogQueryPlanRequestIdCtl->next_request_id =
+		(id == PG_INT64_MAX) ? 1 : id + 1;
+	SpinLockRelease(&LogQueryPlanRequestIdCtl->mutex);
+
+	return id;
+}
+
+/*
+ * Register a pending request in the target process's slot if it is idle.
+ * Return true on success, or false if a request is already pending or being
+ * processed.
+ */
+static bool
+ReserveLogQueryPlanRequest(ProcNumber proc_number, pid_t pid,
+						   LogQueryPlanRequestId request_id)
+{
+	LogQueryPlanRequestSlot *slot;
+	bool		reserved = false;
+
+	Assert(proc_number >= 0 && proc_number < MaxBackends);
+	slot = &LogQueryPlanRequestSlots[proc_number];
+
+	SpinLockAcquire(&slot->mutex);
+	if (slot->state == LOG_QUERY_PLAN_REQUEST_IDLE)
+	{
+		slot->request.request_id = request_id;
+		slot->request.target_pid = pid;
+		slot->state = LOG_QUERY_PLAN_REQUEST_PENDING;
+		reserved = true;
+	}
+	SpinLockRelease(&slot->mutex);
+
+	return reserved;
+}
+
+/*
+ * Undo a reservation after signal delivery fails, but only if the slot still
+ * contains the same pending request.  Leave requests already taken by the
+ * target process or registered by another caller untouched.
+ */
+static void
+RollbackLogQueryPlanRequest(ProcNumber proc_number, pid_t pid,
+							LogQueryPlanRequestId request_id)
+{
+	LogQueryPlanRequestSlot *slot;
+
+	Assert(proc_number >= 0 && proc_number < MaxBackends);
+	slot = &LogQueryPlanRequestSlots[proc_number];
+
+	SpinLockAcquire(&slot->mutex);
+	if (slot->state == LOG_QUERY_PLAN_REQUEST_PENDING &&
+		slot->request.target_pid == pid &&
+		slot->request.request_id == request_id)
+		slot->state = LOG_QUERY_PLAN_REQUEST_IDLE;
+	SpinLockRelease(&slot->mutex);
+}
+
+/*
+ * Take a pending request and set its state to PROCESSING, or return NULL if
+ * none exists.
+ */
+static LogQueryPlanRequest *
+TakePendingLogQueryPlanRequest(void)
+{
+	LogQueryPlanRequestSlot *slot;
+	LogQueryPlanRequest *request = NULL;
+
+	slot = &LogQueryPlanRequestSlots[MyProcNumber];
+
+	SpinLockAcquire(&slot->mutex);
+	if (slot->state == LOG_QUERY_PLAN_REQUEST_PENDING &&
+		slot->request.target_pid == MyProcPid)
+	{
+		slot->state = LOG_QUERY_PLAN_REQUEST_PROCESSING;
+		request = &slot->request;
+	}
+	else
+		/* Clear under the lock so a new request's notification is not lost. */
+		LogQueryPlanPending = false;
+	SpinLockRelease(&slot->mutex);
+
+	return request;
+}
+
+/*
+ * Return the processing request, or return NULL if none exists.
+ */
+static LogQueryPlanRequest *
+GetProcessingLogQueryPlanRequest(void)
+{
+	LogQueryPlanRequestSlot *slot = &LogQueryPlanRequestSlots[MyProcNumber];
+	LogQueryPlanRequest *request = NULL;
+
+	SpinLockAcquire(&slot->mutex);
+	if (slot->state == LOG_QUERY_PLAN_REQUEST_PROCESSING)
+		request = &slot->request;
+	SpinLockRelease(&slot->mutex);
+
+	return request;
+}
+
+static void
+FinishLogQueryPlanRequest(void)
+{
+	LogQueryPlanRequestSlot *slot = &LogQueryPlanRequestSlots[MyProcNumber];
+
+	SpinLockAcquire(&slot->mutex);
+	/* Clear the notification before allowing another request to be registered. */
+	LogQueryPlanPending = false;
+	slot->state = LOG_QUERY_PLAN_REQUEST_IDLE;
+	SpinLockRelease(&slot->mutex);
+}
+
+/*
+ * Discard any unfinished request when a transaction or subtransaction aborts.
+ * Do not allocate or report errors here: the original error must be preserved.
+ */
+void
+AbortLogQueryPlanRequest(void)
+{
+	/* Only client backends use query plan logging request slots. */
+	if (MyBackendType != B_BACKEND)
+		return;
+
+	FinishLogQueryPlanRequest();
+}
 
 /*
  * Wrap all the underlying ExecProcNode with ExecProcNodeFirst.
@@ -71,6 +325,7 @@ LogQueryPlan(void)
 	MemoryContext cxt;
 	MemoryContext old_cxt;
 	QueryDesc  *queryDesc;
+	LogQueryPlanRequest *request;
 
 	/*
 	 * Current QueryDesc is valid only during standard_ExecutorRun. However,
@@ -81,12 +336,15 @@ LogQueryPlan(void)
 
 	if (queryDesc == NULL)
 	{
-		ereport(LOG_SERVER_ONLY,
-				errmsg("query plan logging was requested but there was no opportunity to do it"));
-		LogQueryPlanPending = false;
+		FinishLogQueryPlanRequestWithoutPlan(0);
 		return;
 	}
 
+	request = GetProcessingLogQueryPlanRequest();
+
+	if (request == NULL)
+		return;
+
 	cxt = AllocSetContextCreate(CurrentMemoryContext,
 								"log_query_plan temporary context",
 								ALLOCSET_DEFAULT_SIZES);
@@ -100,19 +358,49 @@ LogQueryPlan(void)
 		ExplainStringAssemble(es, queryDesc, es->format, false, -1);
 
 		ereport(LOG_SERVER_ONLY,
-				errmsg("query and its plan for queryid " INT64_FORMAT " running on backend with PID %d are:\n%s",
-					   queryDesc->plannedstmt->queryId, MyProcPid,
+				errmsg("query and its plan for queryid " INT64_FORMAT " requested by request ID " INT64_FORMAT " running on backend with PID %d are:\n%s",
+					   queryDesc->plannedstmt->queryId,
+					   request->request_id, MyProcPid,
 					   es->str->data));
 	}
 	PG_FINALLY();
 	{
 		MemoryContextSwitchTo(old_cxt);
 		MemoryContextDelete(cxt);
-		LogQueryPlanPending = false;
+		FinishLogQueryPlanRequest();
 	}
 	PG_END_TRY();
 }
 
+/*
+ * Finish a request without logging its plan, and log the queryid and requestid if possible.
+ */
+void
+FinishLogQueryPlanRequestWithoutPlan(uint64 query_id)
+{
+	LogQueryPlanRequestSlot *slot = &LogQueryPlanRequestSlots[MyProcNumber];
+	LogQueryPlanRequestId request_id = 0;
+
+	SpinLockAcquire(&slot->mutex);
+	request_id = slot->request.request_id;
+	/* Clear the notification before allowing another request to be registered. */
+	LogQueryPlanPending = false;
+	slot->state = LOG_QUERY_PLAN_REQUEST_IDLE;
+	SpinLockRelease(&slot->mutex);
+
+	// request_id should have been set when the request was made.
+	Assert(request_id != 0);
+
+	if (query_id != 0)
+		ereport(LOG_SERVER_ONLY,
+				errmsg("query plan logging request " INT64_FORMAT " could not be completed because there was no opportunity to log the plan for queryid " INT64_FORMAT,
+					   request_id, query_id));
+	else
+		ereport(LOG_SERVER_ONLY,
+				errmsg("query plan logging request " INT64_FORMAT " could not be completed because there was no opportunity to log the plan",
+					   request_id));
+}
+
 /*
  * Process the request for logging query plan at CHECK_FOR_INTERRUPTS().
  *
@@ -125,27 +413,25 @@ LogQueryPlan(void)
 void
 ProcessLogQueryPlanInterrupt(void)
 {
-	QueryDesc  *querydesc = GetCurrentQueryDesc();
+	QueryDesc  *querydesc;
+
+	/* Exit immediately if processing a request is already in progress. */
+	if (GetProcessingLogQueryPlanRequest() != NULL)
+		return;
+
+	/* Exit immediately when there is no pending request for this backend. */
+	if (TakePendingLogQueryPlanRequest() == NULL)
+		return;
+
+	querydesc = GetCurrentQueryDesc();
 
 	/* If current query has already finished, we can do nothing but exit */
 	if (querydesc == NULL)
 	{
-		ereport(LOG_SERVER_ONLY,
-				errmsg("query plan logging was requested but there was no opportunity to do it"));
-		LogQueryPlanPending = false;
+		FinishLogQueryPlanRequestWithoutPlan(0);
 		return;
 	}
 
-	/*
-	 * Exit immediately if wrapping plan is already in progress. This prevents
-	 * recursive calls, which could occur if logging is requested repeatedly
-	 * and rapidly, potentially leading to infinite recursion and crash.
-	 */
-	if (WrapNodesInProgress)
-		return;
-
-	WrapNodesInProgress = true;
-
 	PG_TRY();
 	{
 		INJECTION_POINT("log-query-interrupt", NULL);
@@ -156,9 +442,10 @@ ProcessLogQueryPlanInterrupt(void)
 		 */
 		(void) wrapExecProcNode(querydesc->planstate, NULL);
 	}
-	PG_FINALLY();
+	PG_CATCH();
 	{
-		WrapNodesInProgress = false;
+		FinishLogQueryPlanRequest();
+		PG_RE_THROW();
 	}
 	PG_END_TRY();
 }
@@ -176,7 +463,9 @@ pg_log_query_plan(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
+	ProcNumber	procNumber;
 	PgBackendStatus *be_status = NULL;
+	LogQueryPlanRequestId request_id;
 
 	proc = BackendPidGetProc(pid);
 
@@ -191,22 +480,34 @@ pg_log_query_plan(PG_FUNCTION_ARGS)
 		 */
 		ereport(WARNING,
 				(errmsg("PID %d is not a PostgreSQL backend process", pid)));
-		PG_RETURN_BOOL(false);
+		PG_RETURN_NULL();
 	}
 
 	if (be_status->st_backendType != B_BACKEND)
 	{
 		ereport(WARNING,
 				(errmsg("PID %d is not a PostgreSQL client backend process", pid)));
-		PG_RETURN_BOOL(false);
+		PG_RETURN_NULL();
+	}
+
+	procNumber = GetNumberFromPGProc(proc);
+	request_id = GenerateLogQueryPlanRequestId();
+
+	if (!ReserveLogQueryPlanRequest(procNumber, pid, request_id))
+	{
+		ereport(WARNING,
+				(errmsg("query plan request for PID %d is already pending", pid)));
+		PG_RETURN_NULL();
 	}
 
-	if (SendProcSignal(pid, PROCSIG_LOG_QUERY_PLAN, proc->vxid.procNumber) < 0)
+	if (SendProcSignal(pid, PROCSIG_LOG_QUERY_PLAN, procNumber) < 0)
 	{
+		/* The signal was not delivered, so undo our reservation if possible. */
+		RollbackLogQueryPlanRequest(procNumber, pid, request_id);
 		ereport(WARNING,
 				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
+		PG_RETURN_NULL();
 	}
 
-	PG_RETURN_BOOL(true);
+	PG_RETURN_INT64(request_id);
 }
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 970f02ae4f5..a747cfeccca 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -414,12 +414,7 @@ standard_ExecutorRun(QueryDesc *queryDesc,
 	 * execution on the same session.
 	 */
 	if (LogQueryPlanPending)
-	{
-		ereport(LOG_SERVER_ONLY,
-				(errmsg("query plan logging was requested but there was no opportunity to do it for queryid " INT64_FORMAT,
-						queryDesc->plannedstmt->queryId)));
-		LogQueryPlanPending = false;
-	}
+		FinishLogQueryPlanRequestWithoutPlan(queryDesc->plannedstmt->queryId);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 8b6ea195eca..4cb0e292194 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
+#include "commands/explain_running.h"
 #include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
@@ -733,6 +734,13 @@ InitPostgres(const char *in_dbname, Oid dboid,
 
 	elog(DEBUG3, "InitPostgres");
 
+	/*
+	 * Initialize the query plan request slot before InitProcessPhase2()
+	 * exposes me to other backends that can install requests.
+	 */
+	if (MyBackendType == B_BACKEND)
+		LogQueryPlanRequestSlotInit();
+
 	/*
 	 * Add my PGPROC struct to the ProcArray.
 	 *
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ee9644c2cba..479e0f2f421 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8799,7 +8799,7 @@
 # logging plan of the running query on the specified backend
 { oid => '8000', descr => 'log plan of the running query on the specified backend',
   proname => 'pg_log_query_plan',
-  provolatile => 'v', prorettype => 'bool',
+  provolatile => 'v', prorettype => 'int8',
   proargtypes => 'int4', prosrc => 'pg_log_query_plan',
   proacl => '{POSTGRES=X}' },
 
diff --git a/src/include/commands/explain_running.h b/src/include/commands/explain_running.h
index 316ef25be60..7d10c7763fb 100644
--- a/src/include/commands/explain_running.h
+++ b/src/include/commands/explain_running.h
@@ -15,10 +15,14 @@
 
 #include "executor/executor.h"
 #include "commands/explain_state.h"
+#include "storage/procnumber.h"
 
 extern void HandleLogQueryPlanInterrupt(void);
 extern void ProcessLogQueryPlanInterrupt(void);
 extern void LogQueryPlan(void);
+extern void FinishLogQueryPlanRequestWithoutPlan(uint64 query_id);
+extern void AbortLogQueryPlanRequest(void);
+extern void LogQueryPlanRequestSlotInit(void);
 extern Datum pg_log_query_plan(PG_FUNCTION_ARGS);
 
 #endif							/* EXPLAIN_RUNNING_H */
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..608b7c4ea7a 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -78,6 +78,7 @@ PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SyncScanShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(AsyncShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(StatsShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(LogQueryPlanShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(WaitEventCustomShmemCallbacks)
 #ifdef USE_INJECTION_POINTS
 PG_SHMEM_SUBSYSTEM(InjectionPointShmemCallbacks)
diff --git a/src/test/modules/test_misc/t/016_pg_log_query_plan.pl b/src/test/modules/test_misc/t/016_pg_log_query_plan.pl
index 9e7f06f3d89..4a1e0b20adb 100644
--- a/src/test/modules/test_misc/t/016_pg_log_query_plan.pl
+++ b/src/test/modules/test_misc/t/016_pg_log_query_plan.pl
@@ -71,8 +71,11 @@ $psql_session1->query_until(
 # Confirm that session1 is actually waiting on the advisory lock.
 $node->wait_for_event('client backend', 'advisory');
 
-# Run pg_log_query_plan().
-$psql_session2->query_safe("SELECT pg_log_query_plan($session1_pid);");
+# Run pg_log_query_plan() and save the request ID for the log check.
+my $request_id =
+  $psql_session2->query_safe("SELECT pg_log_query_plan($session1_pid);");
+like($request_id, qr/^[1-9][0-9]*$/,
+	'pg_log_query_plan returns a positive request ID');
 
 # Ensure that the signal of pg_log_query_plan() is actually
 # received by confirming session1 is waiting on the injection point.
@@ -91,7 +94,7 @@ $psql_session2->query_safe(
 ]);
 
 $node->wait_for_log(
-	"running on backend with PID $session1_pid ",
+	qr/request ID $request_id running on backend with PID $session1_pid /,
 	$log_offset);
 
 $psql_session1->query_safe("COMMIT;");
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index c72b015e2e1..5073876619d 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -384,9 +384,9 @@ SELECT has_function_privilege('regress_log_plan',
 (1 row)
 
 SET ROLE regress_log_plan;
-SELECT pg_log_query_plan(pg_backend_pid());
- pg_log_query_plan 
--------------------
+SELECT pg_log_query_plan(pg_backend_pid()) > 0 AS request_id_valid;
+ request_id_valid 
+------------------
  t
 (1 row)
 
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 7b709420c6e..7113aceaccb 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -136,7 +136,7 @@ SELECT has_function_privilege('regress_log_plan',
   'pg_log_query_plan(integer)', 'EXECUTE'); -- yes
 
 SET ROLE regress_log_plan;
-SELECT pg_log_query_plan(pg_backend_pid());
+SELECT pg_log_query_plan(pg_backend_pid()) > 0 AS request_id_valid;
 RESET ROLE;
 
 REVOKE EXECUTE ON FUNCTION pg_log_query_plan(integer)
-- 
2.48.1

