Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 928408d

Browse files
committed
Fix a bug with SSI and prepared transactions:
If there's a dangerous structure T0 ---> T1 ---> T2, and T2 commits first, we need to abort something. If T2 commits before both conflicts appear, then it should be caught by OnConflict_CheckForSerializationFailure. If both conflicts appear before T2 commits, it should be caught by PreCommit_CheckForSerializationFailure. But that is actually run when T2 *prepares*. Fix that in OnConflict_CheckForSerializationFailure, by treating a prepared T2 as if it committed already. This is mostly a problem for prepared transactions, which are in prepared state for some time, but also for regular transactions because they also go through the prepared state in the SSI code for a short moment when they're committed. Kevin Grittner and Dan Ports
1 parent b2e3be4 commit 928408d

File tree

4 files changed

+40
-8
lines changed

4 files changed

+40
-8
lines changed

src/backend/storage/lmgr/predicate.c

+35-3
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@
244244

245245
#define SxactIsOnFinishedList(sxact) (!SHMQueueIsDetached(&((sxact)->finishedLink)))
246246

247+
/*
248+
* Note that a sxact is marked "prepared" once it has passed
249+
* PreCommit_CheckForSerializationFailure, even if it isn't using
250+
* 2PC. This is the point at which it can no longer be aborted.
251+
*
252+
* The PREPARED flag remains set after commit, so SxactIsCommitted
253+
* implies SxactIsPrepared.
254+
*/
247255
#define SxactIsCommitted(sxact) (((sxact)->flags & SXACT_FLAG_COMMITTED) != 0)
248256
#define SxactIsPrepared(sxact) (((sxact)->flags & SXACT_FLAG_PREPARED) != 0)
249257
#define SxactIsRolledBack(sxact) (((sxact)->flags & SXACT_FLAG_ROLLED_BACK) != 0)
@@ -3165,6 +3173,13 @@ ReleasePredicateLocks(bool isCommit)
31653173
*/
31663174
MySerializableXact->flags |= SXACT_FLAG_DOOMED;
31673175
MySerializableXact->flags |= SXACT_FLAG_ROLLED_BACK;
3176+
/*
3177+
* If the transaction was previously prepared, but is now failing due
3178+
* to a ROLLBACK PREPARED or (hopefully very rare) error after the
3179+
* prepare, clear the prepared flag. This simplifies conflict
3180+
* checking.
3181+
*/
3182+
MySerializableXact->flags &= ~SXACT_FLAG_PREPARED;
31683183
}
31693184

31703185
if (!topLevelIsDeclaredReadOnly)
@@ -4363,6 +4378,11 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
43634378
* - the writer committed before T2
43644379
* - the reader is a READ ONLY transaction and the reader was concurrent
43654380
* with T2 (= reader acquired its snapshot before T2 committed)
4381+
*
4382+
* We also handle the case that T2 is prepared but not yet committed
4383+
* here. In that case T2 has already checked for conflicts, so if it
4384+
* commits first, making the above conflict real, it's too late for it
4385+
* to abort.
43664386
*------------------------------------------------------------------------
43674387
*/
43684388
if (!failure)
@@ -4381,7 +4401,12 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
43814401
{
43824402
SERIALIZABLEXACT *t2 = conflict->sxactIn;
43834403

4384-
if (SxactIsCommitted(t2)
4404+
/*
4405+
* Note that if T2 is merely prepared but not yet committed, we
4406+
* rely on t->commitSeqNo being InvalidSerCommitSeqNo, which is
4407+
* larger than any valid commit sequence number.
4408+
*/
4409+
if (SxactIsPrepared(t2)
43854410
&& (!SxactIsCommitted(reader)
43864411
|| t2->commitSeqNo <= reader->commitSeqNo)
43874412
&& (!SxactIsCommitted(writer)
@@ -4400,7 +4425,8 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
44004425
}
44014426

44024427
/*------------------------------------------------------------------------
4403-
* Check whether the reader has become a pivot with a committed writer:
4428+
* Check whether the reader has become a pivot with a writer
4429+
* that's committed (or prepared):
44044430
*
44054431
* T0 ------> R ------> W
44064432
* rw rw
@@ -4411,7 +4437,7 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
44114437
* - T0 is READ ONLY, and overlaps the writer
44124438
*------------------------------------------------------------------------
44134439
*/
4414-
if (!failure && SxactIsCommitted(writer) && !SxactIsReadOnly(reader))
4440+
if (!failure && SxactIsPrepared(writer) && !SxactIsReadOnly(reader))
44154441
{
44164442
if (SxactHasSummaryConflictIn(reader))
44174443
{
@@ -4427,6 +4453,12 @@ OnConflict_CheckForSerializationFailure(const SERIALIZABLEXACT *reader,
44274453
{
44284454
SERIALIZABLEXACT *t0 = conflict->sxactOut;
44294455

4456+
/*
4457+
* Note that if the writer is merely prepared but not yet
4458+
* committed, we rely on writer->commitSeqNo being
4459+
* InvalidSerCommitSeqNo, which is larger than any valid commit
4460+
* sequence number.
4461+
*/
44304462
if (!SxactIsDoomed(t0)
44314463
&& (!SxactIsCommitted(t0)
44324464
|| t0->commitSeqNo >= writer->commitSeqNo)

src/test/regress/expected/prepared_xacts.out

+3-3
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ SELECT * FROM pxtest1;
131131
ddd
132132
(2 rows)
133133

134-
INSERT INTO pxtest1 VALUES ('fff');
135134
-- This should fail, because the two transactions have a write-skew anomaly
136-
PREPARE TRANSACTION 'foo5';
135+
INSERT INTO pxtest1 VALUES ('fff');
137136
ERROR: could not serialize access due to read/write dependencies among transactions
138-
DETAIL: Canceled on commit attempt with conflict in from prepared pivot.
137+
DETAIL: Canceled on identification as a pivot, during write.
139138
HINT: The transaction might succeed if retried.
139+
PREPARE TRANSACTION 'foo5';
140140
SELECT gid FROM pg_prepared_xacts;
141141
gid
142142
------

src/test/regress/expected/prepared_xacts_1.out

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ SELECT * FROM pxtest1;
134134
aaa
135135
(1 row)
136136

137-
INSERT INTO pxtest1 VALUES ('fff');
138137
-- This should fail, because the two transactions have a write-skew anomaly
138+
INSERT INTO pxtest1 VALUES ('fff');
139139
PREPARE TRANSACTION 'foo5';
140140
ERROR: prepared transactions are disabled
141141
HINT: Set max_prepared_transactions to a nonzero value.

src/test/regress/sql/prepared_xacts.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ SELECT gid FROM pg_prepared_xacts;
7474

7575
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
7676
SELECT * FROM pxtest1;
77-
INSERT INTO pxtest1 VALUES ('fff');
7877

7978
-- This should fail, because the two transactions have a write-skew anomaly
79+
INSERT INTO pxtest1 VALUES ('fff');
8080
PREPARE TRANSACTION 'foo5';
8181

8282
SELECT gid FROM pg_prepared_xacts;

0 commit comments

Comments
 (0)