From f179240230d91661ef31e0992614e1e9c0be7e08 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Mon, 9 Nov 2026 00:30:25 +0100
Subject: [PATCH v1 2/2] Don't restart a statement timeout that has already
 fired.

Since commit 22f6f2c1ccb, enable_statement_timeout() relies on
get_timeout_active() to avoid restarting a running timeout, but that
returns false once the timeout has fired. If the timeout fires while
exec_simple_query() parses a long query string, which doesn't check for
interrupts, the next start_xact_command() restarts it, resetting its
indicator, so the pending cancel request is reported as "canceling
statement due to user request" instead of a statement timeout.

To fix, don't restart the timer while the indicator is set.  This relies
on the previous commit resetting the indicator at the end of each
statement; otherwise a stale indicator could keep later statements from
being timed out.

Back-patch to all supported branches, like the previous commit.

Author: Tomas Vondra <tomas@vondra.me>
Reviewed-by: FIXME
Discussion: FIXME
Backpatch-through: 15
---
 src/backend/tcop/postgres.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 6d301f8102b..ee75f81194f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -5378,7 +5378,10 @@ log_disconnections(int code, Datum arg)
  *
  * If there's already a timeout running, don't restart the timer.  That
  * enables compromises between accuracy of timeouts and cost of starting a
- * timeout.
+ * timeout.  Likewise, don't restart it if it has already fired, but the query
+ * cancel request it sent hasn't been serviced yet (e.g. because it fired
+ * while parsing a long query string).  Restarting it would reset its
+ * indicator, causing the request to be reported as a user request.
  */
 static void
 enable_statement_timeout(void)
@@ -5389,7 +5392,8 @@ enable_statement_timeout(void)
 	if (StatementTimeout > 0
 		&& (StatementTimeout < TransactionTimeout || TransactionTimeout == 0))
 	{
-		if (!get_timeout_active(STATEMENT_TIMEOUT))
+		if (!get_timeout_active(STATEMENT_TIMEOUT) &&
+			!get_timeout_indicator(STATEMENT_TIMEOUT, false))
 			enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);
 	}
 	else
-- 
2.55.0

