Skip to content

Commit d683d65

Browse files
committed
Fix CREATE INDEX CONCURRENTLY for simultaneous prepared transactions.
In a cluster having used CREATE INDEX CONCURRENTLY while having enabled prepared transactions, queries that use the resulting index can silently fail to find rows. Fix this for future CREATE INDEX CONCURRENTLY by making it wait for prepared transactions like it waits for ordinary transactions. This expands the VirtualTransactionId structure domain to admit prepared transactions. It may be necessary to reindex to recover from past occurrences. Back-patch to 9.5 (all supported versions). Andrey Borodin, reviewed (in earlier versions) by Tom Lane and Michael Paquier. Discussion: https://postgr.es/m/2E712143-97F7-4890-B470-4A35142ABC82@yandex-team.ru
1 parent ea3164a commit d683d65

File tree

7 files changed

+98
-38
lines changed

7 files changed

+98
-38
lines changed

src/backend/storage/lmgr/lmgr.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -846,8 +846,7 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode)
846846

847847
/*
848848
* Note: GetLockConflicts() never reports our own xid, hence we need not
849-
* check for that. Also, prepared xacts are not reported, which is fine
850-
* since they certainly aren't going to do anything anymore.
849+
* check for that. Also, prepared xacts are reported and awaited.
851850
*/
852851

853852
/* Finally wait for each such transaction to complete */

src/backend/storage/lmgr/lock.c

+25-18
Original file line numberDiff line numberDiff line change
@@ -2791,9 +2791,7 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock)
27912791
* so use of this function has to be thought about carefully.
27922792
*
27932793
* Note we never include the current xact's vxid in the result array,
2794-
* since an xact never blocks itself. Also, prepared transactions are
2795-
* ignored, which is a bit more debatable but is appropriate for current
2796-
* uses of the result.
2794+
* since an xact never blocks itself.
27972795
*/
27982796
VirtualTransactionId *
27992797
GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
@@ -2818,19 +2816,21 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
28182816

28192817
/*
28202818
* Allocate memory to store results, and fill with InvalidVXID. We only
2821-
* need enough space for MaxBackends + a terminator, since prepared xacts
2822-
* don't count. InHotStandby allocate once in TopMemoryContext.
2819+
* need enough space for MaxBackends + max_prepared_xacts + a terminator.
2820+
* InHotStandby allocate once in TopMemoryContext.
28232821
*/
28242822
if (InHotStandby)
28252823
{
28262824
if (vxids == NULL)
28272825
vxids = (VirtualTransactionId *)
28282826
MemoryContextAlloc(TopMemoryContext,
2829-
sizeof(VirtualTransactionId) * (MaxBackends + 1));
2827+
sizeof(VirtualTransactionId) *
2828+
(MaxBackends + max_prepared_xacts + 1));
28302829
}
28312830
else
28322831
vxids = (VirtualTransactionId *)
2833-
palloc0(sizeof(VirtualTransactionId) * (MaxBackends + 1));
2832+
palloc0(sizeof(VirtualTransactionId) *
2833+
(MaxBackends + max_prepared_xacts + 1));
28342834

28352835
/* Compute hash code and partition lock, and look up conflicting modes. */
28362836
hashcode = LockTagHashCode(locktag);
@@ -2905,13 +2905,9 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
29052905
/* Conflict! */
29062906
GET_VXID_FROM_PGPROC(vxid, *proc);
29072907

2908-
/*
2909-
* If we see an invalid VXID, then either the xact has already
2910-
* committed (or aborted), or it's a prepared xact. In either
2911-
* case we may ignore it.
2912-
*/
29132908
if (VirtualTransactionIdIsValid(vxid))
29142909
vxids[count++] = vxid;
2910+
/* else, xact already committed or aborted */
29152911

29162912
/* No need to examine remaining slots. */
29172913
break;
@@ -2968,11 +2964,6 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
29682964

29692965
GET_VXID_FROM_PGPROC(vxid, *proc);
29702966

2971-
/*
2972-
* If we see an invalid VXID, then either the xact has already
2973-
* committed (or aborted), or it's a prepared xact. In either
2974-
* case we may ignore it.
2975-
*/
29762967
if (VirtualTransactionIdIsValid(vxid))
29772968
{
29782969
int i;
@@ -2984,6 +2975,7 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
29842975
if (i >= fast_count)
29852976
vxids[count++] = vxid;
29862977
}
2978+
/* else, xact already committed or aborted */
29872979
}
29882980
}
29892981

@@ -2993,7 +2985,7 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
29932985

29942986
LWLockRelease(partitionLock);
29952987

2996-
if (count > MaxBackends) /* should never happen */
2988+
if (count > MaxBackends + max_prepared_xacts) /* should never happen */
29972989
elog(PANIC, "too many conflicting locks found");
29982990

29992991
vxids[count].backendId = InvalidBackendId;
@@ -4349,6 +4341,21 @@ VirtualXactLock(VirtualTransactionId vxid, bool wait)
43494341

43504342
Assert(VirtualTransactionIdIsValid(vxid));
43514343

4344+
if (VirtualTransactionIdIsPreparedXact(vxid))
4345+
{
4346+
LockAcquireResult lar;
4347+
4348+
/*
4349+
* Prepared transactions don't hold vxid locks. The
4350+
* LocalTransactionId is always a normal, locked XID.
4351+
*/
4352+
SET_LOCKTAG_TRANSACTION(tag, vxid.localTransactionId);
4353+
lar = LockAcquire(&tag, ShareLock, false, !wait);
4354+
if (lar != LOCKACQUIRE_NOT_AVAIL)
4355+
LockRelease(&tag, ShareLock, false);
4356+
return lar != LOCKACQUIRE_NOT_AVAIL;
4357+
}
4358+
43524359
SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid);
43534360

43544361
/*

src/include/storage/lock.h

+9-9
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ extern bool Debug_deadlocks;
4747

4848
/*
4949
* Top-level transactions are identified by VirtualTransactionIDs comprising
50-
* the BackendId of the backend running the xact, plus a locally-assigned
51-
* LocalTransactionId. These are guaranteed unique over the short term,
52-
* but will be reused after a database restart; hence they should never
53-
* be stored on disk.
50+
* PGPROC fields backendId and lxid. For prepared transactions, the
51+
* LocalTransactionId is an ordinary XID. These are guaranteed unique over
52+
* the short term, but will be reused after a database restart or XID
53+
* wraparound; hence they should never be stored on disk.
5454
*
5555
* Note that struct VirtualTransactionId can not be assumed to be atomically
5656
* assignable as a whole. However, type LocalTransactionId is assumed to
@@ -62,16 +62,16 @@ extern bool Debug_deadlocks;
6262
*/
6363
typedef struct
6464
{
65-
BackendId backendId; /* determined at backend startup */
66-
LocalTransactionId localTransactionId; /* backend-local transaction
67-
* id */
65+
BackendId backendId; /* backendId from PGPROC */
66+
LocalTransactionId localTransactionId; /* lxid from PGPROC */
6867
} VirtualTransactionId;
6968

7069
#define InvalidLocalTransactionId 0
7170
#define LocalTransactionIdIsValid(lxid) ((lxid) != InvalidLocalTransactionId)
7271
#define VirtualTransactionIdIsValid(vxid) \
73-
(((vxid).backendId != InvalidBackendId) && \
74-
LocalTransactionIdIsValid((vxid).localTransactionId))
72+
(LocalTransactionIdIsValid((vxid).localTransactionId))
73+
#define VirtualTransactionIdIsPreparedXact(vxid) \
74+
((vxid).backendId == InvalidBackendId)
7575
#define VirtualTransactionIdEquals(vxid1, vxid2) \
7676
((vxid1).backendId == (vxid2).backendId && \
7777
(vxid1).localTransactionId == (vxid2).localTransactionId)

src/test/isolation/Makefile

+5-6
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,11 @@ installcheck: all
4848
check: all
4949
$(with_temp_install) ./pg_isolation_regress --temp-instance=./tmp_check $(TEMP_CONF) --inputdir=$(srcdir) --bindir= $(EXTRA_REGRESS_OPTS) --schedule=$(srcdir)/isolation_schedule
5050

51-
# Versions of the check tests that include the prepared_transactions test
52-
# It only makes sense to run these if set up to use prepared transactions,
53-
# via TEMP_CONFIG for the check case, or via the postgresql.conf for the
54-
# installcheck case.
51+
# Non-default tests. It only makes sense to run these if set up to use
52+
# prepared transactions, via TEMP_CONFIG for the check case, or via the
53+
# postgresql.conf for the installcheck case.
5554
installcheck-prepared-txns: all temp-install
56-
./pg_isolation_regress --bindir='$(bindir)' $(EXTRA_REGRESS_OPTS) --inputdir=$(srcdir) --schedule=$(srcdir)/isolation_schedule prepared-transactions
55+
./pg_isolation_regress --bindir='$(bindir)' $(EXTRA_REGRESS_OPTS) --inputdir=$(srcdir) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic
5756

5857
check-prepared-txns: all temp-install
59-
$(with_temp_install) ./pg_isolation_regress --temp-instance=./tmp_check $(TEMP_CONF) --inputdir=$(srcdir) --bindir= $(EXTRA_REGRESS_OPTS) --schedule=$(srcdir)/isolation_schedule prepared-transactions
58+
$(with_temp_install) ./pg_isolation_regress --temp-instance=./tmp_check $(TEMP_CONF) --inputdir=$(srcdir) --bindir= $(EXTRA_REGRESS_OPTS) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic

src/test/isolation/README

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ you can do something like
2323
./pg_isolation_regress fk-contention fk-deadlock
2424
(look into the specs/ subdirectory to see the available tests).
2525

26-
The prepared-transactions test requires the server's
27-
max_prepared_transactions parameter to be set to at least 3; therefore it
28-
is not run by default. To include it in the test run, use
26+
Certain tests require the server's max_prepared_transactions parameter to be
27+
set to at least 3; therefore they are not run by default. To include them in
28+
the test run, use
2929
make check-prepared-txns
3030
or
3131
make installcheck-prepared-txns
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: w1 p1 cic2 c1 r2
4+
step w1: BEGIN; INSERT INTO cic_test VALUES (1);
5+
step p1: PREPARE TRANSACTION 's1';
6+
step cic2:
7+
CREATE INDEX CONCURRENTLY on cic_test(a);
8+
9+
ERROR: canceling statement due to lock timeout
10+
step c1: COMMIT PREPARED 's1';
11+
step r2:
12+
SET enable_seqscan to off;
13+
SET enable_bitmapscan to off;
14+
SELECT * FROM cic_test WHERE a = 1;
15+
16+
a
17+
18+
1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# This test verifies that CREATE INDEX CONCURRENTLY interacts with prepared
2+
# transactions correctly.
3+
setup
4+
{
5+
CREATE TABLE cic_test (a int);
6+
}
7+
8+
teardown
9+
{
10+
DROP TABLE cic_test;
11+
}
12+
13+
14+
# Sessions for CREATE INDEX CONCURRENTLY test
15+
session "s1"
16+
step "w1" { BEGIN; INSERT INTO cic_test VALUES (1); }
17+
step "p1" { PREPARE TRANSACTION 's1'; }
18+
step "c1" { COMMIT PREPARED 's1'; }
19+
20+
session "s2"
21+
# The isolation tester never recognizes that a lock of s1 blocks s2, because a
22+
# prepared transaction's locks have no pid associated. While there's a slight
23+
# chance of timeout while waiting for an autovacuum-held lock, that wouldn't
24+
# change the output. Hence, no timeout is too short.
25+
setup { SET lock_timeout = 10; }
26+
step "cic2"
27+
{
28+
CREATE INDEX CONCURRENTLY on cic_test(a);
29+
}
30+
step "r2"
31+
{
32+
SET enable_seqscan to off;
33+
SET enable_bitmapscan to off;
34+
SELECT * FROM cic_test WHERE a = 1;
35+
}
36+
37+
permutation "w1" "p1" "cic2" "c1" "r2"

0 commit comments

Comments
 (0)