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; --