From 16ace60f82062f0f13fc1814085b045aedd6f202 Mon Sep 17 00:00:00 2001 From: Satya Narlapuram Date: Thu, 9 Apr 2026 00:50:20 +0000 Subject: [PATCH v1 1/2] Fix assertion failure in WAIT FOR LSN when used inside PL/pgSQL WAIT FOR LSN ... INTO inside a DO block crashed with an assertion failure in EnsurePortalSnapshotExists(): TRAP: failed Assert("portal->portalSnapshot == NULL"), File: "pquery.c", Line: 1776 The root cause was that ExecWaitStmt() called PopActiveSnapshot() to release the snapshot before waiting, but did not clear the active portal's portalSnapshot pointer. When the next PL/pgSQL statement requiring a snapshot called EnsurePortalSnapshotExists(), it found a stale non-NULL portalSnapshot and hit the assertion. PortalRunUtility() normally handles this cleanup in the top-level execution path, but when WAIT FOR runs through SPI (PL/pgSQL), ProcessUtility is called directly without the PortalRunUtility wrapper, so the portalSnapshot was never cleared. Fix by clearing ActivePortal->portalSnapshot after PopActiveSnapshot() in ExecWaitStmt(), matching what PortalRunUtility() does. --- src/backend/commands/wait.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c index 85fcd463..8d245dbd 100644 --- a/src/backend/commands/wait.c +++ b/src/backend/commands/wait.c @@ -24,6 +24,7 @@ #include "executor/executor.h" #include "parser/parse_node.h" #include "storage/proc.h" +#include "tcop/pquery.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/pg_lsn.h" @@ -146,10 +147,20 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest) * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is * processed with a snapshot. Thankfully, we can pop this snapshot, * because PortalRunUtility() can tolerate this. + * + * When running inside PL/pgSQL, the active + * portal may hold a pointer to this snapshot in portalSnapshot. We must + * clear it so that EnsurePortalSnapshotExists() doesn't trip an assertion + * when the next PL/pgSQL statement tries to re-establish a snapshot. */ if (ActiveSnapshotSet()) + { PopActiveSnapshot(); + if (ActivePortal && ActivePortal->portalSnapshot != NULL) + ActivePortal->portalSnapshot = NULL; + } + /* * At second, invalidate a catalog snapshot if any. And we should be done * with the preparation. -- 2.43.0