From 6a27a19395df053112616b83c966f4b20869ebc3 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Fri, 21 May 2010 20:17:11 +0200
Subject: [PATCH 2/2] Idle transaction cancellation.

---
 src/backend/tcop/postgres.c |   75 +++++++++++++++++++++++++++++++-----------
 1 files changed, 55 insertions(+), 20 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cba90a9..d0c8696 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -177,6 +177,12 @@ static bool RecoveryConflictPending = false;
 static bool RecoveryConflictRetryable = true;
 static ProcSignalReason RecoveryConflictReason;
 
+/*
+ * Are we disallowed from sending a "ready for query" message right
+ * now because it would confuse the frontend?
+ */
+bool silent_error_while_idle = false;
+
 /* ----------------------------------------------------------------
  *		decls for routines only used in this file
  * ----------------------------------------------------------------
@@ -2877,6 +2883,8 @@ RecoveryConflictInterrupt(ProcSignalReason reason)
 void
 ProcessInterrupts(void)
 {
+	int error = ERROR;
+
 	/* OK to accept interrupt now? */
 	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
 		return;
@@ -2949,18 +2957,24 @@ ProcessInterrupts(void)
 			RecoveryConflictPending = false;
 			DisableNotifyInterrupt();
 			DisableCatchupInterrupt();
-			if (DoingCommandRead)
-				ereport(FATAL,
-						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-						 errmsg("terminating connection due to conflict with recovery"),
-						 errdetail_recovery_conflict(),
-				 errhint("In a moment you should be able to reconnect to the"
-						 " database and repeat your command.")));
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-				 errmsg("canceling statement due to conflict with recovery"),
-						 errdetail_recovery_conflict()));
+
+			if (DoingCommandRead){
+				/*
+				 * We cant issue a normal ERROR here because the
+				 * client doesnt expect the server to send an error at
+				 * that point.
+				 * We also may not send a "ready for query"/Z message
+				 * because that would be unexpected as well.
+				 */
+				silent_error_while_idle = true;
+				error |= LOG_NO_CLIENT;
+
+			}
+
+			ereport(error,
+			        (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+			         errmsg("canceling statement due to conflict with recovery"),
+			         errdetail_recovery_conflict()));
 		}
 
 		/*
@@ -2968,15 +2982,18 @@ ProcessInterrupts(void)
 		 * request --- sending an extra error message won't accomplish
 		 * anything.  Otherwise, go ahead and throw the error.
 		 */
-		if (!DoingCommandRead)
+		if (DoingCommandRead)
 		{
-			ImmediateInterruptOK = false;		/* not idle anymore */
-			DisableNotifyInterrupt();
-			DisableCatchupInterrupt();
-			ereport(ERROR,
-					(errcode(ERRCODE_QUERY_CANCELED),
-					 errmsg("canceling statement due to user request")));
+			silent_error_while_idle = true;
+			error |= LOG_NO_CLIENT;
 		}
+
+		ImmediateInterruptOK = false;		/* not idle anymore */
+		DisableNotifyInterrupt();
+		DisableCatchupInterrupt();
+		ereport(error,
+		        (errcode(ERRCODE_QUERY_CANCELED),
+		         errmsg("canceling statement due to user request")));
 	}
 	/* If we get here, do nothing (probably, QueryCancelPending was reset) */
 }
@@ -3794,7 +3811,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 		 * uncommitted updates (that confuses autovacuum).	The notification
 		 * processor wants a call too, if we are not in a transaction block.
 		 */
-		if (send_ready_for_query)
+		if (send_ready_for_query && !silent_error_while_idle)
 		{
 			if (IsAbortedTransactionBlockState())
 			{
@@ -3819,6 +3836,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 			send_ready_for_query = false;
 		}
 
+
 		/*
 		 * (2) Allow asynchronous signals to be executed immediately if they
 		 * come in while we are waiting for client input. (This must be
@@ -3854,6 +3872,23 @@ PostgresMain(int argc, char *argv[], const char *username)
 		if (ignore_till_sync && firstchar != EOF)
 			continue;
 
+		if(silent_error_while_idle && !doing_extended_query_message)
+		{
+			/*
+			 * When using the simple protocol:
+			 * At this point we have processed a ReadCommand which means
+			 * that we got some input from the frontend and thus can start
+			 * spewing errors again
+
+			 * When using the extended protocol:
+			 * doing_extended_query_message is false after a 'sync'
+			 * message - which is exactly the point after which we can
+			 * start sending errors again.
+			 */
+			silent_error_while_idle = false;
+		}
+
+
 		switch (firstchar)
 		{
 			case 'Q':			/* simple query */
-- 
1.7.3.rc1.5.g73aa2

