/*
* Note: GetLockConflicts() never reports our own xid, hence we need not
- * check for that. Also, prepared xacts are not reported, which is fine
- * since they certainly aren't going to do anything anymore.
+ * check for that. Also, prepared xacts are reported and awaited.
*/
/* Finally wait for each such transaction to complete */
* so use of this function has to be thought about carefully.
*
* Note we never include the current xact's vxid in the result array,
- * since an xact never blocks itself. Also, prepared transactions are
- * ignored, which is a bit more debatable but is appropriate for current
- * uses of the result.
+ * since an xact never blocks itself.
*/
VirtualTransactionId *
GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode, int *countp)
/*
* Allocate memory to store results, and fill with InvalidVXID. We only
- * need enough space for MaxBackends + a terminator, since prepared xacts
- * don't count. InHotStandby allocate once in TopMemoryContext.
+ * need enough space for MaxBackends + max_prepared_xacts + a terminator.
+ * InHotStandby allocate once in TopMemoryContext.
*/
if (InHotStandby)
{
if (vxids == NULL)
vxids = (VirtualTransactionId *)
MemoryContextAlloc(TopMemoryContext,
- sizeof(VirtualTransactionId) * (MaxBackends + 1));
+ sizeof(VirtualTransactionId) *
+ (MaxBackends + max_prepared_xacts + 1));
}
else
vxids = (VirtualTransactionId *)
- palloc0(sizeof(VirtualTransactionId) * (MaxBackends + 1));
+ palloc0(sizeof(VirtualTransactionId) *
+ (MaxBackends + max_prepared_xacts + 1));
/* Compute hash code and partition lock, and look up conflicting modes. */
hashcode = LockTagHashCode(locktag);
/* Conflict! */
GET_VXID_FROM_PGPROC(vxid, *proc);
- /*
- * If we see an invalid VXID, then either the xact has already
- * committed (or aborted), or it's a prepared xact. In either
- * case we may ignore it.
- */
if (VirtualTransactionIdIsValid(vxid))
vxids[count++] = vxid;
+ /* else, xact already committed or aborted */
/* No need to examine remaining slots. */
break;
GET_VXID_FROM_PGPROC(vxid, *proc);
- /*
- * If we see an invalid VXID, then either the xact has already
- * committed (or aborted), or it's a prepared xact. In either
- * case we may ignore it.
- */
if (VirtualTransactionIdIsValid(vxid))
{
int i;
if (i >= fast_count)
vxids[count++] = vxid;
}
+ /* else, xact already committed or aborted */
}
}
LWLockRelease(partitionLock);
- if (count > MaxBackends) /* should never happen */
+ if (count > MaxBackends + max_prepared_xacts) /* should never happen */
elog(PANIC, "too many conflicting locks found");
vxids[count].backendId = InvalidBackendId;
Assert(VirtualTransactionIdIsValid(vxid));
+ if (VirtualTransactionIdIsPreparedXact(vxid))
+ {
+ LockAcquireResult lar;
+
+ /*
+ * Prepared transactions don't hold vxid locks. The
+ * LocalTransactionId is always a normal, locked XID.
+ */
+ SET_LOCKTAG_TRANSACTION(tag, vxid.localTransactionId);
+ lar = LockAcquire(&tag, ShareLock, false, !wait);
+ if (lar != LOCKACQUIRE_NOT_AVAIL)
+ LockRelease(&tag, ShareLock, false);
+ return lar != LOCKACQUIRE_NOT_AVAIL;
+ }
+
SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid);
/*
/*
* Top-level transactions are identified by VirtualTransactionIDs comprising
- * the BackendId of the backend running the xact, plus a locally-assigned
- * LocalTransactionId. These are guaranteed unique over the short term,
- * but will be reused after a database restart; hence they should never
- * be stored on disk.
+ * PGPROC fields backendId and lxid. For prepared transactions, the
+ * LocalTransactionId is an ordinary XID. These are guaranteed unique over
+ * the short term, but will be reused after a database restart or XID
+ * wraparound; hence they should never be stored on disk.
*
* Note that struct VirtualTransactionId can not be assumed to be atomically
* assignable as a whole. However, type LocalTransactionId is assumed to
*/
typedef struct
{
- BackendId backendId; /* determined at backend startup */
- LocalTransactionId localTransactionId; /* backend-local transaction id */
+ BackendId backendId; /* backendId from PGPROC */
+ LocalTransactionId localTransactionId; /* lxid from PGPROC */
} VirtualTransactionId;
#define InvalidLocalTransactionId 0
#define LocalTransactionIdIsValid(lxid) ((lxid) != InvalidLocalTransactionId)
#define VirtualTransactionIdIsValid(vxid) \
- (((vxid).backendId != InvalidBackendId) && \
- LocalTransactionIdIsValid((vxid).localTransactionId))
+ (LocalTransactionIdIsValid((vxid).localTransactionId))
+#define VirtualTransactionIdIsPreparedXact(vxid) \
+ ((vxid).backendId == InvalidBackendId)
#define VirtualTransactionIdEquals(vxid1, vxid2) \
((vxid1).backendId == (vxid2).backendId && \
(vxid1).localTransactionId == (vxid2).localTransactionId)
check: all
$(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule
-# Versions of the check tests that include the prepared-transactions test
-# It only makes sense to run these if set up to use prepared transactions,
-# via TEMP_CONFIG for the check case, or via the postgresql.conf for the
-# installcheck case.
+# Non-default tests. It only makes sense to run these if set up to use
+# prepared transactions, via TEMP_CONFIG for the check case, or via the
+# postgresql.conf for the installcheck case.
installcheck-prepared-txns: all temp-install
- $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule prepared-transactions
+ $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic
check-prepared-txns: all temp-install
- $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions
+ $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic
./pg_isolation_regress fk-contention fk-deadlock
(look into the specs/ subdirectory to see the available tests).
-The prepared-transactions test requires the server's
-max_prepared_transactions parameter to be set to at least 3; therefore it
-is not run by default. To include it in the test run, use
+Certain tests require the server's max_prepared_transactions parameter to be
+set to at least 3; therefore they are not run by default. To include them in
+the test run, use
make check-prepared-txns
or
make installcheck-prepared-txns
--- /dev/null
+Parsed test spec with 2 sessions
+
+starting permutation: w1 p1 cic2 c1 r2
+step w1: BEGIN; INSERT INTO cic_test VALUES (1);
+step p1: PREPARE TRANSACTION 's1';
+step cic2:
+ CREATE INDEX CONCURRENTLY on cic_test(a);
+
+ERROR: canceling statement due to lock timeout
+step c1: COMMIT PREPARED 's1';
+step r2:
+ SET enable_seqscan to off;
+ SET enable_bitmapscan to off;
+ SELECT * FROM cic_test WHERE a = 1;
+
+a
+
+1
--- /dev/null
+# This test verifies that CREATE INDEX CONCURRENTLY interacts with prepared
+# transactions correctly.
+setup
+{
+ CREATE TABLE cic_test (a int);
+}
+
+teardown
+{
+ DROP TABLE cic_test;
+}
+
+
+# Sessions for CREATE INDEX CONCURRENTLY test
+session "s1"
+step "w1" { BEGIN; INSERT INTO cic_test VALUES (1); }
+step "p1" { PREPARE TRANSACTION 's1'; }
+step "c1" { COMMIT PREPARED 's1'; }
+
+session "s2"
+# The isolation tester never recognizes that a lock of s1 blocks s2, because a
+# prepared transaction's locks have no pid associated. While there's a slight
+# chance of timeout while waiting for an autovacuum-held lock, that wouldn't
+# change the output. Hence, no timeout is too short.
+setup { SET lock_timeout = 10; }
+step "cic2"
+{
+ CREATE INDEX CONCURRENTLY on cic_test(a);
+}
+step "r2"
+{
+ SET enable_seqscan to off;
+ SET enable_bitmapscan to off;
+ SELECT * FROM cic_test WHERE a = 1;
+}
+
+permutation "w1" "p1" "cic2" "c1" "r2"