Skip to content

Commit

Permalink
Fix race in SERIALIZABLE READ ONLY.
Browse files Browse the repository at this point in the history
Commit bdaabb9 started skipping doomed transactions when building the
list of possible conflicts for SERIALIZABLE READ ONLY.  That makes
sense, because doomed transactions won't commit, but a couple of subtle
things broke:

1.  If all uncommitted r/w transactions are doomed, a READ ONLY
transaction would arbitrarily not benefit from the safe snapshot
optimization.  It would not be taken immediately, and yet no other
transaction would set SXACT_FLAG_RO_SAFE later.

2.  In the same circumstances but with DEFERRABLE, GetSafeSnapshot()
would correctly exit its wait loop without sleeping and then take the
optimization in non-assert builds, but assert builds would fail a sanity
check that SXACT_FLAG_RO_SAFE had been set by another transaction.

This is similar to the case for PredXact->WritableSxactCount == 0.  We
should opt out immediately if our possibleUnsafeConflicts list is empty
after filtering.

The code to maintain the serializable global xmin is moved down below
the new opt out site, because otherwise we'd have to reverse its effects
before returning.

Back-patch to all supported releases.  Bug #17368.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Discussion: https://postgr.es/m/17116-d6ca217acc180e30%40postgresql.org
Discussion: https://postgr.es/m/20110707212159.GF76634%40csail.mit.edu
  • Loading branch information
macdice committed Mar 9, 2023
1 parent 721626c commit d1c0f81
Showing 1 changed file with 31 additions and 18 deletions.
49 changes: 31 additions & 18 deletions src/backend/storage/lmgr/predicate.c
Expand Up @@ -1821,24 +1821,6 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot,
return snapshot;
}

/* Maintain serializable global xmin info. */
if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
{
Assert(PredXact->SxactGlobalXminCount == 0);
PredXact->SxactGlobalXmin = snapshot->xmin;
PredXact->SxactGlobalXminCount = 1;
OldSerXidSetActiveSerXmin(snapshot->xmin);
}
else if (TransactionIdEquals(snapshot->xmin, PredXact->SxactGlobalXmin))
{
Assert(PredXact->SxactGlobalXminCount > 0);
PredXact->SxactGlobalXminCount++;
}
else
{
Assert(TransactionIdFollows(snapshot->xmin, PredXact->SxactGlobalXmin));
}

/* Initialize the structure. */
sxact->vxid = vxid;
sxact->SeqNo.lastCommitBeforeSnapshot = PredXact->LastSxactCommitSeqNo;
Expand Down Expand Up @@ -1875,6 +1857,19 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot,
SetPossibleUnsafeConflict(sxact, othersxact);
}
}

/*
* If we didn't find any possibly unsafe conflicts because every
* uncommitted writable transaction turned out to be doomed, then we
* can "opt out" immediately. See comments above the earlier check for
* PredXact->WritableSxactCount == 0.
*/
if (SHMQueueEmpty(&sxact->possibleUnsafeConflicts))
{
ReleasePredXact(sxact);
LWLockRelease(SerializableXactHashLock);
return snapshot;
}
}
else
{
Expand All @@ -1883,6 +1878,24 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot,
(MaxBackends + max_prepared_xacts));
}

/* Maintain serializable global xmin info. */
if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
{
Assert(PredXact->SxactGlobalXminCount == 0);
PredXact->SxactGlobalXmin = snapshot->xmin;
PredXact->SxactGlobalXminCount = 1;
OldSerXidSetActiveSerXmin(snapshot->xmin);
}
else if (TransactionIdEquals(snapshot->xmin, PredXact->SxactGlobalXmin))
{
Assert(PredXact->SxactGlobalXminCount > 0);
PredXact->SxactGlobalXminCount++;
}
else
{
Assert(TransactionIdFollows(snapshot->xmin, PredXact->SxactGlobalXmin));
}

MySerializableXact = sxact;
MyXactDidWrite = false; /* haven't written anything yet */

Expand Down

0 comments on commit d1c0f81

Please sign in to comment.