diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 9cd0b7c11b..6db7b37dad 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,8 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/prepare.h"
+#include "commands/sequence.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -80,6 +82,8 @@ bool		XactReadOnly;
 bool		DefaultXactDeferrable = false;
 bool		XactDeferrable;
 
+bool		transaction_cleanup = false;
+
 int			synchronous_commit = SYNCHRONOUS_COMMIT_ON;
 
 /*
@@ -336,6 +340,8 @@ static void CommitTransaction(void);
 static TransactionId RecordTransactionAbort(bool isSubXact);
 static void StartTransaction(void);
 
+static void PostXactSessionCleanup(void);
+
 static void StartSubTransaction(void);
 static void CommitSubTransaction(void);
 static void AbortSubTransaction(void);
@@ -2132,11 +2138,23 @@ CommitTransaction(void)
 	/* Shut down the deferred-trigger manager */
 	AfterTriggerEndXact(true);
 
-	/*
-	 * Let ON COMMIT management do its thing (must happen after closing
-	 * cursors, to avoid dangling-reference problems)
-	 */
-	PreCommit_on_commit_actions();
+	/* Apply cleanup consistently across all end of xact actions */
+	if (transaction_cleanup && !IsBootstrapProcessingMode())
+	{
+		MyXactFlags |= XACT_FLAGS_CLEANUP_AT_XACT_END;
+
+		/* Closing portals might run user-defined code, so do that first. */
+		PortalHashTableDeleteAll();
+		ResetTempTableNamespace();
+	}
+	else
+	{
+		/*
+		 * Let ON COMMIT management do its thing (must happen after closing
+		 * cursors, to avoid dangling-reference problems)
+		 */
+		PreCommit_on_commit_actions();
+	}
 
 	/*
 	 * Synchronize files that are created and not WAL-logged during this
@@ -2372,11 +2390,23 @@ PrepareTransaction(void)
 	/* Shut down the deferred-trigger manager */
 	AfterTriggerEndXact(true);
 
-	/*
-	 * Let ON COMMIT management do its thing (must happen after closing
-	 * cursors, to avoid dangling-reference problems)
-	 */
-	PreCommit_on_commit_actions();
+	/* Apply cleanup consistently across all end of xact actions */
+	if (transaction_cleanup && !IsBootstrapProcessingMode())
+	{
+		MyXactFlags |= XACT_FLAGS_CLEANUP_AT_XACT_END;
+
+		/* Closing portals might run user-defined code, so do that first. */
+		PortalHashTableDeleteAll();
+		ResetTempTableNamespace();
+	}
+	else
+	{
+		/*
+		 * Let ON COMMIT management do its thing (must happen after closing
+		 * cursors, to avoid dangling-reference problems)
+		 */
+		PreCommit_on_commit_actions();
+	}
 
 	/*
 	 * Synchronize files that are created and not WAL-logged during this
@@ -2413,11 +2443,12 @@ PrepareTransaction(void)
 	 * We must check this after executing any ON COMMIT actions, because they
 	 * might still access a temp relation.
 	 *
-	 * XXX In principle this could be relaxed to allow some useful special
-	 * cases, such as a temp table created and dropped all within the
-	 * transaction.  That seems to require much more bookkeeping though.
+	 * This can be relaxed if we are using transaction_cleanup since we know
+	 * that any temp tables will have been created and dropped all within the
+	 * transaction.
 	 */
-	if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
+	if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE) &&
+		!(MyXactFlags & XACT_FLAGS_CLEANUP_AT_XACT_END))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot PREPARE a transaction that has operated on temporary objects")));
@@ -2840,6 +2871,25 @@ CleanupTransaction(void)
 	s->state = TRANS_DEFAULT;
 }
 
+/*
+ * Perform same actions as DISCARD ALL, but don't try to execute that
+ * directly since it expects to be inside a new transaction.
+ */
+static void
+PostXactSessionCleanup(void)
+{
+	if ((MyXactFlags & XACT_FLAGS_CLEANUP_AT_XACT_END))
+	{
+		SetPGVariable("session_authorization", NIL, false);
+		ResetAllOptions();
+		DropAllPreparedStatements();
+		LockReleaseAll(USER_LOCKMETHOD, true);
+		ResetPlanCache();
+		ResetSequenceCaches();
+	}
+}
+
+
 /*
  *	StartTransactionCommand
  */
@@ -3010,6 +3060,8 @@ CommitTransactionCommand(void)
 				s->chain = false;
 				RestoreTransactionCharacteristics();
 			}
+			else
+				PostXactSessionCleanup();
 			break;
 
 			/*
@@ -3036,6 +3088,8 @@ CommitTransactionCommand(void)
 				s->chain = false;
 				RestoreTransactionCharacteristics();
 			}
+			else
+				PostXactSessionCleanup();
 			break;
 
 			/*
@@ -3054,6 +3108,8 @@ CommitTransactionCommand(void)
 				s->chain = false;
 				RestoreTransactionCharacteristics();
 			}
+			else
+				PostXactSessionCleanup();
 			break;
 
 			/*
@@ -3062,6 +3118,7 @@ CommitTransactionCommand(void)
 			 */
 		case TBLOCK_PREPARE:
 			PrepareTransaction();
+			PostXactSessionCleanup();
 			s->blockState = TBLOCK_DEFAULT;
 			break;
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index e04afd9963..ee7e5dba59 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -1018,6 +1018,12 @@ AtCommit_Notify(void)
 		}
 	}
 
+	/*
+	 * Remove all listenChannels directly, if we need to cleanup
+	 */
+	if (amRegisteredListener && transaction_cleanup)
+		Exec_UnlistenAllCommit();
+
 	/* If no longer listening to anything, get out of listener array */
 	if (amRegisteredListener && listenChannels == NIL)
 		asyncQueueUnregister();
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dabcbb0736..d24a3ff285 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1633,6 +1633,15 @@ static struct config_bool ConfigureNamesBool[] =
 		false,
 		NULL, NULL, NULL
 	},
+	{
+		{"transaction_cleanup", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Whether session cleanup occurs immediately following transaction completion."),
+			NULL
+		},
+		&transaction_cleanup,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		{"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Whether to defer a read-only serializable transaction until it can be executed with no possible serialization failures."),
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 7320de345c..b1bbac2cb8 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -86,6 +86,8 @@ extern int	synchronous_commit;
 extern PGDLLIMPORT TransactionId CheckXidAlive;
 extern PGDLLIMPORT bool bsysscan;
 
+extern bool transaction_cleanup;
+
 /*
  * Miscellaneous flag bits to record events which occur on the top level
  * transaction. These flags are only persisted in MyXactFlags and are intended
@@ -107,6 +109,8 @@ extern int	MyXactFlags;
  */
 #define XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK	(1U << 1)
 
+#define XACT_FLAGS_CLEANUP_AT_XACT_END			(1U << 2)
+
 /*
  *	start- and end-of-transaction callbacks for dynamically loaded modules
  */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 811f80a097..1c5552495c 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -530,13 +530,13 @@ SELECT relname FROM pg_class WHERE relname = 'reset_test';
 --
 -- Test DISCARD ALL
 --
+CREATE ROLE regress_guc_user;
 -- do changes
 DECLARE foo CURSOR WITH HOLD FOR SELECT 1;
 PREPARE foo AS SELECT 1;
 LISTEN foo_event;
 SET vacuum_cost_delay = 13;
 CREATE TEMP TABLE tmp_foo (data text) ON COMMIT DELETE ROWS;
-CREATE ROLE regress_guc_user;
 SET SESSION AUTHORIZATION regress_guc_user;
 -- look changes
 SELECT pg_listening_channels();
@@ -610,6 +610,81 @@ SELECT current_user = 'regress_guc_user';
  f
 (1 row)
 
+-- do changes, with cleanup
+SET transaction_cleanup = on;
+BEGIN;
+DECLARE foo CURSOR WITH HOLD FOR SELECT 1;
+PREPARE foo AS SELECT 1;
+LISTEN foo_event;
+SET vacuum_cost_delay = 13;
+CREATE TEMP TABLE tmp_foo (data text) ON COMMIT DELETE ROWS;
+SET SESSION AUTHORIZATION regress_guc_user;
+-- look changes
+SELECT name FROM pg_prepared_statements;
+ name 
+------
+ foo
+(1 row)
+
+SELECT name FROM pg_cursors;
+ name 
+------
+ foo
+(1 row)
+
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay 
+-------------------
+ 13ms
+(1 row)
+
+SELECT relname from pg_class where relname = 'tmp_foo';
+ relname 
+---------
+ tmp_foo
+(1 row)
+
+SELECT current_user = 'regress_guc_user';
+ ?column? 
+----------
+ t
+(1 row)
+
+COMMIT;
+SET transaction_cleanup = off;
+-- look again, should be same as DISCARD ALL
+SELECT pg_listening_channels();
+ pg_listening_channels 
+-----------------------
+(0 rows)
+
+SELECT name FROM pg_prepared_statements;
+ name 
+------
+(0 rows)
+
+SELECT name FROM pg_cursors;
+ name 
+------
+(0 rows)
+
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay 
+-------------------
+ 0
+(1 row)
+
+SELECT relname from pg_class where relname = 'tmp_foo';
+ relname 
+---------
+(0 rows)
+
+SELECT current_user = 'regress_guc_user';
+ ?column? 
+----------
+ f
+(1 row)
+
 DROP ROLE regress_guc_user;
 --
 -- search_path should react to changes in pg_namespace
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index 43dbba3775..e7f4095f82 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -160,13 +160,14 @@ SELECT relname FROM pg_class WHERE relname = 'reset_test';
 -- Test DISCARD ALL
 --
 
+CREATE ROLE regress_guc_user;
+
 -- do changes
 DECLARE foo CURSOR WITH HOLD FOR SELECT 1;
 PREPARE foo AS SELECT 1;
 LISTEN foo_event;
 SET vacuum_cost_delay = 13;
 CREATE TEMP TABLE tmp_foo (data text) ON COMMIT DELETE ROWS;
-CREATE ROLE regress_guc_user;
 SET SESSION AUTHORIZATION regress_guc_user;
 -- look changes
 SELECT pg_listening_channels();
@@ -184,6 +185,32 @@ SELECT name FROM pg_cursors;
 SHOW vacuum_cost_delay;
 SELECT relname from pg_class where relname = 'tmp_foo';
 SELECT current_user = 'regress_guc_user';
+
+-- do changes, with cleanup
+SET transaction_cleanup = on;
+BEGIN;
+DECLARE foo CURSOR WITH HOLD FOR SELECT 1;
+PREPARE foo AS SELECT 1;
+LISTEN foo_event;
+SET vacuum_cost_delay = 13;
+CREATE TEMP TABLE tmp_foo (data text) ON COMMIT DELETE ROWS;
+SET SESSION AUTHORIZATION regress_guc_user;
+-- look changes
+SELECT name FROM pg_prepared_statements;
+SELECT name FROM pg_cursors;
+SHOW vacuum_cost_delay;
+SELECT relname from pg_class where relname = 'tmp_foo';
+SELECT current_user = 'regress_guc_user';
+COMMIT;
+SET transaction_cleanup = off;
+-- look again, should be same as DISCARD ALL
+SELECT pg_listening_channels();
+SELECT name FROM pg_prepared_statements;
+SELECT name FROM pg_cursors;
+SHOW vacuum_cost_delay;
+SELECT relname from pg_class where relname = 'tmp_foo';
+SELECT current_user = 'regress_guc_user';
+
 DROP ROLE regress_guc_user;
 
 --
