From b79d828ea4958a3ef6a1e79736c12683cfab4bb2 Mon Sep 17 00:00:00 2001
From: "Andrey M. Borodin" <x4mmm@night.local>
Date: Fri, 15 Mar 2024 17:18:19 +0500
Subject: [PATCH v2] Fix race condition in transaction timeout TAP tests

Interruption handler within injection point can stuck in infinite
loop while handling transaction timeout. To avoid this situation
we reset timeout flag before invoking injection point.

Author: Alexander Korotkov
Reviewed-by: Andrey Borodin
Discussion: https://www.postgresql.org/message-id/flat/E1rkjDR-003kjB-St%40gemulon.postgresql.org
---
 src/backend/tcop/postgres.c                  | 12 +++++-------
 src/test/modules/test_misc/t/005_timeouts.pl | 12 ++++++------
 2 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7ac623019b..fd4199a098 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3409,8 +3409,10 @@ ProcessInterrupts(void)
 		/*
 		 * If the GUC has been reset to zero, ignore the signal.  This is
 		 * important because the GUC update itself won't disable any pending
-		 * interrupt.
+		 * interrupt.  We need to unset the flag before the injection point,
+		 * otherwise we could loop in interrupts checking.
 		 */
+		IdleInTransactionSessionTimeoutPending = false;
 		if (IdleInTransactionSessionTimeout > 0)
 		{
 			INJECTION_POINT("idle-in-transaction-session-timeout");
@@ -3418,13 +3420,12 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
 					 errmsg("terminating connection due to idle-in-transaction timeout")));
 		}
-		else
-			IdleInTransactionSessionTimeoutPending = false;
 	}
 
 	if (TransactionTimeoutPending)
 	{
 		/* As above, ignore the signal if the GUC has been reset to zero. */
+		TransactionTimeoutPending = false;
 		if (TransactionTimeout > 0)
 		{
 			INJECTION_POINT("transaction-timeout");
@@ -3432,13 +3433,12 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
 					 errmsg("terminating connection due to transaction timeout")));
 		}
-		else
-			TransactionTimeoutPending = false;
 	}
 
 	if (IdleSessionTimeoutPending)
 	{
 		/* As above, ignore the signal if the GUC has been reset to zero. */
+		IdleSessionTimeoutPending = false;
 		if (IdleSessionTimeout > 0)
 		{
 			INJECTION_POINT("idle-session-timeout");
@@ -3446,8 +3446,6 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
 					 errmsg("terminating connection due to idle-session timeout")));
 		}
-		else
-			IdleSessionTimeoutPending = false;
 	}
 
 	/*
diff --git a/src/test/modules/test_misc/t/005_timeouts.pl b/src/test/modules/test_misc/t/005_timeouts.pl
index e67b3e694b..dc45213685 100644
--- a/src/test/modules/test_misc/t/005_timeouts.pl
+++ b/src/test/modules/test_misc/t/005_timeouts.pl
@@ -44,7 +44,7 @@ $psql_session->query_until(
    \q
 ));
 
-# Wait until the backend is in the timeout injection point. Will get an error
+# Wait until the backend enters the timeout injection point. Will get an error
 # here if anything goes wrong.
 $node->wait_for_event('client backend', 'transaction-timeout');
 
@@ -58,13 +58,13 @@ $node->safe_psql('postgres',
 $node->wait_for_log('terminating connection due to transaction timeout',
 	$log_offset);
 
-# If we send \q with $psql_session->quit it can get to pump already closed.
-# So \q is in initial script, here we only finish IPC::Run.
+# If we send \q with $psql_session->quit the command can be sent to the session
+# already closed. So \q is in initial script, here we only finish IPC::Run.
 $psql_session->{run}->finish;
 
 
 #
-# 2. Test of the sidle in transaction timeout
+# 2. Test of the idle in transaction timeout
 #
 
 $node->safe_psql('postgres',
@@ -80,7 +80,7 @@ $psql_session->query_until(
    BEGIN;
 ));
 
-# Wait until the backend is in the timeout injection point.
+# Wait until the backend enters the timeout injection point.
 $node->wait_for_event('client backend',
 	'idle-in-transaction-session-timeout');
 
@@ -111,7 +111,7 @@ $psql_session->query_until(
    SET idle_session_timeout to '10ms';
 ));
 
-# Wait until the backend is in the timeout injection point.
+# Wait until the backend enters the timeout injection point.
 $node->wait_for_event('client backend', 'idle-session-timeout');
 
 $log_offset = -s $node->logfile;
-- 
2.37.1 (Apple Git-137.1)

