Skip to content

Commit ef0339e

Browse files
committed
Revert bogus fixes of HOT-freezing bug
It turns out we misdiagnosed what the real problem was. Revert the previous changes, because they may have worse consequences going forward. A better fix is forthcoming. The simplistic test case is kept, though disabled. Discussion: https://postgr.es/m/20171102112019.33wb7g5wp4zpjelu@alap3.anarazel.de
1 parent f570ec5 commit ef0339e

File tree

6 files changed

+60
-113
lines changed

6 files changed

+60
-113
lines changed

src/backend/access/heap/heapam.c

Lines changed: 46 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,7 +1737,8 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
17371737
* broken.
17381738
*/
17391739
if (TransactionIdIsValid(prev_xmax) &&
1740-
!HeapTupleUpdateXmaxMatchesXmin(prev_xmax, heapTuple->t_data))
1740+
!TransactionIdEquals(prev_xmax,
1741+
HeapTupleHeaderGetXmin(heapTuple->t_data)))
17411742
break;
17421743

17431744
/*
@@ -1919,7 +1920,7 @@ heap_get_latest_tid(Relation relation,
19191920
* tuple. Check for XMIN match.
19201921
*/
19211922
if (TransactionIdIsValid(priorXmax) &&
1922-
!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tp.t_data))
1923+
!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(tp.t_data)))
19231924
{
19241925
UnlockReleaseBuffer(buffer);
19251926
break;
@@ -1951,50 +1952,6 @@ heap_get_latest_tid(Relation relation,
19511952
} /* end of loop */
19521953
}
19531954

1954-
/*
1955-
* HeapTupleUpdateXmaxMatchesXmin - verify update chain xmax/xmin lineage
1956-
*
1957-
* Given the new version of a tuple after some update, verify whether the
1958-
* given Xmax (corresponding to the previous version) matches the tuple's
1959-
* Xmin, taking into account that the Xmin might have been frozen after the
1960-
* update.
1961-
*/
1962-
bool
1963-
HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax, HeapTupleHeader htup)
1964-
{
1965-
TransactionId xmin = HeapTupleHeaderGetXmin(htup);
1966-
1967-
/*
1968-
* If the xmax of the old tuple is identical to the xmin of the new one,
1969-
* it's a match.
1970-
*/
1971-
if (TransactionIdEquals(xmax, xmin))
1972-
return true;
1973-
1974-
/*
1975-
* If the Xmin that was in effect prior to a freeze matches the Xmax,
1976-
* it's good too.
1977-
*/
1978-
if (HeapTupleHeaderXminFrozen(htup) &&
1979-
TransactionIdEquals(HeapTupleHeaderGetRawXmin(htup), xmax))
1980-
return true;
1981-
1982-
/*
1983-
* When a tuple is frozen, the original Xmin is lost, but we know it's a
1984-
* committed transaction. So unless the Xmax is InvalidXid, we don't know
1985-
* for certain that there is a match, but there may be one; and we must
1986-
* return true so that a HOT chain that is half-frozen can be walked
1987-
* correctly.
1988-
*
1989-
* We no longer freeze tuples this way, but we must keep this in order to
1990-
* interpret pre-pg_upgrade pages correctly.
1991-
*/
1992-
if (TransactionIdEquals(xmin, FrozenTransactionId) &&
1993-
TransactionIdIsValid(xmax))
1994-
return true;
1995-
1996-
return false;
1997-
}
19981955

19991956
/*
20001957
* UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
@@ -5283,7 +5240,8 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid,
52835240
* end of the chain, we're done, so return success.
52845241
*/
52855242
if (TransactionIdIsValid(priorXmax) &&
5286-
!HeapTupleUpdateXmaxMatchesXmin(priorXmax, mytup.t_data))
5243+
!TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data),
5244+
priorXmax))
52875245
{
52885246
UnlockReleaseBuffer(buf);
52895247
return HeapTupleMayBeUpdated;
@@ -5729,23 +5687,14 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
57295687
Assert(TransactionIdIsValid(xid));
57305688

57315689
/*
5732-
* The updating transaction cannot possibly be still running, but
5733-
* verify whether it has committed, and request to set the
5734-
* COMMITTED flag if so. (We normally don't see any tuples in
5735-
* this state, because they are removed by page pruning before we
5736-
* try to freeze the page; but this can happen if the updating
5737-
* transaction commits after the page is pruned but before
5738-
* HeapTupleSatisfiesVacuum).
5690+
* If the xid is older than the cutoff, it has to have aborted,
5691+
* otherwise the tuple would have gotten pruned away.
57395692
*/
57405693
if (TransactionIdPrecedes(xid, cutoff_xid))
57415694
{
5742-
if (TransactionIdDidCommit(xid))
5743-
*flags = FRM_MARK_COMMITTED | FRM_RETURN_IS_XID;
5744-
else
5745-
{
5746-
*flags |= FRM_INVALIDATE_XMAX;
5747-
xid = InvalidTransactionId; /* not strictly necessary */
5748-
}
5695+
Assert(!TransactionIdDidCommit(xid));
5696+
*flags |= FRM_INVALIDATE_XMAX;
5697+
xid = InvalidTransactionId; /* not strictly necessary */
57495698
}
57505699
else
57515700
{
@@ -5816,51 +5765,65 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
58165765

58175766
/*
58185767
* It's an update; should we keep it? If the transaction is known
5819-
* aborted or crashed then it's okay to ignore it, otherwise not.
5768+
* aborted then it's okay to ignore it, otherwise not. However,
5769+
* if the Xid is older than the cutoff_xid, we must remove it.
5770+
* Note that such an old updater cannot possibly be committed,
5771+
* because HeapTupleSatisfiesVacuum would have returned
5772+
* HEAPTUPLE_DEAD and we would not be trying to freeze the tuple.
5773+
*
5774+
* Note the TransactionIdDidAbort() test is just an optimization
5775+
* and not strictly necessary for correctness.
58205776
*
58215777
* As with all tuple visibility routines, it's critical to test
5822-
* TransactionIdIsInProgress before TransactionIdDidCommit,
5778+
* TransactionIdIsInProgress before the transam.c routines,
58235779
* because of race conditions explained in detail in tqual.c.
5824-
*
5825-
* We normally don't see committed updating transactions earlier
5826-
* than the cutoff xid, because they are removed by page pruning
5827-
* before we try to freeze the page; but it can happen if the
5828-
* updating transaction commits after the page is pruned but
5829-
* before HeapTupleSatisfiesVacuum.
58305780
*/
58315781
if (TransactionIdIsCurrentTransactionId(xid) ||
58325782
TransactionIdIsInProgress(xid))
58335783
{
58345784
Assert(!TransactionIdIsValid(update_xid));
58355785
update_xid = xid;
58365786
}
5837-
else if (TransactionIdDidCommit(xid))
5787+
else if (!TransactionIdDidAbort(xid))
58385788
{
58395789
/*
5840-
* The transaction committed, so we can tell caller to set
5841-
* HEAP_XMAX_COMMITTED. (We can only do this because we know
5842-
* the transaction is not running.)
5790+
* Test whether to tell caller to set HEAP_XMAX_COMMITTED
5791+
* while we have the Xid still in cache. Note this can only
5792+
* be done if the transaction is known not running.
58435793
*/
5794+
if (TransactionIdDidCommit(xid))
5795+
update_committed = true;
58445796
Assert(!TransactionIdIsValid(update_xid));
5845-
update_committed = true;
58465797
update_xid = xid;
58475798
}
58485799

5849-
/*
5850-
* Not in progress, not committed -- must be aborted or crashed;
5851-
* we can ignore it.
5852-
*/
5853-
58545800
/*
58555801
* If we determined that it's an Xid corresponding to an update
58565802
* that must be retained, additionally add it to the list of
5857-
* members of the new Multi, in case we end up using that. (We
5803+
* members of the new Multis, in case we end up using that. (We
58585804
* might still decide to use only an update Xid and not a multi,
58595805
* but it's easier to maintain the list as we walk the old members
58605806
* list.)
5807+
*
5808+
* It is possible to end up with a very old updater Xid that
5809+
* crashed and thus did not mark itself as aborted in pg_clog.
5810+
* That would manifest as a pre-cutoff Xid. Make sure to ignore
5811+
* it.
58615812
*/
58625813
if (TransactionIdIsValid(update_xid))
5863-
newmembers[nnewmembers++] = members[i];
5814+
{
5815+
if (!TransactionIdPrecedes(update_xid, cutoff_xid))
5816+
{
5817+
newmembers[nnewmembers++] = members[i];
5818+
}
5819+
else
5820+
{
5821+
/* cannot have committed: would be HEAPTUPLE_DEAD */
5822+
Assert(!TransactionIdDidCommit(update_xid));
5823+
update_xid = InvalidTransactionId;
5824+
update_committed = false;
5825+
}
5826+
}
58645827
}
58655828
else
58665829
{
@@ -5927,10 +5890,7 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
59275890
*
59285891
* It is assumed that the caller has checked the tuple with
59295892
* HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD
5930-
* (else we should be removing the tuple, not freezing it). However, note
5931-
* that we don't remove HOT tuples even if they are dead, and it'd be incorrect
5932-
* to freeze them (because that would make them visible), so we mark them as
5933-
* update-committed, and needing further freezing later on.
5893+
* (else we should be removing the tuple, not freezing it).
59345894
*
59355895
* NB: cutoff_xid *must* be <= the current global xmin, to ensure that any
59365896
* XID older than it could neither be running nor seen as running by any
@@ -6035,18 +5995,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
60355995
else if (TransactionIdIsNormal(xid) &&
60365996
TransactionIdPrecedes(xid, cutoff_xid))
60375997
{
6038-
/*
6039-
* Must freeze regular XIDs older than the cutoff. We must not freeze
6040-
* a HOT-updated tuple, though; doing so would bring it back to life.
6041-
*/
6042-
if (HeapTupleHeaderIsHotUpdated(tuple))
6043-
{
6044-
frz->t_infomask |= HEAP_XMAX_COMMITTED;
6045-
changed = true;
6046-
/* must not freeze */
6047-
}
6048-
else
6049-
freeze_xmax = true;
5998+
freeze_xmax = true;
60505999
}
60516000

60526001
if (freeze_xmax)

src/backend/access/heap/pruneheap.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
465465
* Check the tuple XMIN against prior XMAX, if any
466466
*/
467467
if (TransactionIdIsValid(priorXmax) &&
468-
!HeapTupleUpdateXmaxMatchesXmin(priorXmax, htup))
468+
!TransactionIdEquals(HeapTupleHeaderGetXmin(htup), priorXmax))
469469
break;
470470

471471
/*
@@ -805,7 +805,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
805805
htup = (HeapTupleHeader) PageGetItem(page, lp);
806806

807807
if (TransactionIdIsValid(priorXmax) &&
808-
!HeapTupleUpdateXmaxMatchesXmin(priorXmax, htup))
808+
!TransactionIdEquals(priorXmax, HeapTupleHeaderGetXmin(htup)))
809809
break;
810810

811811
/* Remember the root line pointer for this item */

src/backend/commands/vacuumlazy.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,15 +1688,15 @@ lazy_record_dead_tuple(LVRelStats *vacrelstats,
16881688
ItemPointer itemptr)
16891689
{
16901690
/*
1691-
* The array must never overflow, since we rely on all deletable tuples
1692-
* being removed; inability to remove a tuple might cause an old XID to
1693-
* persist beyond the freeze limit, which could be disastrous later on.
1691+
* The array shouldn't overflow under normal behavior, but perhaps it
1692+
* could if we are given a really small maintenance_work_mem. In that
1693+
* case, just forget the last few tuples (we'll get 'em next time).
16941694
*/
1695-
if (vacrelstats->num_dead_tuples >= vacrelstats->max_dead_tuples)
1696-
elog(ERROR, "dead tuple array overflow");
1697-
1698-
vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr;
1699-
vacrelstats->num_dead_tuples++;
1695+
if (vacrelstats->num_dead_tuples < vacrelstats->max_dead_tuples)
1696+
{
1697+
vacrelstats->dead_tuples[vacrelstats->num_dead_tuples] = *itemptr;
1698+
vacrelstats->num_dead_tuples++;
1699+
}
17001700
}
17011701

17021702
/*

src/backend/executor/execMain.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2080,7 +2080,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait,
20802080
* buffer's content lock, since xmin never changes in an existing
20812081
* tuple.)
20822082
*/
2083-
if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data))
2083+
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
2084+
priorXmax))
20842085
{
20852086
ReleaseBuffer(buffer);
20862087
return NULL;
@@ -2209,7 +2210,8 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode, bool noWait,
22092210
/*
22102211
* As above, if xmin isn't what we're expecting, do nothing.
22112212
*/
2212-
if (!HeapTupleUpdateXmaxMatchesXmin(priorXmax, tuple.t_data))
2213+
if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data),
2214+
priorXmax))
22132215
{
22142216
ReleaseBuffer(buffer);
22152217
return NULL;

src/include/access/heapam.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ extern void heap_get_latest_tid(Relation relation, Snapshot snapshot,
129129
ItemPointer tid);
130130
extern void setLastTid(const ItemPointer tid);
131131

132-
extern bool HeapTupleUpdateXmaxMatchesXmin(TransactionId xmax,
133-
HeapTupleHeader htup);
134-
135132
extern BulkInsertState GetBulkInsertState(void);
136133
extern void FreeBulkInsertState(BulkInsertState);
137134

src/test/isolation/isolation_schedule

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ test: lock-committed-keyupdate
3030
test: update-locked-tuple
3131
test: propagate-lock-delete
3232
test: tuplelock-conflict
33-
test: freeze-the-dead
3433
test: nowait
3534
test: nowait-2
3635
test: nowait-3

0 commit comments

Comments
 (0)