Skip to content

Commit ed8e1af

Browse files
committed
Perform a lot more sanity checks when freezing tuples.
The previous commit has shown that the sanity checks around freezing aren't strong enough. Strengthening them seems especially important because the existance of the bug has caused corruption that we don't want to make even worse during future vacuum cycles. The errors are emitted with ereport rather than elog, despite being "should never happen" messages, so a proper error code is emitted. To avoid superflous translations, mark messages as internal. Author: Andres Freund and Alvaro Herrera Reviewed-By: Alvaro Herrera, Michael Paquier Discussion: https://postgr.es/m/20171102112019.33wb7g5wp4zpjelu@alap3.anarazel.de Backpatch: 9.3-
1 parent 4eff5a8 commit ed8e1af

File tree

5 files changed

+126
-23
lines changed

5 files changed

+126
-23
lines changed

src/backend/access/heap/heapam.c

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5638,6 +5638,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
56385638
*/
56395639
static TransactionId
56405640
FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
5641+
TransactionId relfrozenxid, TransactionId relminmxid,
56415642
TransactionId cutoff_xid, MultiXactId cutoff_multi,
56425643
uint16 *flags)
56435644
{
@@ -5664,15 +5665,25 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
56645665
*flags |= FRM_INVALIDATE_XMAX;
56655666
return InvalidTransactionId;
56665667
}
5668+
else if (MultiXactIdPrecedes(multi, relminmxid))
5669+
ereport(ERROR,
5670+
(errcode(ERRCODE_DATA_CORRUPTED),
5671+
errmsg_internal("found multixact %u from before relminmxid %u",
5672+
multi, relminmxid)));
56675673
else if (MultiXactIdPrecedes(multi, cutoff_multi))
56685674
{
56695675
/*
5670-
* This old multi cannot possibly have members still running. If it
5671-
* was a locker only, it can be removed without any further
5672-
* consideration; but if it contained an update, we might need to
5673-
* preserve it.
5676+
* This old multi cannot possibly have members still running, but
5677+
* verify just in case. If it was a locker only, it can be removed
5678+
* without any further consideration; but if it contained an update, we
5679+
* might need to preserve it.
56745680
*/
5675-
Assert(!MultiXactIdIsRunning(multi));
5681+
if (MultiXactIdIsRunning(multi))
5682+
ereport(ERROR,
5683+
(errcode(ERRCODE_DATA_CORRUPTED),
5684+
errmsg_internal("multixact %u from before cutoff %u found to be still running",
5685+
multi, cutoff_multi)));
5686+
56765687
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
56775688
{
56785689
*flags |= FRM_INVALIDATE_XMAX;
@@ -5686,13 +5697,22 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
56865697
/* wasn't only a lock, xid needs to be valid */
56875698
Assert(TransactionIdIsValid(xid));
56885699

5700+
if (TransactionIdPrecedes(xid, relfrozenxid))
5701+
ereport(ERROR,
5702+
(errcode(ERRCODE_DATA_CORRUPTED),
5703+
errmsg_internal("found update xid %u from before relfrozenxid %u",
5704+
xid, relfrozenxid)));
5705+
56895706
/*
56905707
* If the xid is older than the cutoff, it has to have aborted,
56915708
* otherwise the tuple would have gotten pruned away.
56925709
*/
56935710
if (TransactionIdPrecedes(xid, cutoff_xid))
56945711
{
5695-
Assert(!TransactionIdDidCommit(xid));
5712+
if (TransactionIdDidCommit(xid))
5713+
ereport(ERROR,
5714+
(errcode(ERRCODE_DATA_CORRUPTED),
5715+
errmsg_internal("cannot freeze committed update xid %u", xid)));
56965716
*flags |= FRM_INVALIDATE_XMAX;
56975717
xid = InvalidTransactionId; /* not strictly necessary */
56985718
}
@@ -5763,6 +5783,13 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
57635783
{
57645784
TransactionId xid = members[i].xid;
57655785

5786+
Assert(TransactionIdIsValid(xid));
5787+
if (TransactionIdPrecedes(xid, relfrozenxid))
5788+
ereport(ERROR,
5789+
(errcode(ERRCODE_DATA_CORRUPTED),
5790+
errmsg_internal("found update xid %u from before relfrozenxid %u",
5791+
xid, relfrozenxid)));
5792+
57665793
/*
57675794
* It's an update; should we keep it? If the transaction is known
57685795
* aborted then it's okay to ignore it, otherwise not. However,
@@ -5796,6 +5823,26 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
57965823
Assert(!TransactionIdIsValid(update_xid));
57975824
update_xid = xid;
57985825
}
5826+
else
5827+
{
5828+
/*
5829+
* Not in progress, not committed -- must be aborted or crashed;
5830+
* we can ignore it.
5831+
*/
5832+
}
5833+
5834+
/*
5835+
* Since the tuple wasn't marked HEAPTUPLE_DEAD by vacuum, the
5836+
* update Xid cannot possibly be older than the xid cutoff. The
5837+
* presence of such a tuple would cause corruption, so be paranoid
5838+
* and check.
5839+
*/
5840+
if (TransactionIdIsValid(update_xid) &&
5841+
TransactionIdPrecedes(update_xid, cutoff_xid))
5842+
ereport(ERROR,
5843+
(errcode(ERRCODE_DATA_CORRUPTED),
5844+
errmsg_internal("found update xid %u from before xid cutoff %u",
5845+
update_xid, cutoff_xid)));
57995846

58005847
/*
58015848
* If we determined that it's an Xid corresponding to an update
@@ -5907,8 +5954,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
59075954
* recovery. We really need to remove old xids.
59085955
*/
59095956
bool
5910-
heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
5911-
TransactionId cutoff_multi,
5957+
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
5958+
TransactionId relfrozenxid, TransactionId relminmxid,
5959+
TransactionId cutoff_xid, TransactionId cutoff_multi,
59125960
xl_heap_freeze_tuple *frz)
59135961

59145962
{
@@ -5923,11 +5971,25 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
59235971

59245972
/* Process xmin */
59255973
xid = HeapTupleHeaderGetXmin(tuple);
5926-
if (TransactionIdIsNormal(xid) &&
5927-
TransactionIdPrecedes(xid, cutoff_xid))
5974+
if (TransactionIdIsNormal(xid))
59285975
{
5929-
frz->t_infomask |= HEAP_XMIN_FROZEN;
5930-
changed = true;
5976+
if (TransactionIdPrecedes(xid, relfrozenxid))
5977+
ereport(ERROR,
5978+
(errcode(ERRCODE_DATA_CORRUPTED),
5979+
errmsg_internal("found xmin %u from before relfrozenxid %u",
5980+
xid, relfrozenxid)));
5981+
5982+
if (TransactionIdPrecedes(xid, cutoff_xid))
5983+
{
5984+
if (!TransactionIdDidCommit(xid))
5985+
ereport(ERROR,
5986+
(errcode(ERRCODE_DATA_CORRUPTED),
5987+
errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
5988+
xid, cutoff_xid)));
5989+
5990+
frz->t_infomask |= HEAP_XMIN_FROZEN;
5991+
changed = true;
5992+
}
59315993
}
59325994

59335995
/*
@@ -5947,6 +6009,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
59476009
uint16 flags;
59486010

59496011
newxmax = FreezeMultiXactId(xid, tuple->t_infomask,
6012+
relfrozenxid, relminmxid,
59506013
cutoff_xid, cutoff_multi, &flags);
59516014

59526015
if (flags & FRM_INVALIDATE_XMAX)
@@ -5992,10 +6055,30 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
59926055
Assert(flags & FRM_NOOP);
59936056
}
59946057
}
5995-
else if (TransactionIdIsNormal(xid) &&
5996-
TransactionIdPrecedes(xid, cutoff_xid))
6058+
else if (TransactionIdIsNormal(xid))
59976059
{
5998-
freeze_xmax = true;
6060+
if (TransactionIdPrecedes(xid, relfrozenxid))
6061+
ereport(ERROR,
6062+
(errcode(ERRCODE_DATA_CORRUPTED),
6063+
errmsg_internal("found xmax %u from before relfrozenxid %u",
6064+
xid, relfrozenxid)));
6065+
6066+
if (TransactionIdPrecedes(xid, cutoff_xid))
6067+
{
6068+
/*
6069+
* If we freeze xmax, make absolutely sure that it's not an XID
6070+
* that is important. (Note, a lock-only xmax can be removed
6071+
* independent of committedness, since a committed lock holder has
6072+
* released the lock).
6073+
*/
6074+
if (!(tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
6075+
TransactionIdDidCommit(xid))
6076+
ereport(ERROR,
6077+
(errcode(ERRCODE_DATA_CORRUPTED),
6078+
errmsg_internal("cannot freeze committed xmax %u",
6079+
xid)));
6080+
freeze_xmax = true;
6081+
}
59996082
}
60006083

60016084
if (freeze_xmax)
@@ -6089,13 +6172,16 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
60896172
* Useful for callers like CLUSTER that perform their own WAL logging.
60906173
*/
60916174
bool
6092-
heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
6093-
TransactionId cutoff_multi)
6175+
heap_freeze_tuple(HeapTupleHeader tuple,
6176+
TransactionId relfrozenxid, TransactionId relminmxid,
6177+
TransactionId cutoff_xid, TransactionId cutoff_multi)
60946178
{
60956179
xl_heap_freeze_tuple frz;
60966180
bool do_freeze;
60976181

6098-
do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
6182+
do_freeze = heap_prepare_freeze_tuple(tuple,
6183+
relfrozenxid, relminmxid,
6184+
cutoff_xid, cutoff_multi,
60996185
&frz);
61006186

61016187
/*

src/backend/access/heap/rewriteheap.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,10 @@ rewrite_heap_tuple(RewriteState state,
407407
* While we have our hands on the tuple, we may as well freeze any
408408
* eligible xmin or xmax, so that future VACUUM effort can be saved.
409409
*/
410-
heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
410+
heap_freeze_tuple(new_tuple->t_data,
411+
state->rs_old_rel->rd_rel->relfrozenxid,
412+
state->rs_old_rel->rd_rel->relminmxid,
413+
state->rs_freeze_xid,
411414
state->rs_cutoff_multi);
412415

413416
/*

src/backend/commands/vacuumlazy.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
429429
blkno;
430430
HeapTupleData tuple;
431431
char *relname;
432+
TransactionId relfrozenxid = onerel->rd_rel->relfrozenxid;
433+
TransactionId relminmxid = onerel->rd_rel->relminmxid;
432434
BlockNumber empty_pages,
433435
vacuumed_pages;
434436
double num_tuples,
@@ -821,6 +823,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
821823
* tuple, we choose to keep it, because it'll be a lot
822824
* cheaper to get rid of it in the next pruning pass than
823825
* to treat it like an indexed tuple.
826+
*
827+
* If this were to happen for a tuple that actually needed
828+
* to be deleted, we'd be in trouble, because it'd
829+
* possibly leave a tuple below the relation's xmin
830+
* horizon alive. heap_prepare_freeze_tuple() is prepared
831+
* to detect that case and abort the transaction,
832+
* preventing corruption.
824833
*/
825834
if (HeapTupleIsHotUpdated(&tuple) ||
826835
HeapTupleIsHeapOnly(&tuple))
@@ -910,8 +919,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
910919
* Each non-removable tuple must be checked to see if it needs
911920
* freezing. Note we already have exclusive buffer lock.
912921
*/
913-
if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
914-
MultiXactCutoff, &frozen[nfrozen]))
922+
if (heap_prepare_freeze_tuple(tuple.t_data,
923+
relfrozenxid, relminmxid,
924+
FreezeLimit, MultiXactCutoff,
925+
&frozen[nfrozen]))
915926
frozen[nfrozen++].offset = offnum;
916927
}
917928
} /* scan along page */

src/include/access/heapam.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
148148
bool follow_update,
149149
Buffer *buffer, HeapUpdateFailureData *hufd);
150150
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
151-
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
152-
TransactionId cutoff_multi);
151+
extern bool heap_freeze_tuple(HeapTupleHeader tuple,
152+
TransactionId relfrozenxid, TransactionId relminmxid,
153+
TransactionId cutoff_xid, TransactionId cutoff_multi);
153154
extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
154155
MultiXactId cutoff_multi, Buffer buf);
155156

src/include/access/heapam_xlog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
386386
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
387387
int ntuples);
388388
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
389+
TransactionId relfrozenxid,
390+
TransactionId relminmxid,
389391
TransactionId cutoff_xid,
390392
TransactionId cutoff_multi,
391393
xl_heap_freeze_tuple *frz);

0 commit comments

Comments
 (0)