From fef38ad31b4bd0c4ac968a93420a3bb4513e6b15 Mon Sep 17 00:00:00 2001 From: alterego655 <824662526@qq.com> Date: Wed, 7 Jan 2026 00:57:40 +0800 Subject: [PATCH v1] Move snapshot release to the beginning of ExecWaitStmt() Move the snapshot handling code (PopActiveSnapshot, InvalidateCatalogSnapshot, and the HaveRegisteredOrActiveSnapshot check) from after option parsing to the very beginning of ExecWaitStmt(). This reduces the window during which the WAIT FOR LSN session could be killed by snapshot-based recovery conflicts. When a snapshot-based recovery conflict is processed on a hot standby, GetConflictingVirtualXIDs() targets backends whose xmin <= limitXmin. By releasing our snapshot and clearing xmin before option parsing, we become immune to such conflicts during that phase. This is a pure code movement with no functional change to the snapshot handling logic itself. All original comments are preserved. --- src/backend/commands/wait.c | 68 ++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/backend/commands/wait.c b/src/backend/commands/wait.c index 97f1e778488..dd1daa89623 100644 --- a/src/backend/commands/wait.c +++ b/src/backend/commands/wait.c @@ -44,6 +44,40 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest) bool no_throw_specified = false; bool mode_specified = false; + /* + * We are going to wait for the LSN. We should first care that we don't + * hold a snapshot and correspondingly our MyProc->xmin is invalid. + * Otherwise, our snapshot could prevent the replay of WAL records + * implying a kind of self-deadlock. This is the reason why WAIT FOR is a + * command, not a procedure or function. + * + * At first, we should check there is no active snapshot. According to + * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is + * processed with a snapshot. Thankfully, we can pop this snapshot, + * because PortalRunUtility() can tolerate this. + */ + if (ActiveSnapshotSet()) + PopActiveSnapshot(); + + /* + * At second, invalidate a catalog snapshot if any. And we should be done + * with the preparation. + */ + InvalidateCatalogSnapshot(); + + /* Give up if there is still an active or registered snapshot. */ + if (HaveRegisteredOrActiveSnapshot()) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("WAIT FOR must be called without an active or registered snapshot"), + errdetail("WAIT FOR cannot be executed from a function or procedure, nor within a transaction with an isolation level higher than READ COMMITTED.")); + + /* + * As the result we should hold no snapshot, and correspondingly our xmin + * should be unset. + */ + Assert(MyProc->xmin == InvalidTransactionId); + /* Parse and validate the mandatory LSN */ lsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in, CStringGetDatum(stmt->lsn_literal))); @@ -134,40 +168,6 @@ ExecWaitStmt(ParseState *pstate, WaitStmt *stmt, DestReceiver *dest) } } - /* - * We are going to wait for the LSN. We should first care that we don't - * hold a snapshot and correspondingly our MyProc->xmin is invalid. - * Otherwise, our snapshot could prevent the replay of WAL records - * implying a kind of self-deadlock. This is the reason why WAIT FOR is a - * command, not a procedure or function. - * - * At first, we should check there is no active snapshot. According to - * PlannedStmtRequiresSnapshot(), even in an atomic context, CallStmt is - * processed with a snapshot. Thankfully, we can pop this snapshot, - * because PortalRunUtility() can tolerate this. - */ - if (ActiveSnapshotSet()) - PopActiveSnapshot(); - - /* - * At second, invalidate a catalog snapshot if any. And we should be done - * with the preparation. - */ - InvalidateCatalogSnapshot(); - - /* Give up if there is still an active or registered snapshot. */ - if (HaveRegisteredOrActiveSnapshot()) - ereport(ERROR, - errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("WAIT FOR must be called without an active or registered snapshot"), - errdetail("WAIT FOR cannot be executed from a function or procedure, nor within a transaction with an isolation level higher than READ COMMITTED.")); - - /* - * As the result we should hold no snapshot, and correspondingly our xmin - * should be unset. - */ - Assert(MyProc->xmin == InvalidTransactionId); - /* * Validate that the requested mode matches the current server state. * Primary modes can only be used on a primary. -- 2.51.0