From 8d39abbeea8362048eb437289fab718ea8ef23f3 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sat, 30 Oct 2010 10:36:41 +0200
Subject: [PATCH 3/3] Enable rethrowing errors which occured while IDLE IN TRANSACTION state when explicitly told so via a new error flag.

This makes cancelling an idle transactions more visible to the cancelled connection so it can react more sensibly than just reacting to an ominous "in failed transaction" error.
---
 src/backend/tcop/fastpath.c    |    5 +--
 src/backend/tcop/postgres.c    |   61 +++++++++++++++++----------------------
 src/backend/utils/error/elog.c |   23 +++++++++++++++
 src/include/tcop/tcopprot.h    |    4 ++-
 src/include/utils/elog.h       |    2 +
 5 files changed, 56 insertions(+), 39 deletions(-)

diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index af58e4e..ed6f02a 100644
*** a/src/backend/tcop/fastpath.c
--- b/src/backend/tcop/fastpath.c
*************** HandleFunctionRequest(StringInfo msgBuf)
*** 298,307 ****
  	 * won't lose sync with the frontend.
  	 */
  	if (IsAbortedTransactionBlockState())
! 		ereport(ERROR,
! 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 				 errmsg("current transaction is aborted, "
! 						"commands ignored until end of transaction block")));
  
  	/*
  	 * Now that we know we are in a valid transaction, set snapshot in case
--- 298,304 ----
  	 * won't lose sync with the frontend.
  	 */
  	if (IsAbortedTransactionBlockState())
! 		RaiseInFailedTransactionError();
  
  	/*
  	 * Now that we know we are in a valid transaction, set snapshot in case
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 505d136..f144101 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** static ProcSignalReason RecoveryConflict
*** 183,188 ****
--- 183,195 ----
   */
  static bool silent_error_while_idle = false;
  
+ /*
+  * Which error occured while we were idle? We want to rethrow it in
+  * the face of the next query - except if it is a ROLLBACK/COMMIT - it
+  * will silently be swallowed there...
+  */
+ ErrorData *silent_error_while_idle_edata;
+ 
  /* ----------------------------------------------------------------
   *		decls for routines only used in this file
   * ----------------------------------------------------------------
*************** exec_simple_query(const char *query_stri
*** 952,962 ****
  		 */
  		if (IsAbortedTransactionBlockState() &&
  			!IsTransactionExitStmt(parsetree))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 					 errmsg("current transaction is aborted, "
! 						  "commands ignored until end of transaction block"),
! 					 errdetail_abort()));
  
  		/* Make sure we are in a transaction command */
  		start_xact_command();
--- 959,965 ----
  		 */
  		if (IsAbortedTransactionBlockState() &&
  			!IsTransactionExitStmt(parsetree))
! 			RaiseInFailedTransactionError();
  
  		/* Make sure we are in a transaction command */
  		start_xact_command();
*************** exec_parse_message(const char *query_str
*** 1262,1273 ****
  		 */
  		if (IsAbortedTransactionBlockState() &&
  			!IsTransactionExitStmt(raw_parse_tree))
! 			ereport(ERROR,
! 					(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 					 errmsg("current transaction is aborted, "
! 						  "commands ignored until end of transaction block"),
! 					 errdetail_abort()));
! 
  		/*
  		 * Set up a snapshot if parse analysis/planning will need one.
  		 */
--- 1265,1271 ----
  		 */
  		if (IsAbortedTransactionBlockState() &&
  			!IsTransactionExitStmt(raw_parse_tree))
! 			RaiseInFailedTransactionError();
  		/*
  		 * Set up a snapshot if parse analysis/planning will need one.
  		 */
*************** exec_bind_message(StringInfo input_messa
*** 1543,1554 ****
  	if (IsAbortedTransactionBlockState() &&
  		(!IsTransactionExitStmt(psrc->raw_parse_tree) ||
  		 numParams != 0))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 				 errmsg("current transaction is aborted, "
! 						"commands ignored until end of transaction block"),
! 				 errdetail_abort()));
! 
  	/*
  	 * Create the portal.  Allow silent replacement of an existing portal only
  	 * if the unnamed portal is specified.
--- 1541,1547 ----
  	if (IsAbortedTransactionBlockState() &&
  		(!IsTransactionExitStmt(psrc->raw_parse_tree) ||
  		 numParams != 0))
! 		RaiseInFailedTransactionError();
  	/*
  	 * Create the portal.  Allow silent replacement of an existing portal only
  	 * if the unnamed portal is specified.
*************** exec_execute_message(const char *portal_
*** 1985,1995 ****
  	 */
  	if (IsAbortedTransactionBlockState() &&
  		!IsTransactionExitStmtList(portal->stmts))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 				 errmsg("current transaction is aborted, "
! 						"commands ignored until end of transaction block"),
! 				 errdetail_abort()));
  
  	/* Check for cancel signal before we start execution */
  	CHECK_FOR_INTERRUPTS();
--- 1978,1984 ----
  	 */
  	if (IsAbortedTransactionBlockState() &&
  		!IsTransactionExitStmtList(portal->stmts))
! 		RaiseInFailedTransactionError();
  
  	/* Check for cancel signal before we start execution */
  	CHECK_FOR_INTERRUPTS();
*************** exec_describe_statement_message(const ch
*** 2353,2363 ****
  	 */
  	if (IsAbortedTransactionBlockState() &&
  		psrc->resultDesc)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 				 errmsg("current transaction is aborted, "
! 						"commands ignored until end of transaction block"),
! 				 errdetail_abort()));
  
  	if (whereToSendOutput != DestRemote)
  		return;					/* can't actually do anything... */
--- 2342,2348 ----
  	 */
  	if (IsAbortedTransactionBlockState() &&
  		psrc->resultDesc)
! 		RaiseInFailedTransactionError();
  
  	if (whereToSendOutput != DestRemote)
  		return;					/* can't actually do anything... */
*************** exec_describe_portal_message(const char
*** 2434,2444 ****
  	 */
  	if (IsAbortedTransactionBlockState() &&
  		portal->tupDesc)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
! 				 errmsg("current transaction is aborted, "
! 						"commands ignored until end of transaction block"),
! 				 errdetail_abort()));
  
  	if (whereToSendOutput != DestRemote)
  		return;					/* can't actually do anything... */
--- 2419,2425 ----
  	 */
  	if (IsAbortedTransactionBlockState() &&
  		portal->tupDesc)
! 		RaiseInFailedTransactionError();
  
  	if (whereToSendOutput != DestRemote)
  		return;					/* can't actually do anything... */
*************** IsTransactionStmtList(List *parseTrees)
*** 2571,2576 ****
--- 2552,2569 ----
  	return false;
  }
  
+ void
+ RaiseInFailedTransactionError(void){
+ 	if(silent_error_while_idle_edata){
+ 		ReThrowError(silent_error_while_idle_edata);
+ 	}
+ 	ereport(ERROR,
+ 	        (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
+ 	         errmsg("current transaction is aborted, "
+ 	                "commands ignored until end of transaction block"),
+ 	         errdetail_abort()));
+ }
+ 
  /* Release any existing unnamed prepared statement */
  static void
  drop_unnamed_stmt(void)
*************** ProcessInterrupts(void)
*** 2967,2973 ****
  				 * because that would be unexpected as well.
  				 */
  				silent_error_while_idle = true;
! 				error |= LOG_NO_CLIENT;
  
  			}
  
--- 2960,2966 ----
  				 * because that would be unexpected as well.
  				 */
  				silent_error_while_idle = true;
! 				error |= LOG_NO_CLIENT|LOG_RE_THROW_AFTER_SYNC;
  
  			}
  
*************** ProcessInterrupts(void)
*** 2985,2991 ****
  		if (DoingCommandRead)
  		{
  			silent_error_while_idle = true;
! 			error |= LOG_NO_CLIENT;
  		}
  
  		ImmediateInterruptOK = false;		/* not idle anymore */
--- 2978,2984 ----
  		if (DoingCommandRead)
  		{
  			silent_error_while_idle = true;
! 			error |= LOG_NO_CLIENT|LOG_RE_THROW_AFTER_SYNC;
  		}
  
  		ImmediateInterruptOK = false;		/* not idle anymore */
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 6e174d8..d8895c6 100644
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
*************** EmitErrorReport(void)
*** 1173,1178 ****
--- 1173,1201 ----
  	if (edata->output_to_client && !(edata->eflags & LOG_NO_CLIENT))
  		send_message_to_frontend(edata);
  
+ 	if(silent_error_while_idle_edata){
+ 		FreeErrorData(silent_error_while_idle_edata);
+ 		silent_error_while_idle_edata = 0;
+ 	}
+ 
+ 	if (edata->eflags & LOG_RE_THROW_AFTER_SYNC){
+ 		Assert(edata->elevel >= ERROR);
+ 
+ 		/* The old context is already saved above and reset
+ 		 * below... We cannot store the ErrorData in any other context
+ 		 * than TopMemoryContext - they all may get reset inbetween
+ 		 * two EmitErrorReport() calls.
+ 		 * As there is no convenient point to reset the variable using
+ 		 * any transaction bound variable is not possible without
+ 		 * leaving a stray pointer.
+ 		 */
+ 		MemoryContextSwitchTo(TopMemoryContext);
+ 		silent_error_while_idle_edata = CopyErrorData();
+ 
+ 		/* We dont want to save/rethrow that error again */
+ 		silent_error_while_idle_edata->eflags &= ~(LOG_RE_THROW_AFTER_SYNC|LOG_NO_CLIENT);
+ 	}
+ 
  	MemoryContextSwitchTo(oldcontext);
  	recursion_depth--;
  }
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index f4026ec..b529ce1 100644
*** a/src/include/tcop/tcopprot.h
--- b/src/include/tcop/tcopprot.h
*************** typedef enum
*** 45,50 ****
--- 45,52 ----
  
  extern int	log_statement;
  
+ extern ErrorData *silent_error_while_idle_edata;
+ 
  extern List *pg_parse_and_rewrite(const char *query_string,
  					 Oid *paramTypes, int numParams);
  extern List *pg_parse_query(const char *query_string);
*************** extern void set_debug_options(int debug_
*** 81,85 ****
  extern bool set_plan_disabling_options(const char *arg,
  						   GucContext context, GucSource source);
  extern const char *get_stats_option_name(const char *arg);
! 
  #endif   /* TCOPPROT_H */
--- 83,87 ----
  extern bool set_plan_disabling_options(const char *arg,
  						   GucContext context, GucSource source);
  extern const char *get_stats_option_name(const char *arg);
! extern void RaiseInFailedTransactionError(void);
  #endif   /* TCOPPROT_H */
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 1853e1d..529265a 100644
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 23,28 ****
--- 23,30 ----
                                   * if it would confuse the client at
                                   * that moment */
  
+ #define LOG_RE_THROW_AFTER_SYNC (2<<29)
+ 
  /* Error level codes */
  #define DEBUG5		10			/* Debugging messages, in categories of
  								 * decreasing detail. */
-- 
1.7.3.rc1.5.g73aa2

