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:
@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user