1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-11 10:01:57 +03:00

Allow snapshot references to still work during transaction abort.

In REPEATABLE READ (nee SERIALIZABLE) mode, an attempt to do
GetTransactionSnapshot() between AbortTransaction and CleanupTransaction
failed, because GetTransactionSnapshot would recompute the transaction
snapshot (which is already wrong, given the isolation mode) and then
re-register it in the TopTransactionResourceOwner, leading to an Assert
because the TopTransactionResourceOwner should be empty of resources after
AbortTransaction.  This is the root cause of bug #6218 from Yamamoto
Takashi.  While changing plancache.c to avoid requesting a snapshot when
handling a ROLLBACK masks the problem, I think this is really a snapmgr.c
bug: it's lower-level than the resource manager mechanism and should not be
shutting itself down before we unwind resource manager resources.  However,
just postponing the release of the transaction snapshot until cleanup time
didn't work because of the circular dependency with
TopTransactionResourceOwner.  Fix by managing the internal reference to
that snapshot manually instead of depending on TopTransactionResourceOwner.
This saves a few cycles as well as making the module layering more
straightforward.  predicate.c's dependencies on TopTransactionResourceOwner
go away too.

I think this is a longstanding bug, but there's no evidence that it's more
than a latent bug, so it doesn't seem worth any risk of back-patching.
This commit is contained in:
Tom Lane
2011-09-26 22:25:28 -04:00
parent 16762b519c
commit 57eb009092
5 changed files with 67 additions and 57 deletions

View File

@ -7,6 +7,14 @@
* persistent memory. When a snapshot is no longer in any of these lists
* (tracked by separate refcounts on each snapshot), its memory can be freed.
*
* The FirstXactSnapshot, if any, is treated a bit specially: we increment its
* regd_count and count it in RegisteredSnapshots, but this reference is not
* tracked by a resource owner. We used to use the TopTransactionResourceOwner
* to track this snapshot reference, but that introduces logical circularity
* and thus makes it impossible to clean up in a sane fashion. It's better to
* handle this reference as an internally-tracked registration, so that this
* module is entirely lower-level than ResourceOwners.
*
* These arrangements let us reset MyProc->xmin when there are no snapshots
* referenced by this transaction. (One possible improvement would be to be
* able to advance Xmin when the snapshot with the earliest Xmin is no longer
@ -97,11 +105,11 @@ static int RegisteredSnapshots = 0;
bool FirstSnapshotSet = false;
/*
* Remembers whether this transaction registered a transaction snapshot at
* start. We cannot trust FirstSnapshotSet in combination with
* IsolationUsesXactSnapshot(), because GUC may be reset before us.
* Remember the serializable transaction snapshot, if any. We cannot trust
* FirstSnapshotSet in combination with IsolationUsesXactSnapshot(), because
* GUC may be reset before us, changing the value of IsolationUsesXactSnapshot.
*/
static bool registered_xact_snapshot = false;
static Snapshot FirstXactSnapshot = NULL;
static Snapshot CopySnapshot(Snapshot snapshot);
@ -125,23 +133,27 @@ GetTransactionSnapshot(void)
if (!FirstSnapshotSet)
{
Assert(RegisteredSnapshots == 0);
Assert(FirstXactSnapshot == NULL);
/*
* In transaction-snapshot mode, the first snapshot must live until
* end of xact regardless of what the caller does with it, so we must
* register it internally here and unregister it at end of xact.
* make a copy of it rather than returning CurrentSnapshotData
* directly.
*/
if (IsolationUsesXactSnapshot())
{
/* First, create the snapshot in CurrentSnapshotData */
if (IsolationIsSerializable())
CurrentSnapshot = RegisterSerializableTransaction(&CurrentSnapshotData);
CurrentSnapshot = GetSerializableTransactionSnapshot(&CurrentSnapshotData);
else
{
CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
CurrentSnapshot = RegisterSnapshotOnOwner(CurrentSnapshot,
TopTransactionResourceOwner);
}
registered_xact_snapshot = true;
/* Make a saved copy */
CurrentSnapshot = CopySnapshot(CurrentSnapshot);
FirstXactSnapshot = CurrentSnapshot;
/* Mark it as "registered" in FirstXactSnapshot */
FirstXactSnapshot->regd_count++;
RegisteredSnapshots++;
}
else
CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
@ -522,26 +534,6 @@ AtSubAbort_Snapshot(int level)
SnapshotResetXmin();
}
/*
* AtEarlyCommit_Snapshot
*
* Snapshot manager's cleanup function, to be called on commit, before
* doing resowner.c resource release.
*/
void
AtEarlyCommit_Snapshot(void)
{
/*
* In transaction-snapshot mode we must unregister our private refcount to
* the transaction-snapshot.
*/
if (registered_xact_snapshot)
UnregisterSnapshotFromOwner(CurrentSnapshot,
TopTransactionResourceOwner);
registered_xact_snapshot = false;
}
/*
* AtEOXact_Snapshot
* Snapshot manager's cleanup function for end of transaction
@ -549,6 +541,23 @@ AtEarlyCommit_Snapshot(void)
void
AtEOXact_Snapshot(bool isCommit)
{
/*
* In transaction-snapshot mode we must release our privately-managed
* reference to the transaction snapshot. We must decrement
* RegisteredSnapshots to keep the check below happy. But we don't bother
* to do FreeSnapshot, for two reasons: the memory will go away with
* TopTransactionContext anyway, and if someone has left the snapshot
* stacked as active, we don't want the code below to be chasing through
* a dangling pointer.
*/
if (FirstXactSnapshot != NULL)
{
Assert(FirstXactSnapshot->regd_count > 0);
Assert(RegisteredSnapshots > 0);
RegisteredSnapshots--;
}
FirstXactSnapshot = NULL;
/* On commit, complain about leftover snapshots */
if (isCommit)
{
@ -574,5 +583,6 @@ AtEOXact_Snapshot(bool isCommit)
SecondarySnapshot = NULL;
FirstSnapshotSet = false;
registered_xact_snapshot = false;
SnapshotResetXmin();
}