Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4800f16

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 387abe8 commit 4800f16

File tree

5 files changed

+132
-28
lines changed

5 files changed

+132
-28
lines changed

src/backend/access/heap/heapam.c

+110-23
Original file line numberDiff line numberDiff line change
@@ -5440,6 +5440,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
54405440
*/
54415441
static TransactionId
54425442
FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
5443+
TransactionId relfrozenxid, TransactionId relminmxid,
54435444
TransactionId cutoff_xid, MultiXactId cutoff_multi,
54445445
uint16 *flags)
54455446
{
@@ -5466,15 +5467,25 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
54665467
*flags |= FRM_INVALIDATE_XMAX;
54675468
return InvalidTransactionId;
54685469
}
5470+
else if (MultiXactIdPrecedes(multi, relminmxid))
5471+
ereport(ERROR,
5472+
(errcode(ERRCODE_DATA_CORRUPTED),
5473+
errmsg_internal("found multixact %u from before relminmxid %u",
5474+
multi, relminmxid)));
54695475
else if (MultiXactIdPrecedes(multi, cutoff_multi))
54705476
{
54715477
/*
5472-
* This old multi cannot possibly have members still running. If it
5473-
* was a locker only, it can be removed without any further
5474-
* consideration; but if it contained an update, we might need to
5475-
* preserve it.
5478+
* This old multi cannot possibly have members still running, but
5479+
* verify just in case. If it was a locker only, it can be removed
5480+
* without any further consideration; but if it contained an update, we
5481+
* might need to preserve it.
54765482
*/
5477-
Assert(!MultiXactIdIsRunning(multi));
5483+
if (MultiXactIdIsRunning(multi))
5484+
ereport(ERROR,
5485+
(errcode(ERRCODE_DATA_CORRUPTED),
5486+
errmsg_internal("multixact %u from before cutoff %u found to be still running",
5487+
multi, cutoff_multi)));
5488+
54785489
if (HEAP_XMAX_IS_LOCKED_ONLY(t_infomask))
54795490
{
54805491
*flags |= FRM_INVALIDATE_XMAX;
@@ -5488,13 +5499,22 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
54885499
/* wasn't only a lock, xid needs to be valid */
54895500
Assert(TransactionIdIsValid(xid));
54905501

5502+
if (TransactionIdPrecedes(xid, relfrozenxid))
5503+
ereport(ERROR,
5504+
(errcode(ERRCODE_DATA_CORRUPTED),
5505+
errmsg_internal("found update xid %u from before relfrozenxid %u",
5506+
xid, relfrozenxid)));
5507+
54915508
/*
54925509
* If the xid is older than the cutoff, it has to have aborted,
54935510
* otherwise the tuple would have gotten pruned away.
54945511
*/
54955512
if (TransactionIdPrecedes(xid, cutoff_xid))
54965513
{
5497-
Assert(!TransactionIdDidCommit(xid));
5514+
if (TransactionIdDidCommit(xid))
5515+
ereport(ERROR,
5516+
(errcode(ERRCODE_DATA_CORRUPTED),
5517+
errmsg_internal("cannot freeze committed update xid %u", xid)));
54985518
*flags |= FRM_INVALIDATE_XMAX;
54995519
xid = InvalidTransactionId; /* not strictly necessary */
55005520
}
@@ -5565,6 +5585,13 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
55655585
{
55665586
TransactionId xid = members[i].xid;
55675587

5588+
Assert(TransactionIdIsValid(xid));
5589+
if (TransactionIdPrecedes(xid, relfrozenxid))
5590+
ereport(ERROR,
5591+
(errcode(ERRCODE_DATA_CORRUPTED),
5592+
errmsg_internal("found update xid %u from before relfrozenxid %u",
5593+
xid, relfrozenxid)));
5594+
55685595
/*
55695596
* It's an update; should we keep it? If the transaction is known
55705597
* aborted then it's okay to ignore it, otherwise not. However,
@@ -5598,6 +5625,26 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
55985625
Assert(!TransactionIdIsValid(update_xid));
55995626
update_xid = xid;
56005627
}
5628+
else
5629+
{
5630+
/*
5631+
* Not in progress, not committed -- must be aborted or crashed;
5632+
* we can ignore it.
5633+
*/
5634+
}
5635+
5636+
/*
5637+
* Since the tuple wasn't marked HEAPTUPLE_DEAD by vacuum, the
5638+
* update Xid cannot possibly be older than the xid cutoff. The
5639+
* presence of such a tuple would cause corruption, so be paranoid
5640+
* and check.
5641+
*/
5642+
if (TransactionIdIsValid(update_xid) &&
5643+
TransactionIdPrecedes(update_xid, cutoff_xid))
5644+
ereport(ERROR,
5645+
(errcode(ERRCODE_DATA_CORRUPTED),
5646+
errmsg_internal("found update xid %u from before xid cutoff %u",
5647+
update_xid, cutoff_xid)));
56015648

56025649
/*
56035650
* If we determined that it's an Xid corresponding to an update
@@ -5709,8 +5756,9 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask,
57095756
* recovery. We really need to remove old xids.
57105757
*/
57115758
bool
5712-
heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
5713-
TransactionId cutoff_multi,
5759+
heap_prepare_freeze_tuple(HeapTupleHeader tuple,
5760+
TransactionId relfrozenxid, TransactionId relminmxid,
5761+
TransactionId cutoff_xid, TransactionId cutoff_multi,
57145762
xl_heap_freeze_tuple *frz)
57155763

57165764
{
@@ -5725,17 +5773,32 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
57255773

57265774
/* Process xmin */
57275775
xid = HeapTupleHeaderGetXmin(tuple);
5728-
if (TransactionIdIsNormal(xid) &&
5729-
TransactionIdPrecedes(xid, cutoff_xid))
5776+
if (TransactionIdIsNormal(xid))
57305777
{
5731-
frz->frzflags |= XLH_FREEZE_XMIN;
5778+
if (TransactionIdPrecedes(xid, relfrozenxid))
5779+
{
5780+
ereport(ERROR,
5781+
(errcode(ERRCODE_DATA_CORRUPTED),
5782+
errmsg_internal("found xmin %u from before relfrozenxid %u",
5783+
xid, relfrozenxid)));
5784+
}
5785+
if (TransactionIdPrecedes(xid, cutoff_xid))
5786+
{
5787+
if (!TransactionIdDidCommit(xid))
5788+
ereport(ERROR,
5789+
(errcode(ERRCODE_DATA_CORRUPTED),
5790+
errmsg_internal("uncommitted xmin %u from before xid cutoff %u needs to be frozen",
5791+
xid, cutoff_xid)));
57325792

5733-
/*
5734-
* Might as well fix the hint bits too; usually XMIN_COMMITTED will
5735-
* already be set here, but there's a small chance not.
5736-
*/
5737-
frz->t_infomask |= HEAP_XMIN_COMMITTED;
5738-
changed = true;
5793+
frz->frzflags |= XLH_FREEZE_XMIN;
5794+
5795+
/*
5796+
* Might as well fix the hint bits too; usually XMIN_COMMITTED will
5797+
* already be set here, but there's a small chance not.
5798+
*/
5799+
frz->t_infomask |= HEAP_XMIN_COMMITTED;
5800+
changed = true;
5801+
}
57395802
}
57405803

57415804
/*
@@ -5755,6 +5818,7 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
57555818
uint16 flags;
57565819

57575820
newxmax = FreezeMultiXactId(xid, tuple->t_infomask,
5821+
relfrozenxid, relminmxid,
57585822
cutoff_xid, cutoff_multi, &flags);
57595823

57605824
if (flags & FRM_INVALIDATE_XMAX)
@@ -5800,10 +5864,30 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
58005864
Assert(flags & FRM_NOOP);
58015865
}
58025866
}
5803-
else if (TransactionIdIsNormal(xid) &&
5804-
TransactionIdPrecedes(xid, cutoff_xid))
5867+
else if (TransactionIdIsNormal(xid))
58055868
{
5806-
freeze_xmax = true;
5869+
if (TransactionIdPrecedes(xid, relfrozenxid))
5870+
ereport(ERROR,
5871+
(errcode(ERRCODE_DATA_CORRUPTED),
5872+
errmsg_internal("found xmax %u from before relfrozenxid %u",
5873+
xid, relfrozenxid)));
5874+
5875+
if (TransactionIdPrecedes(xid, cutoff_xid))
5876+
{
5877+
/*
5878+
* If we freeze xmax, make absolutely sure that it's not an XID
5879+
* that is important. (Note, a lock-only xmax can be removed
5880+
* independent of committedness, since a committed lock holder has
5881+
* released the lock).
5882+
*/
5883+
if (!(tuple->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
5884+
TransactionIdDidCommit(xid))
5885+
ereport(ERROR,
5886+
(errcode(ERRCODE_DATA_CORRUPTED),
5887+
errmsg_internal("cannot freeze committed xmax %u",
5888+
xid)));
5889+
freeze_xmax = true;
5890+
}
58075891
}
58085892

58095893
if (freeze_xmax)
@@ -5900,13 +5984,16 @@ heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
59005984
* Useful for callers like CLUSTER that perform their own WAL logging.
59015985
*/
59025986
bool
5903-
heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
5904-
TransactionId cutoff_multi)
5987+
heap_freeze_tuple(HeapTupleHeader tuple,
5988+
TransactionId relfrozenxid, TransactionId relminmxid,
5989+
TransactionId cutoff_xid, TransactionId cutoff_multi)
59055990
{
59065991
xl_heap_freeze_tuple frz;
59075992
bool do_freeze;
59085993

5909-
do_freeze = heap_prepare_freeze_tuple(tuple, cutoff_xid, cutoff_multi,
5994+
do_freeze = heap_prepare_freeze_tuple(tuple,
5995+
relfrozenxid, relminmxid,
5996+
cutoff_xid, cutoff_multi,
59105997
&frz);
59115998

59125999
/*

src/backend/access/heap/rewriteheap.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,10 @@ rewrite_heap_tuple(RewriteState state,
348348
* While we have our hands on the tuple, we may as well freeze any
349349
* very-old xmin or xmax, so that future VACUUM effort can be saved.
350350
*/
351-
heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
351+
heap_freeze_tuple(new_tuple->t_data,
352+
state->rs_old_rel->rd_rel->relfrozenxid,
353+
state->rs_old_rel->rd_rel->relminmxid,
354+
state->rs_freeze_xid,
352355
state->rs_cutoff_multi);
353356

354357
/*

src/backend/commands/vacuumlazy.c

+13-2
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
420420
blkno;
421421
HeapTupleData tuple;
422422
char *relname;
423+
TransactionId relfrozenxid = onerel->rd_rel->relfrozenxid;
424+
TransactionId relminmxid = onerel->rd_rel->relminmxid;
423425
BlockNumber empty_pages,
424426
vacuumed_pages;
425427
double num_tuples,
@@ -815,6 +817,13 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
815817
* tuple, we choose to keep it, because it'll be a lot
816818
* cheaper to get rid of it in the next pruning pass than
817819
* to treat it like an indexed tuple.
820+
*
821+
* If this were to happen for a tuple that actually needed
822+
* to be deleted, we'd be in trouble, because it'd
823+
* possibly leave a tuple below the relation's xmin
824+
* horizon alive. heap_prepare_freeze_tuple() is prepared
825+
* to detect that case and abort the transaction,
826+
* preventing corruption.
818827
*/
819828
if (HeapTupleIsHotUpdated(&tuple) ||
820829
HeapTupleIsHeapOnly(&tuple))
@@ -904,8 +913,10 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
904913
* Each non-removable tuple must be checked to see if it needs
905914
* freezing. Note we already have exclusive buffer lock.
906915
*/
907-
if (heap_prepare_freeze_tuple(tuple.t_data, FreezeLimit,
908-
MultiXactCutoff, &frozen[nfrozen]))
916+
if (heap_prepare_freeze_tuple(tuple.t_data,
917+
relfrozenxid, relminmxid,
918+
FreezeLimit, MultiXactCutoff,
919+
&frozen[nfrozen]))
909920
frozen[nfrozen++].offset = offnum;
910921
}
911922
} /* scan along page */

src/include/access/heapam.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
146146
bool follow_update,
147147
Buffer *buffer, HeapUpdateFailureData *hufd);
148148
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
149-
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
150-
TransactionId cutoff_multi);
149+
extern bool heap_freeze_tuple(HeapTupleHeader tuple,
150+
TransactionId relfrozenxid, TransactionId relminmxid,
151+
TransactionId cutoff_xid, TransactionId cutoff_multi);
151152
extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
152153
MultiXactId cutoff_multi, Buffer buf);
153154

src/include/access/heapam_xlog.h

+2
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
311311
TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
312312
int ntuples);
313313
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
314+
TransactionId relfrozenxid,
315+
TransactionId relminmxid,
314316
TransactionId cutoff_xid,
315317
TransactionId cutoff_multi,
316318
xl_heap_freeze_tuple *frz);

0 commit comments

Comments
 (0)