From afb2b7c7cb198f79d593c70def859e35b7a45ef1 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sun, 14 Feb 2010 23:59:21 +0100
Subject: [PATCH 2/2] Allow HS conflict cancellations to abort subtransactions.

Nested transactions were earlier FATALed because there could be
references to some level. This patch solved this by only aborting
recursively in the MainLoop where its sure that there are no other
errors.

As the the conflict cancellation now has its own error code this is
easy to recognize. Its likely a good idea to check in the procedual
language's exception handlers whether the error is a conflict one and
not allow those to get catched.
---
 src/backend/access/transam/xact.c |   46 +++++++++++++++++++++++++++++++++++++
 src/backend/tcop/postgres.c       |   40 +++++++++++++++++++-------------
 src/include/access/xact.h         |    2 +
 3 files changed, 72 insertions(+), 16 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 92e4146..d31ed3e 100644
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
*************** AbortOutOfAnyTransaction(void)
*** 3727,3732 ****
--- 3727,3778 ----
  }
  
  /*
+  *     AbortTransactionAndAnySubtransactions
+  *
+  * Similar to AbortCurrentTransaction but if any subtransactions
+  * in progress we abort them *and* all of their parents. So this is
+  * used when the caller wishes to make the abort untrappable by the user.
+  * After this has run IsAbortedTransactionBlockState() will be true.
+  *
+  * May only be run if nobody else references the subtransactions.
+  */
+ void
+ AbortTransactionAndAnySubtransactions(void)
+ {
+ 	while(true){
+ 		TransactionState s = CurrentTransactionState;
+ 
+ 		switch (s->blockState)
+ 		{
+ 			case TBLOCK_DEFAULT:
+ 			case TBLOCK_STARTED:
+ 			case TBLOCK_BEGIN:
+ 			case TBLOCK_INPROGRESS:
+ 			case TBLOCK_END:
+ 			case TBLOCK_ABORT:
+ 			case TBLOCK_SUBABORT:
+ 			case TBLOCK_ABORT_END:
+ 			case TBLOCK_ABORT_PENDING:
+ 			case TBLOCK_PREPARE:
+ 			case TBLOCK_SUBABORT_END:
+ 			case TBLOCK_SUBABORT_RESTART:
+ 				AbortCurrentTransaction();
+ 				return;
+ 				break;
+ 
+ 			case TBLOCK_SUBINPROGRESS:
+ 			case TBLOCK_SUBBEGIN:
+ 			case TBLOCK_SUBEND:
+ 			case TBLOCK_SUBABORT_PENDING:
+ 			case TBLOCK_SUBRESTART:
+ 				AbortSubTransaction();
+ 				CleanupSubTransaction();
+ 				break;
+ 		}
+ 	}
+ }
+ 
+ /*
   * IsTransactionBlock --- are we within a transaction block?
   */
  bool
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 67fa16b..b7b0f83 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** RecoveryConflictInterrupt(ProcSignalReas
*** 2794,2804 ****
  						return;
  
  					/*
! 					 * If we can abort just the current subtransaction then we
! 					 * are OK to throw an ERROR to resolve the conflict. Otherwise
! 					 * drop through to the FATAL case.
  					 *
- 					 * XXX other times that we can throw just an ERROR *may* be
  					 *   PROCSIG_RECOVERY_CONFLICT_LOCK
  					 *		if no locks are held in parent transactions
  					 *
--- 2794,2808 ----
  						return;
  
  					/*
! 					 * If we already aborted then we no longer need to
! 					 * cancel. We do not want to do this if were just
! 					 * in an aborted subtransaction because whatever
! 					 * the conflicting object was it might be locked
! 					 * from an a txn higher in the chain.
! 					 *
! 					 * XXX in some situations we *may* not need to kill the
! 					 * whole chain:
  					 *
  					 *   PROCSIG_RECOVERY_CONFLICT_LOCK
  					 *		if no locks are held in parent transactions
  					 *
*************** RecoveryConflictInterrupt(ProcSignalReas
*** 2809,2824 ****
  					 *   PROCSIG_RECOVERY_CONFLICT_TABLESPACE
  					 *		if no temp files or cursors open in parent transactions
  					 */
! 					if (!IsSubTransaction())
! 					{
! 						/*
! 						 * If we already aborted then we no longer need to cancel.
! 						 * We do this here since we do not wish to ignore aborted
! 						 * subtransactions, which must cause FATAL, currently.
! 						 */
! 						if (IsAbortedTransactionBlockState())
! 							return;
! 
  						RecoveryConflictPending = true;
  						QueryCancelPending = true;
  						InterruptPending = true;
--- 2813,2821 ----
  					 *   PROCSIG_RECOVERY_CONFLICT_TABLESPACE
  					 *		if no temp files or cursors open in parent transactions
  					 */
! 					if(!IsSubTransaction && IsAbortedTransactionBlockState())
! 						return;
! 					else{
  						RecoveryConflictPending = true;
  						QueryCancelPending = true;
  						InterruptPending = true;
*************** PostgresMain(int argc, char *argv[], con
*** 3730,3738 ****
  		debug_query_string = NULL;
  
  		/*
! 		 * Abort the current transaction in order to recover.
  		 */
! 		AbortCurrentTransaction();
  
  		/*
  		 * Now return to normal top-level context and clear ErrorContext for
--- 3727,3746 ----
  		debug_query_string = NULL;
  
  		/*
! 		 *
! 		 * Abort the current transaction in order to recover. If were
! 		 * in HS this is the only safe point where we can abort not
! 		 * only the current transaction but also all transaction above
! 		 * the current as there exists no higher point to jump to and
! 		 * thus were not playing around with somebodys execution context.
  		 */
! 		if(geterrcode() == ERRCODE_QUERY_CANCELED_HS)
! 		{
! 			AbortTransactionAndAnySubtransactions();
! 		}
! 		else{
! 			AbortCurrentTransaction();
! 		}
  
  		/*
  		 * Now return to normal top-level context and clear ErrorContext for
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 67ee39d..a0ae69e 100644
*** a/src/include/access/xact.h
--- b/src/include/access/xact.h
*************** extern bool IsTransactionBlock(void);
*** 204,209 ****
--- 204,211 ----
  extern bool IsTransactionOrTransactionBlock(void);
  extern char TransactionBlockStatusCode(void);
  extern void AbortOutOfAnyTransaction(void);
+ extern void AbortTransactionAndAnySubtransactions(void);
+ 
  extern void PreventTransactionChain(bool isTopLevel, const char *stmtType);
  extern void RequireTransactionChain(bool isTopLevel, const char *stmtType);
  extern bool IsInTransactionChain(bool isTopLevel);
-- 
1.6.5.12.gd65df24

