Skip to content

Commit 94d1c88

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 32c0295 commit 94d1c88

File tree

5 files changed

+123
-32
lines changed

5 files changed

+123
-32
lines changed

src/backend/access/heap/heapam.c

Lines changed: 101 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6030,6 +6030,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
60306030
*/
60316031
static TransactionId
60326032
FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
6033+
TransactionId relfrozenxid, TransactionId relminmxid,
60336034
TransactionId cutoff_xid, MultiXactId cutoff_multi,
60346035
uint16 *flags)
60356036
{
@@ -6056,16 +6057,26 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
60566057
*flags |= FRM_INVALIDATE_XMAX;
60576058
return InvalidTransactionId;
60586059
}
6060+
else if (MultiXactIdPrecedes(multi, relminmxid))
6061+
ereport(ERROR,
6062+
(errcode(ERRCODE_DATA_CORRUPTED),
6063+
errmsg_internal("found multixact %u from before relminmxid %u",
6064+
multi, relminmxid)));
60596065
else if (MultiXactIdPrecedes(multi, cutoff_multi))
60606066
{
60616067
/*
6062-
* This old multi cannot possibly have members still running. If it
6063-
* was a locker only, it can be removed without any further
6064-
* consideration; but if it contained an update, we might need to
6065-
* preserve it.
6068+
* This old multi cannot possibly have members still running, but
6069+
* verify just in case. If it was a locker only, it can be removed
6070+
* without any further consideration; but if it contained an update, we
6071+
* might need to preserve it.
60666072
*/
6067-
Assert(!MultiXactIdIsRunning(multi,
6068-
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)));
6073+
if (MultiXactIdIsRunning(multi,
6074+
HEAP_XMAX_IS_LOCKED_ONLY(t_infomask)))
6075+
ereport(ERROR,
6076+
(errcode(ERRCODE_DATA_CORRUPTED),
6077+
errmsg_internal("multixact %u from before cutoff %u found to be still running",
6078+
multi, cutoff_multi)));
6079+
60696080
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
60706081
{
60716082
*flags |= FRM_INVALIDATE_XMAX;
@@ -6079,13 +6090,22 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
60796090
/* wasn't only a lock, xid needs to be valid */
60806091
Assert(TransactionIdIsValid(xid));
60816092

6093+
if (TransactionIdPrecedes(xid, relfrozenxid))
6094+
ereport(ERROR,
6095+
(errcode(ERRCODE_DATA_CORRUPTED),
6096+
errmsg_internal("found update xid %u from before relfrozenxid %u",
6097+
xid, relfrozenxid)));
6098+
60826099
/*
60836100
* If the xid is older than the cutoff, it has to have aborted,
60846101
* otherwise the tuple would have gotten pruned away.
60856102
*/
60866103
if (TransactionIdPrecedes(xid, cutoff_xid))
60876104
{
6088-
Assert(!TransactionIdDidCommit(xid));
6105+
if (TransactionIdDidCommit(xid))
6106+
ereport(ERROR,
6107+
(errcode(ERRCODE_DATA_CORRUPTED),
6108+
errmsg_internal("cannot freeze committed update xid %u", xid)));
60896109
*flags |= FRM_INVALIDATE_XMAX;
60906110
xid = InvalidTransactionId; /* not strictly necessary */
60916111
}
@@ -6157,6 +6177,13 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
61576177
{
61586178
TransactionId xid = members[i].xid;
61596179

6180+
Assert(TransactionIdIsValid(xid));
6181+
if (TransactionIdPrecedes(xid, relfrozenxid))
6182+
ereport(ERROR,
6183+
(errcode(ERRCODE_DATA_CORRUPTED),
6184+
errmsg_internal("found update xid %u from before relfrozenxid %u",
6185+
xid, relfrozenxid)));
6186+
61606187
/*
61616188
* It's an update; should we keep it? If the transaction is known
61626189
* aborted or crashed then it's okay to ignore it, otherwise not.
@@ -6185,18 +6212,26 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
61856212
update_committed = true;
61866213
update_xid = xid;
61876214
}
6188-
6189-
/*
6190-
* Not in progress, not committed -- must be aborted or crashed;
6191-
* we can ignore it.
6192-
*/
6215+
else
6216+
{
6217+
/*
6218+
* Not in progress, not committed -- must be aborted or crashed;
6219+
* we can ignore it.
6220+
*/
6221+
}
61936222

61946223
/*
61956224
* Since the tuple wasn't marked HEAPTUPLE_DEAD by vacuum, the
6196-
* update Xid cannot possibly be older than the xid cutoff.
6225+
* update Xid cannot possibly be older than the xid cutoff. The
6226+
* presence of such a tuple would cause corruption, so be paranoid
6227+
* and check.
61976228
*/
6198-
Assert(!TransactionIdIsValid(update_xid) ||
6199-
!TransactionIdPrecedes(update_xid, cutoff_xid));
6229+
if (TransactionIdIsValid(update_xid) &&
6230+
TransactionIdPrecedes(update_xid, cutoff_xid))
6231+
ereport(ERROR,
6232+
(errcode(ERRCODE_DATA_CORRUPTED),
6233+
errmsg_internal("found update xid %u from before xid cutoff %u",
6234+
update_xid, cutoff_xid)));
62006235

62016236
/*
62026237
* If we determined that it's an Xid corresponding to an update
@@ -6291,8 +6326,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
62916326
* recovery. We really need to remove old xids.
62926327
*/
62936328
bool
6294-
heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
6295-
TransactionId cutoff_multi,
6329+
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
6330+
TransactionId relfrozenxid, TransactionId relminmxid,
6331+
TransactionId cutoff_xid, TransactionId cutoff_multi,
62966332
xl_heap_freeze_tuple *frz)
62976333

62986334
{
@@ -6307,11 +6343,25 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
63076343

63086344
/* Process xmin */
63096345
xid = HeapTupleHeaderGetXmin(tuple);
6310-
if (TransactionIdIsNormal(xid) &&
6311-
TransactionIdPrecedes(xid, cutoff_xid))
6346+
if (TransactionIdIsNormal(xid))
63126347
{
6313-
frz->t_infomask |= HEAP_XMIN_FROZEN;
6314-
changed = true;
6348+
if (TransactionIdPrecedes(xid, relfrozenxid))
6349+
ereport(ERROR,
6350+
(errcode(ERRCODE_DATA_CORRUPTED),
6351+
errmsg_internal("found xmin %u from before relfrozenxid %u",
6352+
xid, relfrozenxid)));
6353+
6354+
if (TransactionIdPrecedes(xid, cutoff_xid))
6355+
{
6356+
if (!TransactionIdDidCommit(xid))
6357+
ereport(ERROR,
6358+
(errcode(ERRCODE_DATA_CORRUPTED),
6359+
errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
6360+
xid, cutoff_xid)));
6361+
6362+
frz->t_infomask |= HEAP_XMIN_FROZEN;
6363+
changed = true;
6364+
}
63156365
}
63166366

63176367
/*
@@ -6331,6 +6381,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
63316381
uint16 flags;
63326382

63336383
newxmax = FreezeMultiXactId(xid, tuple->t_infomask,
6384+
relfrozenxid, relminmxid,
63346385
cutoff_xid, cutoff_multi, &flags);
63356386

63366387
if (flags & FRM_INVALIDATE_XMAX)
@@ -6376,10 +6427,30 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
63766427
Assert(flags & FRM_NOOP);
63776428
}
63786429
}
6379-
else if (TransactionIdIsNormal(xid) &&
6380-
TransactionIdPrecedes(xid, cutoff_xid))
6430+
else if (TransactionIdIsNormal(xid))
63816431
{
6382-
freeze_xmax = true;
6432+
if (TransactionIdPrecedes(xid, relfrozenxid))
6433+
ereport(ERROR,
6434+
(errcode(ERRCODE_DATA_CORRUPTED),
6435+
errmsg_internal("found xmax %u from before relfrozenxid %u",
6436+
xid, relfrozenxid)));
6437+
6438+
if (TransactionIdPrecedes(xid, cutoff_xid))
6439+
{
6440+
/*
6441+
* If we freeze xmax, make absolutely sure that it's not an XID
6442+
* that is important. (Note, a lock-only xmax can be removed
6443+
* independent of committedness, since a committed lock holder has
6444+
* released the lock).
6445+
*/
6446+
if (!(tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
6447+
TransactionIdDidCommit(xid))
6448+
ereport(ERROR,
6449+
(errcode(ERRCODE_DATA_CORRUPTED),
6450+
errmsg_internal("cannot freeze committed xmax %u",
6451+
xid)));
6452+
freeze_xmax = true;
6453+
}
63836454
}
63846455

63856456
if (freeze_xmax)
@@ -6473,13 +6544,16 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
64736544
* Useful for callers like CLUSTER that perform their own WAL logging.
64746545
*/
64756546
bool
6476-
heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
6477-
TransactionId cutoff_multi)
6547+
heap_freeze_tuple(HeapTupleHeader tuple,
6548+
TransactionId relfrozenxid, TransactionId relminmxid,
6549+
TransactionId cutoff_xid, TransactionId cutoff_multi)
64786550
{
64796551
xl_heap_freeze_tuple frz;
64806552
bool do_freeze;
64816553

6482-
do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
6554+
do_freeze = heap_prepare_freeze_tuple(tuple,
6555+
relfrozenxid, relminmxid,
6556+
cutoff_xid, cutoff_multi,
64836557
&frz);
64846558

64856559
/*

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
@@ -447,6 +447,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
447447
blkno;
448448
HeapTupleData tuple;
449449
char *relname;
450+
TransactionId relfrozenxid = onerel->rd_rel->relfrozenxid;
451+
TransactionId relminmxid = onerel->rd_rel->relminmxid;
450452
BlockNumber empty_pages,
451453
vacuumed_pages;
452454
double num_tuples,
@@ -842,6 +844,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
842844
* tuple, we choose to keep it, because it'll be a lot
843845
* cheaper to get rid of it in the next pruning pass than
844846
* to treat it like an indexed tuple.
847+
*
848+
* If this were to happen for a tuple that actually needed
849+
* to be deleted, we'd be in trouble, because it'd
850+
* possibly leave a tuple below the relation's xmin
851+
* horizon alive. heap_prepare_freeze_tuple() is prepared
852+
* to detect that case and abort the transaction,
853+
* preventing corruption.
845854
*/
846855
if (HeapTupleIsHotUpdated(&tuple) ||
847856
HeapTupleIsHeapOnly(&tuple))
@@ -931,8 +940,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
931940
* Each non-removable tuple must be checked to see if it needs
932941
* freezing. Note we already have exclusive buffer lock.
933942
*/
934-
if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
935-
MultiXactCutoff, &frozen[nfrozen]))
943+
if (heap_prepare_freeze_tuple(tuple.t_data,
944+
relfrozenxid, relminmxid,
945+
FreezeLimit, MultiXactCutoff,
946+
&frozen[nfrozen]))
936947
frozen[nfrozen++].offset = offnum;
937948
}
938949
} /* scan along page */

src/include/access/heapam.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
160160
bool follow_update,
161161
Buffer *buffer, HeapUpdateFailureData *hufd);
162162
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
163-
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
164-
TransactionId cutoff_multi);
163+
extern bool heap_freeze_tuple(HeapTupleHeader tuple,
164+
TransactionId relfrozenxid, TransactionId relminmxid,
165+
TransactionId cutoff_xid, TransactionId cutoff_multi);
165166
extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
166167
MultiXactId cutoff_multi, Buffer buf);
167168

src/include/access/heapam_xlog.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
377377
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
378378
int ntuples);
379379
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
380+
TransactionId relfrozenxid,
381+
TransactionId relminmxid,
380382
TransactionId cutoff_xid,
381383
TransactionId cutoff_multi,
382384
xl_heap_freeze_tuple *frz);

0 commit comments

Comments
 (0)