Read-only access to temp tables for 2PC transactions

Started by Stas Kelvichover 6 years ago10 messages
#1Stas Kelvich
s.kelvich@postgrespro.ru
1 attachment(s)

Hi,

That is an attempt number N+1 to relax checks for a temporary table access
in a transaction that is going to be prepared.

One of the problems regarding the use of temporary tables in prepared transactions
is that such transaction will hold locks for a temporary table after being prepared.
That locks will prevent the backend from exiting since it will fail to acquire lock
needed to delete temp table during exit. Also, re-acquiring such lock after server
restart seems like an ill-defined operation.

I tried to allow prepared transactions that opened a temporary relation only in
AccessShare mode and then neither transfer this lock to a dummy PGPROC nor include
it in a 'prepare' record. Such prepared transaction will not prevent the backend from
exiting and can be committed from other backend or after a restart.

However, that modification allows new DDL-related serialization anomaly: it will be
possible to prepare transaction which read table A; then drop A; then commit the
transaction. I not sure whether that is worse than not being able to access temp
relations or not. On the other hand, it is possible to drop AccessShare locks only for
temporary relation and don't change behavior for an ordinary table (in the attached
patch this is done for all tables).

Also, I slightly modified ON COMMIT DELETE code path. Right now all ON COMMIT DELETE
temp tables are linked in a static list and if transaction accessed any temp table
in any mode then during commit all tables from that list will be truncated. For a
given patch that means that even if a transaction only did read from a temp table it
anyway can access other temp tables with high lock mode during commit. I've added
hashtable that tracks higher-than-AccessShare action with a temp table during
current transaction, so during commit, only tables from that hash will be truncated.
That way ON COMMIT DELETE tables in the backend will not prevent read-only access to
some other table in a given backend.

Any thoughts?

--
Stas Kelvich
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

2PC-ro-temprels.patchapplication/octet-stream; name=2PC-ro-temprels.patch; x-unix-mode=0644Download
commit 5d0c2d5b847bd48aa4c187077717ca99a90086bd
Author: Stas Kelvich <stanconn@gmail.com>
Date:   Tue May 14 12:47:59 2019 +0300

    Read-only access to temp tables for 2PC transactions

diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 41a0051f88..c70449bf4c 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -23,6 +23,7 @@
 #include "access/relation.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/lmgr.h"
@@ -69,9 +70,19 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 		   IsBootstrapProcessingMode() ||
 		   CheckRelationLockedByMe(r, AccessShareLock, true));
 
-	/* Make note that we've accessed a temporary relation */
-	if (RelationUsesLocalBuffers(r))
+	/*
+	 * Make note that we've accessed a temporary relation with at least
+	 * RowShareLock lockmode. See also comments above MyXactFlags check
+	 * in PrepareTransaction().
+	 */
+	if (RelationUsesLocalBuffers(r) &&
+		(lockmode > AccessShareLock ||
+		 (lockmode == NoLock &&
+		  CheckRelationLockedByMe(r, RowShareLock, true))))
+	{
+		register_temprel_modify(relationId);
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+	}
 
 	pgstat_initstats(r);
 
@@ -119,9 +130,19 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	Assert(lockmode != NoLock ||
 		   CheckRelationLockedByMe(r, AccessShareLock, true));
 
-	/* Make note that we've accessed a temporary relation */
-	if (RelationUsesLocalBuffers(r))
+	/*
+	 * Make note that we've accessed a temporary relation with at least
+	 * RowShareLock lockmode. See also comments above MyXactFlags check
+	 * in PrepareTransaction().
+	 */
+	if (RelationUsesLocalBuffers(r) &&
+		(lockmode > AccessShareLock ||
+		 (lockmode == NoLock &&
+		  CheckRelationLockedByMe(r, AccessShareLock, true))))
+	{
+		register_temprel_modify(relationId);
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+	}
 
 	pgstat_initstats(r);
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 20feeec327..39a4c1106d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2352,12 +2352,14 @@ PrepareTransaction(void)
 	/* NOTIFY will be handled below */
 
 	/*
-	 * Don't allow PREPARE TRANSACTION if we've accessed a temporary table in
+	 * Don't allow PREPARE TRANSACTION if we've modified a temporary table in
 	 * this transaction.  Having the prepared xact hold locks on another
 	 * backend's temp table seems a bad idea --- for instance it would prevent
 	 * the backend from exiting.  There are other problems too, such as how to
 	 * clean up the source backend's local buffers and ON COMMIT state if the
-	 * prepared xact includes a DROP of a temp table.
+	 * prepared xact includes a DROP of a temp table.  Special case of reading
+	 * from a temporary table is allowed by dropping AccessShare locks during
+	 * PREPARE.
 	 *
 	 * Other objects types, like functions, operators or extensions, share the
 	 * same restriction as they should not be created, locked or dropped as
@@ -2368,8 +2370,9 @@ PrepareTransaction(void)
 	 * 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.
+	 * cases besides reading from a temp table, such as a temp table created
+	 * and dropped all within the transaction.  That seems to require much more
+	 * bookkeeping though.
 	 */
 	if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
 		ereport(ERROR,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index baeb13e6a0..1e3ae90d95 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -122,6 +122,12 @@ typedef struct OnCommitItem
 
 static List *on_commits = NIL;
 
+/*
+ * Set of temp relation oid's for which we aquired locks stronger then
+ * AccessShare.  During commit this set will be intersected with on_commits
+ * to determine which relations needs truncation.
+ */
+static HTAB *modified_temprels = NULL;
 
 /*
  * State information for ALTER TABLE
@@ -14398,6 +14404,34 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	MemoryContextSwitchTo(oldcxt);
 }
 
+/*
+ * Track modifying access to temporary relations.
+ * Later we use that set to determine which relations should be
+ * truncated.
+ */
+void
+register_temprel_modify(Oid relid)
+{
+	/*
+	 * Init modified_temprels.
+	 * max_locks_per_xact is used as a guess to the amount of relations
+	 * that normally is accessed in a single transaction.
+	 */
+	if (modified_temprels == NULL)
+	{
+		HASHCTL		ctl;
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(Oid);
+		ctl.hcxt = TopTransactionContext;
+		modified_temprels = hash_create("Temp rels modified in this tx",
+									max_locks_per_xact,
+									&ctl, HASH_ELEM | HASH_BLOBS);
+	}
+
+	(void) hash_search(modified_temprels, &relid, HASH_ENTER, NULL);
+}
+
 /*
  * Unregister any ON COMMIT action when a relation is deleted.
  *
@@ -14448,14 +14482,19 @@ PreCommit_on_commit_actions(void)
 				/* Do nothing (there shouldn't be such entries, actually) */
 				break;
 			case ONCOMMIT_DELETE_ROWS:
-
 				/*
-				 * If this transaction hasn't accessed any temporary
-				 * relations, we can skip truncating ON COMMIT DELETE ROWS
-				 * tables, as they must still be empty.
+				 * We will truncate only relations that we opened with lock
+				 * stronger then AccessShare (so possibly were modified).
 				 */
-				if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
-					oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				if (modified_temprels != NULL)
+				{
+					bool modified;
+					(void) hash_search(modified_temprels, &oc->relid,
+									HASH_FIND, &modified);
+
+					if (modified)
+						oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid);
+				}
 				break;
 			case ONCOMMIT_DROP:
 				oids_to_drop = lappend_oid(oids_to_drop, oc->relid);
@@ -14559,6 +14598,12 @@ AtEOXact_on_commit_actions(bool isCommit)
 			cur_item = lnext(prev_item);
 		}
 	}
+
+	/*
+	 * Reset modified_temprels. Hash table itself will be released with
+	 * TopTransactionContext.
+	 */
+	modified_temprels = NULL;
 }
 
 /*
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index c8958766f1..9eee017c3d 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -3212,6 +3212,13 @@ AtPrepare_Locks(void)
 			locallock->lock = locallock->proclock->tag.myLock;
 		}
 
+		/*
+		 * Do not save any AccessShare locks as they will not be transfered to
+		 * a dummy PGPRPROC by PostPrepare_Locks().
+		 */
+		if (locallock->tag.mode == AccessShareLock)
+			continue;
+
 		/*
 		 * Arrange to not release any strong lock count held by this lock
 		 * entry.  We must retain the count until the prepared transaction is
@@ -3382,6 +3389,33 @@ PostPrepare_Locks(TransactionId xid)
 			if (proclock->releaseMask != proclock->holdMask)
 				elog(PANIC, "we seem to have dropped a bit somewhere");
 
+			/*
+			 * To be able to safely read from temp tables prepared transaction
+			 * should not hold AccessShare locks. So here we are releasing any
+			 * AccessShare locks without transfering them further to a dummy
+			 * PGPROC.  See also comments above MyXactFlags check in
+			 * PrepareTransaction().
+			 */
+			if (proclock->holdMask & LOCKBIT_ON(AccessShareLock))
+			{
+				LockMethod	lockMethodTable;
+				bool wakeupNeeded;
+
+				lockMethodTable = LockMethods[lock->tag.locktag_lockmethodid];
+				wakeupNeeded = UnGrantLock(lock, AccessShareLock,
+										   proclock, lockMethodTable);
+
+				CleanUpLock(lock, proclock, lockMethodTable,
+							LockTagHashCode(&lock->tag), wakeupNeeded);
+
+				/*
+				 * If we were holding lock only in AccessShare mode than
+				 * there is nothing to pass to a new PGPROC.
+				 */
+				if (proclock->holdMask == 0)
+					continue;
+			}
+
 			/*
 			 * We cannot simply modify proclock->tag.myProc to reassign
 			 * ownership of the lock, because that's part of the hash key and
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 96927b900d..f3c75e70a6 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -80,6 +80,7 @@ extern void register_on_commit_action(Oid relid, OnCommitAction action);
 extern void remove_on_commit_action(Oid relid);
 
 extern void PreCommit_on_commit_actions(void);
+extern void register_temprel_modify(Oid relid);
 extern void AtEOXact_on_commit_actions(bool isCommit);
 extern void AtEOSubXact_on_commit_actions(bool isCommit,
 							  SubTransactionId mySubid,
diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out
index eb77c18788..14ea813342 100644
--- a/src/test/regress/expected/prepared_xacts.out
+++ b/src/test/regress/expected/prepared_xacts.out
@@ -241,6 +241,36 @@ SELECT * FROM pxtest3;
 ERROR:  relation "pxtest3" does not exist
 LINE 1: SELECT * FROM pxtest3;
                       ^
+-- Read-only access to temporary table is allowed from a prepared
+-- transaction
+create temp table twophase_tab_preparable (a int);
+begin;
+select a from twophase_tab_preparable;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_preparable';
+-- prepared transactions should not hold AccessShare locks
+create table twophase_tab_testdrop1 (a int);
+create table twophase_tab_testdrop2 (a int);
+begin;
+select a from twophase_tab_testdrop1;
+ a 
+---
+(0 rows)
+
+select a from twophase_tab_testdrop2;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_testdrop';
+drop table twophase_tab_testdrop1;
+\c -
+drop table twophase_tab_testdrop2;
+commit prepared 'twophase_tab_preparable';
+commit prepared 'twophase_tab_testdrop';
 -- There should be no prepared transactions
 SELECT gid FROM pg_prepared_xacts;
  gid 
diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out
index 0857d259e0..b7f0f56818 100644
--- a/src/test/regress/expected/prepared_xacts_1.out
+++ b/src/test/regress/expected/prepared_xacts_1.out
@@ -235,6 +235,42 @@ SELECT * FROM pxtest3;
 -----
 (0 rows)
 
+-- Read-only access to temporary table is allowed from a prepared
+-- transaction
+create temp table twophase_tab_preparable (a int);
+begin;
+select a from twophase_tab_preparable;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_preparable';
+ERROR:  prepared transactions are disabled
+HINT:  Set max_prepared_transactions to a nonzero value.
+-- prepared transactions should not hold AccessShare locks
+create table twophase_tab_testdrop1 (a int);
+create table twophase_tab_testdrop2 (a int);
+begin;
+select a from twophase_tab_testdrop1;
+ a 
+---
+(0 rows)
+
+select a from twophase_tab_testdrop2;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_testdrop';
+ERROR:  prepared transactions are disabled
+HINT:  Set max_prepared_transactions to a nonzero value.
+drop table twophase_tab_testdrop1;
+\c -
+drop table twophase_tab_testdrop2;
+commit prepared 'twophase_tab_preparable';
+ERROR:  prepared transaction with identifier "twophase_tab_preparable" does not exist
+commit prepared 'twophase_tab_testdrop';
+ERROR:  prepared transaction with identifier "twophase_tab_testdrop" does not exist
 -- There should be no prepared transactions
 SELECT gid FROM pg_prepared_xacts;
  gid 
diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out
index ad7d558191..1f4a342f75 100644
--- a/src/test/regress/expected/temp.out
+++ b/src/test/regress/expected/temp.out
@@ -336,17 +336,9 @@ begin;
 create sequence pg_temp.twophase_seq;
 prepare transaction 'twophase_sequence';
 ERROR:  cannot PREPARE a transaction that has operated on temporary objects
--- Temporary tables cannot be used with two-phase commit.
+-- Temporary tables cannot be modified by prepared transaction.
 create temp table twophase_tab (a int);
 begin;
-select a from twophase_tab;
- a 
----
-(0 rows)
-
-prepare transaction 'twophase_tab';
-ERROR:  cannot PREPARE a transaction that has operated on temporary objects
-begin;
 insert into twophase_tab values (1);
 prepare transaction 'twophase_tab';
 ERROR:  cannot PREPARE a transaction that has operated on temporary objects
diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql
index d8249a27dc..59c5435239 100644
--- a/src/test/regress/sql/prepared_xacts.sql
+++ b/src/test/regress/sql/prepared_xacts.sql
@@ -149,6 +149,26 @@ SELECT gid FROM pg_prepared_xacts;
 COMMIT PREPARED 'regress-two';
 SELECT * FROM pxtest3;
 
+-- Read-only access to temporary table is allowed from a prepared
+-- transaction
+create temp table twophase_tab_preparable (a int);
+begin;
+select a from twophase_tab_preparable;
+prepare transaction 'twophase_tab_preparable';
+
+-- prepared transactions should not hold AccessShare locks
+create table twophase_tab_testdrop1 (a int);
+create table twophase_tab_testdrop2 (a int);
+begin;
+select a from twophase_tab_testdrop1;
+select a from twophase_tab_testdrop2;
+prepare transaction 'twophase_tab_testdrop';
+drop table twophase_tab_testdrop1;
+\c -
+drop table twophase_tab_testdrop2;
+commit prepared 'twophase_tab_preparable';
+commit prepared 'twophase_tab_testdrop';
+
 -- There should be no prepared transactions
 SELECT gid FROM pg_prepared_xacts;
 
diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql
index e634ddb9ca..4643278d84 100644
--- a/src/test/regress/sql/temp.sql
+++ b/src/test/regress/sql/temp.sql
@@ -257,12 +257,9 @@ begin;
 create sequence pg_temp.twophase_seq;
 prepare transaction 'twophase_sequence';
 
--- Temporary tables cannot be used with two-phase commit.
+-- Temporary tables cannot be modified by prepared transaction.
 create temp table twophase_tab (a int);
 begin;
-select a from twophase_tab;
-prepare transaction 'twophase_tab';
-begin;
 insert into twophase_tab values (1);
 prepare transaction 'twophase_tab';
 begin;
#2Stas Kelvich
s.kelvich@postgrespro.ru
In reply to: Stas Kelvich (#1)
1 attachment(s)
Re: Read-only access to temp tables for 2PC transactions

On 14 May 2019, at 12:53, Stas Kelvich <s.kelvich@postgrespro.ru> wrote:

Hi,

That is an attempt number N+1 to relax checks for a temporary table access
in a transaction that is going to be prepared.

Konstantin Knizhnik made off-list review of this patch and spotted
few problems.

* Incorrect reasoning that ON COMMIT DELETE truncate mechanism
should be changed in order to allow preparing transactions with
read-only access to temp relations. It actually can be be leaved
as is. Things done in previous patch for ON COMMIT DELETE may be
a performance win, but not directly related to this topic so I've
deleted that part.

* Copy-paste error with check conditions in
relation_open/relation_try_open.

Fixed version attached.

--
Stas Kelvich
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

2PC-ro-temprels-v2.patchapplication/octet-stream; name=2PC-ro-temprels-v2.patch; x-unix-mode=0644Download
commit 0df9d036f3fd9b8a8e22b0101b4632b5a8f62ea0
Author: Stas Kelvich <stanconn@gmail.com>
Date:   Tue May 14 12:47:59 2019 +0300

    Read-only access to temp tables for 2PC transactions

diff --git a/src/backend/access/common/relation.c b/src/backend/access/common/relation.c
index 41a0051f88..9e35311ad4 100644
--- a/src/backend/access/common/relation.c
+++ b/src/backend/access/common/relation.c
@@ -23,6 +23,7 @@
 #include "access/relation.h"
 #include "access/xact.h"
 #include "catalog/namespace.h"
+#include "commands/tablecmds.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/lmgr.h"
@@ -69,9 +70,18 @@ relation_open(Oid relationId, LOCKMODE lockmode)
 		   IsBootstrapProcessingMode() ||
 		   CheckRelationLockedByMe(r, AccessShareLock, true));
 
-	/* Make note that we've accessed a temporary relation */
-	if (RelationUsesLocalBuffers(r))
+	/*
+	 * Make note that we've accessed a temporary relation with at least
+	 * RowShareLock lockmode. See also comments above MyXactFlags check
+	 * in PrepareTransaction().
+	 */
+	if (RelationUsesLocalBuffers(r) &&
+		(lockmode > AccessShareLock ||
+		 (lockmode == NoLock &&
+		  CheckRelationLockedByMe(r, RowShareLock, true))))
+	{
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+	}
 
 	pgstat_initstats(r);
 
@@ -119,9 +129,18 @@ try_relation_open(Oid relationId, LOCKMODE lockmode)
 	Assert(lockmode != NoLock ||
 		   CheckRelationLockedByMe(r, AccessShareLock, true));
 
-	/* Make note that we've accessed a temporary relation */
-	if (RelationUsesLocalBuffers(r))
+	/*
+	 * Make note that we've accessed a temporary relation with at least
+	 * RowShareLock lockmode. See also comments above MyXactFlags check
+	 * in PrepareTransaction().
+	 */
+	if (RelationUsesLocalBuffers(r) &&
+		(lockmode > AccessShareLock ||
+		 (lockmode == NoLock &&
+		  CheckRelationLockedByMe(r, RowShareLock, true))))
+	{
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
+	}
 
 	pgstat_initstats(r);
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 20feeec327..39a4c1106d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2352,12 +2352,14 @@ PrepareTransaction(void)
 	/* NOTIFY will be handled below */
 
 	/*
-	 * Don't allow PREPARE TRANSACTION if we've accessed a temporary table in
+	 * Don't allow PREPARE TRANSACTION if we've modified a temporary table in
 	 * this transaction.  Having the prepared xact hold locks on another
 	 * backend's temp table seems a bad idea --- for instance it would prevent
 	 * the backend from exiting.  There are other problems too, such as how to
 	 * clean up the source backend's local buffers and ON COMMIT state if the
-	 * prepared xact includes a DROP of a temp table.
+	 * prepared xact includes a DROP of a temp table.  Special case of reading
+	 * from a temporary table is allowed by dropping AccessShare locks during
+	 * PREPARE.
 	 *
 	 * Other objects types, like functions, operators or extensions, share the
 	 * same restriction as they should not be created, locked or dropped as
@@ -2368,8 +2370,9 @@ PrepareTransaction(void)
 	 * 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.
+	 * cases besides reading from a temp table, such as a temp table created
+	 * and dropped all within the transaction.  That seems to require much more
+	 * bookkeeping though.
 	 */
 	if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
 		ereport(ERROR,
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index c8958766f1..9eee017c3d 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -3212,6 +3212,13 @@ AtPrepare_Locks(void)
 			locallock->lock = locallock->proclock->tag.myLock;
 		}
 
+		/*
+		 * Do not save any AccessShare locks as they will not be transfered to
+		 * a dummy PGPRPROC by PostPrepare_Locks().
+		 */
+		if (locallock->tag.mode == AccessShareLock)
+			continue;
+
 		/*
 		 * Arrange to not release any strong lock count held by this lock
 		 * entry.  We must retain the count until the prepared transaction is
@@ -3382,6 +3389,33 @@ PostPrepare_Locks(TransactionId xid)
 			if (proclock->releaseMask != proclock->holdMask)
 				elog(PANIC, "we seem to have dropped a bit somewhere");
 
+			/*
+			 * To be able to safely read from temp tables prepared transaction
+			 * should not hold AccessShare locks. So here we are releasing any
+			 * AccessShare locks without transfering them further to a dummy
+			 * PGPROC.  See also comments above MyXactFlags check in
+			 * PrepareTransaction().
+			 */
+			if (proclock->holdMask & LOCKBIT_ON(AccessShareLock))
+			{
+				LockMethod	lockMethodTable;
+				bool wakeupNeeded;
+
+				lockMethodTable = LockMethods[lock->tag.locktag_lockmethodid];
+				wakeupNeeded = UnGrantLock(lock, AccessShareLock,
+										   proclock, lockMethodTable);
+
+				CleanUpLock(lock, proclock, lockMethodTable,
+							LockTagHashCode(&lock->tag), wakeupNeeded);
+
+				/*
+				 * If we were holding lock only in AccessShare mode than
+				 * there is nothing to pass to a new PGPROC.
+				 */
+				if (proclock->holdMask == 0)
+					continue;
+			}
+
 			/*
 			 * We cannot simply modify proclock->tag.myProc to reassign
 			 * ownership of the lock, because that's part of the hash key and
diff --git a/src/test/regress/expected/prepared_xacts.out b/src/test/regress/expected/prepared_xacts.out
index eb77c18788..14ea813342 100644
--- a/src/test/regress/expected/prepared_xacts.out
+++ b/src/test/regress/expected/prepared_xacts.out
@@ -241,6 +241,36 @@ SELECT * FROM pxtest3;
 ERROR:  relation "pxtest3" does not exist
 LINE 1: SELECT * FROM pxtest3;
                       ^
+-- Read-only access to temporary table is allowed from a prepared
+-- transaction
+create temp table twophase_tab_preparable (a int);
+begin;
+select a from twophase_tab_preparable;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_preparable';
+-- prepared transactions should not hold AccessShare locks
+create table twophase_tab_testdrop1 (a int);
+create table twophase_tab_testdrop2 (a int);
+begin;
+select a from twophase_tab_testdrop1;
+ a 
+---
+(0 rows)
+
+select a from twophase_tab_testdrop2;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_testdrop';
+drop table twophase_tab_testdrop1;
+\c -
+drop table twophase_tab_testdrop2;
+commit prepared 'twophase_tab_preparable';
+commit prepared 'twophase_tab_testdrop';
 -- There should be no prepared transactions
 SELECT gid FROM pg_prepared_xacts;
  gid 
diff --git a/src/test/regress/expected/prepared_xacts_1.out b/src/test/regress/expected/prepared_xacts_1.out
index 0857d259e0..b7f0f56818 100644
--- a/src/test/regress/expected/prepared_xacts_1.out
+++ b/src/test/regress/expected/prepared_xacts_1.out
@@ -235,6 +235,42 @@ SELECT * FROM pxtest3;
 -----
 (0 rows)
 
+-- Read-only access to temporary table is allowed from a prepared
+-- transaction
+create temp table twophase_tab_preparable (a int);
+begin;
+select a from twophase_tab_preparable;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_preparable';
+ERROR:  prepared transactions are disabled
+HINT:  Set max_prepared_transactions to a nonzero value.
+-- prepared transactions should not hold AccessShare locks
+create table twophase_tab_testdrop1 (a int);
+create table twophase_tab_testdrop2 (a int);
+begin;
+select a from twophase_tab_testdrop1;
+ a 
+---
+(0 rows)
+
+select a from twophase_tab_testdrop2;
+ a 
+---
+(0 rows)
+
+prepare transaction 'twophase_tab_testdrop';
+ERROR:  prepared transactions are disabled
+HINT:  Set max_prepared_transactions to a nonzero value.
+drop table twophase_tab_testdrop1;
+\c -
+drop table twophase_tab_testdrop2;
+commit prepared 'twophase_tab_preparable';
+ERROR:  prepared transaction with identifier "twophase_tab_preparable" does not exist
+commit prepared 'twophase_tab_testdrop';
+ERROR:  prepared transaction with identifier "twophase_tab_testdrop" does not exist
 -- There should be no prepared transactions
 SELECT gid FROM pg_prepared_xacts;
  gid 
diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out
index ad7d558191..1f4a342f75 100644
--- a/src/test/regress/expected/temp.out
+++ b/src/test/regress/expected/temp.out
@@ -336,17 +336,9 @@ begin;
 create sequence pg_temp.twophase_seq;
 prepare transaction 'twophase_sequence';
 ERROR:  cannot PREPARE a transaction that has operated on temporary objects
--- Temporary tables cannot be used with two-phase commit.
+-- Temporary tables cannot be modified by prepared transaction.
 create temp table twophase_tab (a int);
 begin;
-select a from twophase_tab;
- a 
----
-(0 rows)
-
-prepare transaction 'twophase_tab';
-ERROR:  cannot PREPARE a transaction that has operated on temporary objects
-begin;
 insert into twophase_tab values (1);
 prepare transaction 'twophase_tab';
 ERROR:  cannot PREPARE a transaction that has operated on temporary objects
diff --git a/src/test/regress/sql/prepared_xacts.sql b/src/test/regress/sql/prepared_xacts.sql
index d8249a27dc..59c5435239 100644
--- a/src/test/regress/sql/prepared_xacts.sql
+++ b/src/test/regress/sql/prepared_xacts.sql
@@ -149,6 +149,26 @@ SELECT gid FROM pg_prepared_xacts;
 COMMIT PREPARED 'regress-two';
 SELECT * FROM pxtest3;
 
+-- Read-only access to temporary table is allowed from a prepared
+-- transaction
+create temp table twophase_tab_preparable (a int);
+begin;
+select a from twophase_tab_preparable;
+prepare transaction 'twophase_tab_preparable';
+
+-- prepared transactions should not hold AccessShare locks
+create table twophase_tab_testdrop1 (a int);
+create table twophase_tab_testdrop2 (a int);
+begin;
+select a from twophase_tab_testdrop1;
+select a from twophase_tab_testdrop2;
+prepare transaction 'twophase_tab_testdrop';
+drop table twophase_tab_testdrop1;
+\c -
+drop table twophase_tab_testdrop2;
+commit prepared 'twophase_tab_preparable';
+commit prepared 'twophase_tab_testdrop';
+
 -- There should be no prepared transactions
 SELECT gid FROM pg_prepared_xacts;
 
diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql
index e634ddb9ca..4643278d84 100644
--- a/src/test/regress/sql/temp.sql
+++ b/src/test/regress/sql/temp.sql
@@ -257,12 +257,9 @@ begin;
 create sequence pg_temp.twophase_seq;
 prepare transaction 'twophase_sequence';
 
--- Temporary tables cannot be used with two-phase commit.
+-- Temporary tables cannot be modified by prepared transaction.
 create temp table twophase_tab (a int);
 begin;
-select a from twophase_tab;
-prepare transaction 'twophase_tab';
-begin;
 insert into twophase_tab values (1);
 prepare transaction 'twophase_tab';
 begin;
#3Simon Riggs
simon@2ndquadrant.com
In reply to: Stas Kelvich (#1)
Re: Read-only access to temp tables for 2PC transactions

On Tue, 14 May 2019 at 10:53, Stas Kelvich <s.kelvich@postgrespro.ru> wrote:

One of the problems regarding the use of temporary tables in prepared
transactions
is that such transaction will hold locks for a temporary table after being
prepared.
That locks will prevent the backend from exiting since it will fail to
acquire lock
needed to delete temp table during exit. Also, re-acquiring such lock
after server
restart seems like an ill-defined operation.

...

Any thoughts?

It occurs to me that there is no problem to solve here.

When we PREPARE, it is because we expect to COMMIT or ABORT soon afterwards.

If we are using an external transaction manager, the session is idle while
we wait for the manager to decide whether to commit or abort. Or the
session is disconnected or server is crashes. Either way, nothing happens
between PREPARE and resolution. So there is no need at all for locking of
temporary tables after the prepare.

The ONLY case where this matters is if someone does a PREPARE and then
starts doing other work on the session. Which makes no sense in the normal
workflow of a session. I'm sure there are tests that do that, but those
tests are unrepresentative of sensible usage.

If you were using session temporary tables while using a transaction mode
pool then you're already going to have problems, so that aspect is a
non-issue.

So I think we should ban this by definition. Say that we expect that you
won't do any work on the session until COMMIT/ABORT. That means we can then
drop locks on sesion temporary tables and drop on-commit temp tables when
we hit the prepare, not try and hold them for later.

A patch is needed to implement the above, but I think we can forget the
current patch as not needed.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#4Andres Freund
andres@anarazel.de
In reply to: Simon Riggs (#3)
Re: Read-only access to temp tables for 2PC transactions

Hi,

On 2019-05-23 12:36:09 +0100, Simon Riggs wrote:

The ONLY case where this matters is if someone does a PREPARE and then
starts doing other work on the session. Which makes no sense in the normal
workflow of a session. I'm sure there are tests that do that, but those
tests are unrepresentative of sensible usage.

That's extremely common.

There's no way we can forbid using session after 2PC unconditionally,
it'd break most users of 2PC.

Greetings,

Andres Freund

#5Michael Paquier
michael@paquier.xyz
In reply to: Andres Freund (#4)
Re: Read-only access to temp tables for 2PC transactions

On Thu, May 23, 2019 at 08:54:59AM -0700, Andres Freund wrote:

On 2019-05-23 12:36:09 +0100, Simon Riggs wrote:

The ONLY case where this matters is if someone does a PREPARE and then
starts doing other work on the session. Which makes no sense in the normal
workflow of a session. I'm sure there are tests that do that, but those
tests are unrepresentative of sensible usage.

That's extremely common.

There's no way we can forbid using session after 2PC unconditionally,
it'd break most users of 2PC.

This does not break Postgres-XC or XL as their inner parts with a
COMMIT involving multiple write nodes issue a set of PREPARE
TRANSACTION followed by an immediate COMMIT PREPARED which are
transparent for the user, so the point of Simon looks sensible from
this angle. Howewer, I much agree with Andres that it is very common
to have PREPARE and COMMIT PREPARED issued with different sessions. I
am not much into the details of XA-compliant drivers, but I think that
having us lose this property would be the source of many complaints.
--
Michael

#6Simon Riggs
simon@2ndquadrant.com
In reply to: Andres Freund (#4)
Re: Read-only access to temp tables for 2PC transactions

On Thu, 23 May 2019 at 16:55, Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2019-05-23 12:36:09 +0100, Simon Riggs wrote:

The ONLY case where this matters is if someone does a PREPARE and then
starts doing other work on the session. Which makes no sense in the

normal

workflow of a session. I'm sure there are tests that do that, but those
tests are unrepresentative of sensible usage.

That's extremely common.

Not at all.

There's no way we can forbid using session after 2PC unconditionally,
it'd break most users of 2PC.

Since we disagree, can you provide more information about this usage
pattern?

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#7Simon Riggs
simon@2ndquadrant.com
In reply to: Michael Paquier (#5)
Re: Read-only access to temp tables for 2PC transactions

On Fri, 24 May 2019 at 01:39, Michael Paquier <michael@paquier.xyz> wrote:

On Thu, May 23, 2019 at 08:54:59AM -0700, Andres Freund wrote:

On 2019-05-23 12:36:09 +0100, Simon Riggs wrote:

The ONLY case where this matters is if someone does a PREPARE and then
starts doing other work on the session. Which makes no sense in the

normal

workflow of a session. I'm sure there are tests that do that, but those
tests are unrepresentative of sensible usage.

That's extremely common.

There's no way we can forbid using session after 2PC unconditionally,
it'd break most users of 2PC.

This does not break Postgres-XC or XL as their inner parts with a
COMMIT involving multiple write nodes issue a set of PREPARE
TRANSACTION followed by an immediate COMMIT PREPARED which are
transparent for the user, so the point of Simon looks sensible from
this angle.

Maybe, but I am not discussing other products since they can be changed
without discussion here.

Howewer, I much agree with Andres that it is very common
to have PREPARE and COMMIT PREPARED issued with different sessions. I
am not much into the details of XA-compliant drivers, but I think that
having us lose this property would be the source of many complaints.

Yes, it is *very* common to have PREPARE and COMMIT PREPARED issued from
different sessions. That is the main usage in a session pool and not the
point I made.

There are two usage patterns, with a correlation between the way 2PC and
temp tables work:

Transaction-mode session-pool: (Most common usage mode)
* No usage of session-level temp tables (because that wouldn't work)
* 2PC with PREPARE and COMMIT PREPARED on different sessions
* No reason at all to hold locks on temp table after PREPARE

Session-mode (Less frequent usage mode)
* Usage of session-level temp tables
* 2PC on same session only, i.e. no usage of session between PREPARE and
COMMIT PREPARED (Simon's observation)
* No reason at all to hold locks on temp table after PREPARE (Simon's
conclusion)

I'd like to hear from anyone that thinks my observation is incorrect and to
explain their usage pattern so we can understand why they think they would
execute further SQL between PREPARE and COMMIT PREPARED when using a single
session, while at the same time using temp tables.

If there really is a usage pattern there we should take note of, then I
suggest we introduce a parameter that allows temp table locks to be dropped
at PREPARE, so that we can use 2PC and Temp Tables with ease, for those
that want it.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#8Konstantin Knizhnik
k.knizhnik@postgrespro.ru
In reply to: Simon Riggs (#7)
Re: Read-only access to temp tables for 2PC transactions

On 24.05.2019 11:52, Simon Riggs wrote:

On Fri, 24 May 2019 at 01:39, Michael Paquier <michael@paquier.xyz
<mailto:michael@paquier.xyz>> wrote:

On Thu, May 23, 2019 at 08:54:59AM -0700, Andres Freund wrote:

On 2019-05-23 12:36:09 +0100, Simon Riggs wrote:

The ONLY case where this matters is if someone does a PREPARE

and then

starts doing other work on the session. Which makes no sense in

the normal

workflow of a session. I'm sure there are tests that do that,

but those

tests are unrepresentative of sensible usage.

That's extremely common.

There's no way we can forbid using session after 2PC

unconditionally,

it'd break most users of 2PC.

This does not break Postgres-XC or XL as their inner parts with a
COMMIT involving multiple write nodes issue a set of PREPARE
TRANSACTION followed by an immediate COMMIT PREPARED which are
transparent for the user, so the point of Simon looks sensible from
this angle.

Maybe, but I am not discussing other products since they can be
changed without discussion here.

Howewer, I much agree with Andres that it is very common
to have PREPARE and COMMIT PREPARED issued with different sessions.  I
am not much into the details of XA-compliant drivers, but I think that
having us lose this property would be the source of many complaints.

Yes, it is *very* common to have PREPARE and COMMIT PREPARED issued
from different sessions. That is the main usage in a session pool and
not the point I made.

There are two usage patterns, with a correlation between the way 2PC
and temp tables work:

Transaction-mode session-pool: (Most common usage mode)
* No usage of session-level temp tables (because that wouldn't work)
* 2PC with PREPARE and COMMIT PREPARED on different sessions
* No reason at all to hold locks on temp table after PREPARE

Session-mode (Less frequent usage mode)
* Usage of session-level temp tables
* 2PC on same session only, i.e. no usage of session between PREPARE
and COMMIT PREPARED (Simon's observation)
* No reason at all to hold locks on temp table after PREPARE (Simon's
conclusion)

I'd like to hear from anyone that thinks my observation is incorrect
and to explain their usage pattern so we can understand why they think
they would execute further SQL between PREPARE and COMMIT PREPARED
when using a single session, while at the same time using temp tables.

If there really is a usage pattern there we should take note of, then
I suggest we introduce a parameter that allows temp table locks to be
dropped at PREPARE, so that we can use 2PC and Temp Tables with ease,
for those that want it.

--
Simon Riggs http://www.2ndQuadrant.com/ <http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

From my point of view releasing all temporary table locks after
preparing of 2PC transaction is not technically possible:
assume that this transaction has  updated some tuples of temporary table
- them are not visible to other transactions until 2PC is committed,
but since lock is removed, other transactions can update the same tuple.

Prohibiting transaction to do anything else  except COMMIT/ROLLBACK
PREPARED after preparing transaction seems to be too voluntaristic decision.
I do not think that "That's extremely common", but I almost sure that
there are such cases.

The safe scenario is when temporary table is created and dropped inside
transaction (table created with ON COMMIT DROP). But there is still one
issue with this scenario: first creation of temporary table cause
creation of
pg_temp namespace and it can not be undone. Another possible scenario is
temporary table created outside transaction with ON COMMIT DELETE. In
this case truncation of table on prepare will also release all locks.

Pure read-only access to temporary tables seems to be not so useful, 
because before reading something from temporary table, we have to write
something to it. And if reading of temporary table is wrapped in 2PC,
then most likely writing to temporary table also has to be wrapped in
2PC, which is not possible with the proposed solution.

--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#9Andres Freund
andres@anarazel.de
In reply to: Konstantin Knizhnik (#8)
Re: Read-only access to temp tables for 2PC transactions

Hi,

On 2019-05-24 19:37:15 +0300, Konstantin Knizhnik wrote:

From my point of view releasing all temporary table locks after preparing of
2PC transaction is not technically possible:
assume that this transaction has� updated some tuples of temporary table - them
are not visible to other transactions until 2PC is committed,
but since lock is removed, other transactions can update the same tuple.

I don't think tuple level actions are the problem? Those doesn't require
table level locks to be held.

Generally, I fail to see how locks themselves are the problem. The
problem are the catalog entries for the temp table, the relation forks,
and the fact that a session basically couldn't drop (and if created in
that transaction, use) etc the temp table after the PREPARE.

Greetings,

Andres Freund

#10Simon Riggs
simon@2ndquadrant.com
In reply to: Andres Freund (#9)
Re: Read-only access to temp tables for 2PC transactions

On Fri, 24 May 2019 at 18:09, Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2019-05-24 19:37:15 +0300, Konstantin Knizhnik wrote:

From my point of view releasing all temporary table locks after

preparing of

2PC transaction is not technically possible:
assume that this transaction has updated some tuples of temporary table

- them

are not visible to other transactions until 2PC is committed,
but since lock is removed, other transactions can update the same tuple.

I don't think tuple level actions are the problem? Those doesn't require
table level locks to be held.

Generally, I fail to see how locks themselves are the problem.

Agreed

The
problem are the catalog entries for the temp table, the relation forks,
and the fact that a session basically couldn't drop (and if created in
that transaction, use) etc the temp table after the PREPARE.

I don't see there is a problem here, but run out of time to explain more,
for a week.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services