mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-12-27 08:39:28 +08:00
amcheck: Fix verify_heapam's tuple visibility checking rules.
We now follow the order of checks from HeapTupleSatisfies* more closely to avoid coming to erroneous conclusions. Mark Dilger and Robert Haas Discussion: http://postgr.es/m/CA+Tgmob6sii0yTvULYJ0Vq4w6ZBmj7zUhddL3b+SKDi9z9NA7Q@mail.gmail.com
This commit is contained in:
parent
ec03f2df17
commit
3b6c1259f9
@ -46,6 +46,7 @@ typedef enum XidBoundsViolation
|
||||
typedef enum XidCommitStatus
|
||||
{
|
||||
XID_COMMITTED,
|
||||
XID_IS_CURRENT_XID,
|
||||
XID_IN_PROGRESS,
|
||||
XID_ABORTED
|
||||
} XidCommitStatus;
|
||||
@ -72,6 +73,8 @@ typedef struct HeapCheckContext
|
||||
TransactionId oldest_xid; /* ShmemVariableCache->oldestXid */
|
||||
FullTransactionId oldest_fxid; /* 64-bit version of oldest_xid, computed
|
||||
* relative to next_fxid */
|
||||
TransactionId safe_xmin; /* this XID and newer ones can't become
|
||||
* all-visible while we're running */
|
||||
|
||||
/*
|
||||
* Cached copy of value from MultiXactState
|
||||
@ -113,6 +116,9 @@ typedef struct HeapCheckContext
|
||||
uint32 offset; /* offset in tuple data */
|
||||
AttrNumber attnum;
|
||||
|
||||
/* True if tuple's xmax makes it eligible for pruning */
|
||||
bool tuple_could_be_pruned;
|
||||
|
||||
/* Values for iterating over toast for the attribute */
|
||||
int32 chunkno;
|
||||
int32 attrsize;
|
||||
@ -133,8 +139,8 @@ static void check_tuple(HeapCheckContext *ctx);
|
||||
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx);
|
||||
|
||||
static bool check_tuple_attribute(HeapCheckContext *ctx);
|
||||
static bool check_tuple_header_and_visibilty(HeapTupleHeader tuphdr,
|
||||
HeapCheckContext *ctx);
|
||||
static bool check_tuple_header(HeapCheckContext *ctx);
|
||||
static bool check_tuple_visibility(HeapCheckContext *ctx);
|
||||
|
||||
static void report_corruption(HeapCheckContext *ctx, char *msg);
|
||||
static TupleDesc verify_heapam_tupdesc(void);
|
||||
@ -248,6 +254,12 @@ verify_heapam(PG_FUNCTION_ARGS)
|
||||
memset(&ctx, 0, sizeof(HeapCheckContext));
|
||||
ctx.cached_xid = InvalidTransactionId;
|
||||
|
||||
/*
|
||||
* Any xmin newer than the xmin of our snapshot can't become all-visible
|
||||
* while we're running.
|
||||
*/
|
||||
ctx.safe_xmin = GetTransactionSnapshot()->xmin;
|
||||
|
||||
/*
|
||||
* If we report corruption when not examining some individual attribute,
|
||||
* we need attnum to be reported as NULL. Set that up before any
|
||||
@ -555,16 +567,11 @@ verify_heapam_tupdesc(void)
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for tuple header corruption and tuple visibility.
|
||||
*
|
||||
* Since we do not hold a snapshot, tuple visibility is not a question of
|
||||
* whether we should be able to see the tuple relative to any particular
|
||||
* snapshot, but rather a question of whether it is safe and reasonable to
|
||||
* check the tuple attributes.
|
||||
* Check for tuple header corruption.
|
||||
*
|
||||
* Some kinds of corruption make it unsafe to check the tuple attributes, for
|
||||
* example when the line pointer refers to a range of bytes outside the page.
|
||||
* In such cases, we return false (not visible) after recording appropriate
|
||||
* In such cases, we return false (not checkable) after recording appropriate
|
||||
* corruption messages.
|
||||
*
|
||||
* Some other kinds of tuple header corruption confuse the question of where
|
||||
@ -576,29 +583,18 @@ verify_heapam_tupdesc(void)
|
||||
*
|
||||
* Other kinds of tuple header corruption do not bear on the question of
|
||||
* whether the tuple attributes can be checked, so we record corruption
|
||||
* messages for them but do not base our visibility determination on them. (In
|
||||
* other words, we do not return false merely because we detected them.)
|
||||
* messages for them but we do not return false merely because we detected
|
||||
* them.
|
||||
*
|
||||
* For visibility determination not specifically related to corruption, what we
|
||||
* want to know is if a tuple is potentially visible to any running
|
||||
* transaction. If you are tempted to replace this function's visibility logic
|
||||
* with a call to another visibility checking function, keep in mind that this
|
||||
* function does not update hint bits, as it seems imprudent to write hint bits
|
||||
* (or anything at all) to a table during a corruption check. Nor does this
|
||||
* function bother classifying tuple visibility beyond a boolean visible vs.
|
||||
* not visible.
|
||||
*
|
||||
* The caller should already have checked that xmin and xmax are not out of
|
||||
* bounds for the relation.
|
||||
*
|
||||
* Returns whether the tuple is both visible and sufficiently sensible to
|
||||
* undergo attribute checks.
|
||||
* Returns whether the tuple is sufficiently sensible to undergo visibility and
|
||||
* attribute checks.
|
||||
*/
|
||||
static bool
|
||||
check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
|
||||
check_tuple_header(HeapCheckContext *ctx)
|
||||
{
|
||||
HeapTupleHeader tuphdr = ctx->tuphdr;
|
||||
uint16 infomask = tuphdr->t_infomask;
|
||||
bool header_garbled = false;
|
||||
bool result = true;
|
||||
unsigned expected_hoff;
|
||||
|
||||
if (ctx->tuphdr->t_hoff > ctx->lp_len)
|
||||
@ -606,7 +602,7 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
|
||||
report_corruption(ctx,
|
||||
psprintf("data begins at offset %u beyond the tuple length %u",
|
||||
ctx->tuphdr->t_hoff, ctx->lp_len));
|
||||
header_garbled = true;
|
||||
result = false;
|
||||
}
|
||||
|
||||
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
|
||||
@ -616,9 +612,9 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
|
||||
pstrdup("multixact should not be marked committed"));
|
||||
|
||||
/*
|
||||
* This condition is clearly wrong, but we do not consider the header
|
||||
* garbled, because we don't rely on this property for determining if
|
||||
* the tuple is visible or for interpreting other relevant header
|
||||
* This condition is clearly wrong, but it's not enough to justify
|
||||
* skipping further checks, because we don't rely on this to determine
|
||||
* whether the tuple is visible or to interpret other relevant header
|
||||
* fields.
|
||||
*/
|
||||
}
|
||||
@ -645,175 +641,449 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
|
||||
report_corruption(ctx,
|
||||
psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)",
|
||||
expected_hoff, ctx->tuphdr->t_hoff, ctx->natts));
|
||||
header_garbled = true;
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (header_garbled)
|
||||
return false; /* checking of this tuple should not continue */
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks tuple visibility so we know which further checks are safe to
|
||||
* perform.
|
||||
*
|
||||
* If a tuple could have been inserted by a transaction that also added a
|
||||
* column to the table, but which ultimately did not commit, or which has not
|
||||
* yet committed, then the table's current TupleDesc might differ from the one
|
||||
* used to construct this tuple, so we must not check it.
|
||||
*
|
||||
* As a special case, if our own transaction inserted the tuple, even if we
|
||||
* added a column to the table, our TupleDesc should match. We could check the
|
||||
* tuple, but choose not to do so.
|
||||
*
|
||||
* If a tuple has been updated or deleted, we can still read the old tuple for
|
||||
* corruption checking purposes, as long as we are careful about concurrent
|
||||
* vacuums. The main table tuple itself cannot be vacuumed away because we
|
||||
* hold a buffer lock on the page, but if the deleting transaction is older
|
||||
* than our transaction snapshot's xmin, then vacuum could remove the toast at
|
||||
* any time, so we must not try to follow TOAST pointers.
|
||||
*
|
||||
* If xmin or xmax values are older than can be checked against clog, or appear
|
||||
* to be in the future (possibly due to wrap-around), then we cannot make a
|
||||
* determination about the visibility of the tuple, so we skip further checks.
|
||||
*
|
||||
* Returns true if the tuple itself should be checked, false otherwise. Sets
|
||||
* ctx->tuple_could_be_pruned if the tuple -- and thus also any associated
|
||||
* TOAST tuples -- are eligible for pruning.
|
||||
*/
|
||||
static bool
|
||||
check_tuple_visibility(HeapCheckContext *ctx)
|
||||
{
|
||||
TransactionId xmin;
|
||||
TransactionId xvac;
|
||||
TransactionId xmax;
|
||||
XidCommitStatus xmin_status;
|
||||
XidCommitStatus xvac_status;
|
||||
XidCommitStatus xmax_status;
|
||||
HeapTupleHeader tuphdr = ctx->tuphdr;
|
||||
|
||||
ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */
|
||||
|
||||
/* If xmin is normal, it should be within valid range */
|
||||
xmin = HeapTupleHeaderGetXmin(tuphdr);
|
||||
switch (get_xid_status(xmin, ctx, &xmin_status))
|
||||
{
|
||||
case XID_INVALID:
|
||||
case XID_BOUNDS_OK:
|
||||
break;
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u",
|
||||
xmin,
|
||||
EpochFromFullTransactionId(ctx->next_fxid),
|
||||
XidFromFullTransactionId(ctx->next_fxid)));
|
||||
return false;
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmin %u precedes oldest valid transaction ID %u:%u",
|
||||
xmin,
|
||||
EpochFromFullTransactionId(ctx->oldest_fxid),
|
||||
XidFromFullTransactionId(ctx->oldest_fxid)));
|
||||
return false;
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmin %u precedes relation freeze threshold %u:%u",
|
||||
xmin,
|
||||
EpochFromFullTransactionId(ctx->relfrozenfxid),
|
||||
XidFromFullTransactionId(ctx->relfrozenfxid)));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ok, we can examine the header for tuple visibility purposes, though we
|
||||
* still need to be careful about a few remaining types of header
|
||||
* corruption. This logic roughly follows that of
|
||||
* HeapTupleSatisfiesVacuum. Where possible the comments indicate which
|
||||
* HTSV_Result we think that function might return for this tuple.
|
||||
* Has inserting transaction committed?
|
||||
*/
|
||||
if (!HeapTupleHeaderXminCommitted(tuphdr))
|
||||
{
|
||||
TransactionId raw_xmin = HeapTupleHeaderGetRawXmin(tuphdr);
|
||||
|
||||
if (HeapTupleHeaderXminInvalid(tuphdr))
|
||||
return false; /* HEAPTUPLE_DEAD */
|
||||
return false; /* inserter aborted, don't check */
|
||||
/* Used by pre-9.0 binary upgrades */
|
||||
else if (infomask & HEAP_MOVED_OFF ||
|
||||
infomask & HEAP_MOVED_IN)
|
||||
else if (tuphdr->t_infomask & HEAP_MOVED_OFF)
|
||||
{
|
||||
XidCommitStatus status;
|
||||
TransactionId xvac = HeapTupleHeaderGetXvac(tuphdr);
|
||||
xvac = HeapTupleHeaderGetXvac(tuphdr);
|
||||
|
||||
switch (get_xid_status(xvac, ctx, &status))
|
||||
switch (get_xid_status(xvac, ctx, &xvac_status))
|
||||
{
|
||||
case XID_INVALID:
|
||||
report_corruption(ctx,
|
||||
pstrdup("old-style VACUUM FULL transaction ID is invalid"));
|
||||
return false; /* corrupt */
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u equals or exceeds next valid transaction ID %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->next_fxid),
|
||||
XidFromFullTransactionId(ctx->next_fxid)));
|
||||
return false; /* corrupt */
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u precedes relation freeze threshold %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->relfrozenfxid),
|
||||
XidFromFullTransactionId(ctx->relfrozenfxid)));
|
||||
return false; /* corrupt */
|
||||
break;
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u precedes oldest valid transaction ID %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->oldest_fxid),
|
||||
XidFromFullTransactionId(ctx->oldest_fxid)));
|
||||
return false; /* corrupt */
|
||||
break;
|
||||
case XID_BOUNDS_OK:
|
||||
switch (status)
|
||||
{
|
||||
case XID_IN_PROGRESS:
|
||||
return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
|
||||
case XID_COMMITTED:
|
||||
case XID_ABORTED:
|
||||
return false; /* HEAPTUPLE_DEAD */
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
XidCommitStatus status;
|
||||
|
||||
switch (get_xid_status(raw_xmin, ctx, &status))
|
||||
{
|
||||
case XID_INVALID:
|
||||
report_corruption(ctx,
|
||||
pstrdup("raw xmin is invalid"));
|
||||
pstrdup("old-style VACUUM FULL transaction ID for moved off tuple is invalid"));
|
||||
return false;
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("raw xmin %u equals or exceeds next valid transaction ID %u:%u",
|
||||
raw_xmin,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->next_fxid),
|
||||
XidFromFullTransactionId(ctx->next_fxid)));
|
||||
return false; /* corrupt */
|
||||
return false;
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("raw xmin %u precedes relation freeze threshold %u:%u",
|
||||
raw_xmin,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->relfrozenfxid),
|
||||
XidFromFullTransactionId(ctx->relfrozenfxid)));
|
||||
return false; /* corrupt */
|
||||
return false;
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("raw xmin %u precedes oldest valid transaction ID %u:%u",
|
||||
raw_xmin,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->oldest_fxid),
|
||||
XidFromFullTransactionId(ctx->oldest_fxid)));
|
||||
return false; /* corrupt */
|
||||
return false;
|
||||
case XID_BOUNDS_OK:
|
||||
switch (status)
|
||||
{
|
||||
case XID_COMMITTED:
|
||||
break;
|
||||
case XID_IN_PROGRESS:
|
||||
return true; /* insert or delete in progress */
|
||||
case XID_ABORTED:
|
||||
return false; /* HEAPTUPLE_DEAD */
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (xvac_status)
|
||||
{
|
||||
case XID_IS_CURRENT_XID:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
|
||||
xvac));
|
||||
return false;
|
||||
case XID_IN_PROGRESS:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
|
||||
xvac));
|
||||
return false;
|
||||
|
||||
case XID_COMMITTED:
|
||||
/*
|
||||
* The tuple is dead, because the xvac transaction moved
|
||||
* it off and comitted. It's checkable, but also prunable.
|
||||
*/
|
||||
return true;
|
||||
|
||||
case XID_ABORTED:
|
||||
/*
|
||||
* The original xmin must have committed, because the xvac
|
||||
* transaction tried to move it later. Since xvac is
|
||||
* aborted, whether it's still alive now depends on the
|
||||
* status of xmax.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(infomask & HEAP_XMAX_INVALID) && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
|
||||
{
|
||||
if (infomask & HEAP_XMAX_IS_MULTI)
|
||||
/* Used by pre-9.0 binary upgrades */
|
||||
else if (tuphdr->t_infomask & HEAP_MOVED_IN)
|
||||
{
|
||||
XidCommitStatus status;
|
||||
TransactionId xmax = HeapTupleGetUpdateXid(tuphdr);
|
||||
xvac = HeapTupleHeaderGetXvac(tuphdr);
|
||||
|
||||
switch (get_xid_status(xmax, ctx, &status))
|
||||
switch (get_xid_status(xvac, ctx, &xvac_status))
|
||||
{
|
||||
/* not LOCKED_ONLY, so it has to have an xmax */
|
||||
case XID_INVALID:
|
||||
report_corruption(ctx,
|
||||
pstrdup("xmax is invalid"));
|
||||
return false; /* corrupt */
|
||||
pstrdup("old-style VACUUM FULL transaction ID for moved in tuple is invalid"));
|
||||
return false;
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
|
||||
xmax,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->next_fxid),
|
||||
XidFromFullTransactionId(ctx->next_fxid)));
|
||||
return false; /* corrupt */
|
||||
return false;
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmax %u precedes relation freeze threshold %u:%u",
|
||||
xmax,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->relfrozenfxid),
|
||||
XidFromFullTransactionId(ctx->relfrozenfxid)));
|
||||
return false; /* corrupt */
|
||||
return false;
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
|
||||
xmax,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
|
||||
xvac,
|
||||
EpochFromFullTransactionId(ctx->oldest_fxid),
|
||||
XidFromFullTransactionId(ctx->oldest_fxid)));
|
||||
return false; /* corrupt */
|
||||
return false;
|
||||
case XID_BOUNDS_OK:
|
||||
switch (status)
|
||||
{
|
||||
case XID_IN_PROGRESS:
|
||||
return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
|
||||
case XID_COMMITTED:
|
||||
case XID_ABORTED:
|
||||
return false; /* HEAPTUPLE_RECENTLY_DEAD or
|
||||
* HEAPTUPLE_DEAD */
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ok, the tuple is live */
|
||||
switch (xvac_status)
|
||||
{
|
||||
case XID_IS_CURRENT_XID:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
|
||||
xvac));
|
||||
return false;
|
||||
case XID_IN_PROGRESS:
|
||||
report_corruption(ctx,
|
||||
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
|
||||
xvac));
|
||||
return false;
|
||||
|
||||
case XID_COMMITTED:
|
||||
/*
|
||||
* The original xmin must have committed, because the xvac
|
||||
* transaction moved it later. Whether it's still alive
|
||||
* now depends on the status of xmax.
|
||||
*/
|
||||
break;
|
||||
|
||||
case XID_ABORTED:
|
||||
/*
|
||||
* The tuple is dead, because the xvac transaction moved
|
||||
* it off and comitted. It's checkable, but also prunable.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (xmin_status != XID_COMMITTED)
|
||||
{
|
||||
/*
|
||||
* Inserting transaction is not in progress, and not committed, so
|
||||
* it might have changed the TupleDesc in ways we don't know about.
|
||||
* Thus, don't try to check the tuple structure.
|
||||
*
|
||||
* If xmin_status happens to be XID_IS_CURRENT_XID, then in theory
|
||||
* any such DDL changes ought to be visible to us, so perhaps
|
||||
* we could check anyway in that case. But, for now, let's be
|
||||
* conservate and treat this like any other uncommitted insert.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
else if (!(infomask & HEAP_XMAX_COMMITTED))
|
||||
return true; /* HEAPTUPLE_DELETE_IN_PROGRESS or
|
||||
* HEAPTUPLE_LIVE */
|
||||
else
|
||||
return false; /* HEAPTUPLE_RECENTLY_DEAD or HEAPTUPLE_DEAD */
|
||||
}
|
||||
return true; /* not dead */
|
||||
|
||||
/*
|
||||
* Okay, the inserter committed, so it was good at some point. Now what
|
||||
* about the deleting transaction?
|
||||
*/
|
||||
|
||||
if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
|
||||
{
|
||||
/*
|
||||
* xmax is a multixact, so sanity-check the MXID. Note that we do this
|
||||
* prior to checking for HEAP_XMAX_INVALID or HEAP_XMAX_IS_LOCKED_ONLY.
|
||||
* This might therefore complain about things that wouldn't actually
|
||||
* be a problem during a normal scan, but eventually we're going to
|
||||
* have to freeze, and that process will ignore hint bits.
|
||||
*
|
||||
* Even if the MXID is out of range, we still know that the original
|
||||
* insert committed, so we can check the tuple itself. However, we
|
||||
* can't rule out the possibility that this tuple is dead, so don't
|
||||
* clear ctx->tuple_could_be_pruned. Possibly we should go ahead and
|
||||
* clear that flag anyway if HEAP_XMAX_INVALID is set or if
|
||||
* HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side
|
||||
* of avoiding possibly-bogus complaints about missing TOAST entries.
|
||||
*/
|
||||
xmax = HeapTupleHeaderGetRawXmax(tuphdr);
|
||||
switch (check_mxid_valid_in_rel(xmax, ctx))
|
||||
{
|
||||
case XID_INVALID:
|
||||
report_corruption(ctx,
|
||||
pstrdup("multitransaction ID is invalid"));
|
||||
return true;
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
|
||||
xmax, ctx->relminmxid));
|
||||
return true;
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
|
||||
xmax, ctx->oldest_mxact));
|
||||
return true;
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
|
||||
xmax,
|
||||
ctx->next_mxact));
|
||||
return true;
|
||||
case XID_BOUNDS_OK:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tuphdr->t_infomask & HEAP_XMAX_INVALID)
|
||||
{
|
||||
/*
|
||||
* This tuple is live. A concurrently running transaction could
|
||||
* delete it before we get around to checking the toast, but any such
|
||||
* running transaction is surely not less than our safe_xmin, so the
|
||||
* toast cannot be vacuumed out from under us.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask))
|
||||
{
|
||||
/*
|
||||
* "Deleting" xact really only locked it, so the tuple is live in any
|
||||
* case. As above, a concurrently running transaction could delete
|
||||
* it, but it cannot be vacuumed out from under us.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
|
||||
{
|
||||
/*
|
||||
* We already checked above that this multixact is within limits for
|
||||
* this table. Now check the update xid from this multixact.
|
||||
*/
|
||||
xmax = HeapTupleGetUpdateXid(tuphdr);
|
||||
switch (get_xid_status(xmax, ctx, &xmax_status))
|
||||
{
|
||||
case XID_INVALID:
|
||||
/* not LOCKED_ONLY, so it has to have an xmax */
|
||||
report_corruption(ctx,
|
||||
pstrdup("update xid is invalid"));
|
||||
return true;
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
|
||||
xmax,
|
||||
EpochFromFullTransactionId(ctx->next_fxid),
|
||||
XidFromFullTransactionId(ctx->next_fxid)));
|
||||
return true;
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("update xid %u precedes relation freeze threshold %u:%u",
|
||||
xmax,
|
||||
EpochFromFullTransactionId(ctx->relfrozenfxid),
|
||||
XidFromFullTransactionId(ctx->relfrozenfxid)));
|
||||
return true;
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
|
||||
xmax,
|
||||
EpochFromFullTransactionId(ctx->oldest_fxid),
|
||||
XidFromFullTransactionId(ctx->oldest_fxid)));
|
||||
return true;
|
||||
case XID_BOUNDS_OK:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (xmax_status)
|
||||
{
|
||||
case XID_IS_CURRENT_XID:
|
||||
case XID_IN_PROGRESS:
|
||||
|
||||
/*
|
||||
* The delete is in progress, so it cannot be visible to our
|
||||
* snapshot.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = false;
|
||||
break;
|
||||
case XID_COMMITTED:
|
||||
|
||||
/*
|
||||
* The delete committed. Whether the toast can be vacuumed
|
||||
* away depends on how old the deleting transaction is.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = TransactionIdPrecedes(xmax,
|
||||
ctx->safe_xmin);
|
||||
break;
|
||||
case XID_ABORTED:
|
||||
/*
|
||||
* The delete aborted or crashed. The tuple is still live.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Tuple itself is checkable even if it's dead. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* xmax is an XID, not a MXID. Sanity check it. */
|
||||
xmax = HeapTupleHeaderGetRawXmax(tuphdr);
|
||||
switch (get_xid_status(xmax, ctx, &xmax_status))
|
||||
{
|
||||
case XID_IN_FUTURE:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
|
||||
xmax,
|
||||
EpochFromFullTransactionId(ctx->next_fxid),
|
||||
XidFromFullTransactionId(ctx->next_fxid)));
|
||||
return false; /* corrupt */
|
||||
case XID_PRECEDES_RELMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmax %u precedes relation freeze threshold %u:%u",
|
||||
xmax,
|
||||
EpochFromFullTransactionId(ctx->relfrozenfxid),
|
||||
XidFromFullTransactionId(ctx->relfrozenfxid)));
|
||||
return false; /* corrupt */
|
||||
case XID_PRECEDES_CLUSTERMIN:
|
||||
report_corruption(ctx,
|
||||
psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
|
||||
xmax,
|
||||
EpochFromFullTransactionId(ctx->oldest_fxid),
|
||||
XidFromFullTransactionId(ctx->oldest_fxid)));
|
||||
return false; /* corrupt */
|
||||
case XID_BOUNDS_OK:
|
||||
case XID_INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether the toast can be vacuumed away depends on how old the deleting
|
||||
* transaction is.
|
||||
*/
|
||||
switch (xmax_status)
|
||||
{
|
||||
case XID_IS_CURRENT_XID:
|
||||
case XID_IN_PROGRESS:
|
||||
|
||||
/*
|
||||
* The delete is in progress, so it cannot be visible to our
|
||||
* snapshot.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = false;
|
||||
break;
|
||||
|
||||
case XID_COMMITTED:
|
||||
/*
|
||||
* The delete committed. Whether the toast can be vacuumed away
|
||||
* depends on how old the deleting transaction is.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = TransactionIdPrecedes(xmax,
|
||||
ctx->safe_xmin);
|
||||
break;
|
||||
|
||||
case XID_ABORTED:
|
||||
/*
|
||||
* The delete aborted or crashed. The tuple is still live.
|
||||
*/
|
||||
ctx->tuple_could_be_pruned = false;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Tuple itself is checkable even if it's dead. */
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Check the current toast tuple against the state tracked in ctx, recording
|
||||
* any corruption found in ctx->tupstore.
|
||||
@ -1247,7 +1517,10 @@ check_tuple(HeapCheckContext *ctx)
|
||||
* corrupt to continue checking, or if the tuple is not visible to anyone,
|
||||
* we cannot continue with other checks.
|
||||
*/
|
||||
if (!check_tuple_header_and_visibilty(ctx->tuphdr, ctx))
|
||||
if (!check_tuple_header(ctx))
|
||||
return;
|
||||
|
||||
if (!check_tuple_visibility(ctx))
|
||||
return;
|
||||
|
||||
/*
|
||||
@ -1448,13 +1721,13 @@ get_xid_status(TransactionId xid, HeapCheckContext *ctx,
|
||||
if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid))
|
||||
{
|
||||
if (TransactionIdIsCurrentTransactionId(xid))
|
||||
*status = XID_IS_CURRENT_XID;
|
||||
else if (TransactionIdIsInProgress(xid))
|
||||
*status = XID_IN_PROGRESS;
|
||||
else if (TransactionIdDidCommit(xid))
|
||||
*status = XID_COMMITTED;
|
||||
else if (TransactionIdDidAbort(xid))
|
||||
*status = XID_ABORTED;
|
||||
else
|
||||
*status = XID_IN_PROGRESS;
|
||||
*status = XID_ABORTED;
|
||||
}
|
||||
LWLockRelease(XactTruncationLock);
|
||||
ctx->cached_xid = xid;
|
||||
|
Loading…
Reference in New Issue
Block a user