Skip to content

Commit 3b6c125

Browse files
committedApr 1, 2021
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

File tree

1 file changed

+414
-141
lines changed

1 file changed

+414
-141
lines changed
 

‎contrib/amcheck/verify_heapam.c

Lines changed: 414 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef enum XidBoundsViolation
4646
typedef enum XidCommitStatus
4747
{
4848
XID_COMMITTED,
49+
XID_IS_CURRENT_XID,
4950
XID_IN_PROGRESS,
5051
XID_ABORTED
5152
} XidCommitStatus;
@@ -72,6 +73,8 @@ typedef struct HeapCheckContext
7273
TransactionId oldest_xid; /* ShmemVariableCache->oldestXid */
7374
FullTransactionId oldest_fxid; /* 64-bit version of oldest_xid, computed
7475
* relative to next_fxid */
76+
TransactionId safe_xmin; /* this XID and newer ones can't become
77+
* all-visible while we're running */
7578

7679
/*
7780
* Cached copy of value from MultiXactState
@@ -113,6 +116,9 @@ typedef struct HeapCheckContext
113116
uint32 offset; /* offset in tuple data */
114117
AttrNumber attnum;
115118

119+
/* True if tuple's xmax makes it eligible for pruning */
120+
bool tuple_could_be_pruned;
121+
116122
/* Values for iterating over toast for the attribute */
117123
int32 chunkno;
118124
int32 attrsize;
@@ -133,8 +139,8 @@ static void check_tuple(HeapCheckContext *ctx);
133139
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx);
134140

135141
static bool check_tuple_attribute(HeapCheckContext *ctx);
136-
static bool check_tuple_header_and_visibilty(HeapTupleHeader tuphdr,
137-
HeapCheckContext *ctx);
142+
static bool check_tuple_header(HeapCheckContext *ctx);
143+
static bool check_tuple_visibility(HeapCheckContext *ctx);
138144

139145
static void report_corruption(HeapCheckContext *ctx, char *msg);
140146
static TupleDesc verify_heapam_tupdesc(void);
@@ -248,6 +254,12 @@ verify_heapam(PG_FUNCTION_ARGS)
248254
memset(&ctx, 0, sizeof(HeapCheckContext));
249255
ctx.cached_xid = InvalidTransactionId;
250256

257+
/*
258+
* Any xmin newer than the xmin of our snapshot can't become all-visible
259+
* while we're running.
260+
*/
261+
ctx.safe_xmin = GetTransactionSnapshot()->xmin;
262+
251263
/*
252264
* If we report corruption when not examining some individual attribute,
253265
* we need attnum to be reported as NULL. Set that up before any
@@ -555,16 +567,11 @@ verify_heapam_tupdesc(void)
555567
}
556568

557569
/*
558-
* Check for tuple header corruption and tuple visibility.
559-
*
560-
* Since we do not hold a snapshot, tuple visibility is not a question of
561-
* whether we should be able to see the tuple relative to any particular
562-
* snapshot, but rather a question of whether it is safe and reasonable to
563-
* check the tuple attributes.
570+
* Check for tuple header corruption.
564571
*
565572
* Some kinds of corruption make it unsafe to check the tuple attributes, for
566573
* example when the line pointer refers to a range of bytes outside the page.
567-
* In such cases, we return false (not visible) after recording appropriate
574+
* In such cases, we return false (not checkable) after recording appropriate
568575
* corruption messages.
569576
*
570577
* Some other kinds of tuple header corruption confuse the question of where
@@ -576,37 +583,26 @@ verify_heapam_tupdesc(void)
576583
*
577584
* Other kinds of tuple header corruption do not bear on the question of
578585
* whether the tuple attributes can be checked, so we record corruption
579-
* messages for them but do not base our visibility determination on them. (In
580-
* other words, we do not return false merely because we detected them.)
581-
*
582-
* For visibility determination not specifically related to corruption, what we
583-
* want to know is if a tuple is potentially visible to any running
584-
* transaction. If you are tempted to replace this function's visibility logic
585-
* with a call to another visibility checking function, keep in mind that this
586-
* function does not update hint bits, as it seems imprudent to write hint bits
587-
* (or anything at all) to a table during a corruption check. Nor does this
588-
* function bother classifying tuple visibility beyond a boolean visible vs.
589-
* not visible.
590-
*
591-
* The caller should already have checked that xmin and xmax are not out of
592-
* bounds for the relation.
586+
* messages for them but we do not return false merely because we detected
587+
* them.
593588
*
594-
* Returns whether the tuple is both visible and sufficiently sensible to
595-
* undergo attribute checks.
589+
* Returns whether the tuple is sufficiently sensible to undergo visibility and
590+
* attribute checks.
596591
*/
597592
static bool
598-
check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
593+
check_tuple_header(HeapCheckContext *ctx)
599594
{
595+
HeapTupleHeader tuphdr = ctx->tuphdr;
600596
uint16 infomask = tuphdr->t_infomask;
601-
bool header_garbled = false;
597+
bool result = true;
602598
unsigned expected_hoff;
603599

604600
if (ctx->tuphdr->t_hoff > ctx->lp_len)
605601
{
606602
report_corruption(ctx,
607603
psprintf("data begins at offset %u beyond the tuple length %u",
608604
ctx->tuphdr->t_hoff, ctx->lp_len));
609-
header_garbled = true;
605+
result = false;
610606
}
611607

612608
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
@@ -616,9 +612,9 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
616612
pstrdup("multixact should not be marked committed"));
617613

618614
/*
619-
* This condition is clearly wrong, but we do not consider the header
620-
* garbled, because we don't rely on this property for determining if
621-
* the tuple is visible or for interpreting other relevant header
615+
* This condition is clearly wrong, but it's not enough to justify
616+
* skipping further checks, because we don't rely on this to determine
617+
* whether the tuple is visible or to interpret other relevant header
622618
* fields.
623619
*/
624620
}
@@ -645,175 +641,449 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
645641
report_corruption(ctx,
646642
psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)",
647643
expected_hoff, ctx->tuphdr->t_hoff, ctx->natts));
648-
header_garbled = true;
644+
result = false;
649645
}
650646

651-
if (header_garbled)
652-
return false; /* checking of this tuple should not continue */
647+
return result;
648+
}
649+
650+
/*
651+
* Checks tuple visibility so we know which further checks are safe to
652+
* perform.
653+
*
654+
* If a tuple could have been inserted by a transaction that also added a
655+
* column to the table, but which ultimately did not commit, or which has not
656+
* yet committed, then the table's current TupleDesc might differ from the one
657+
* used to construct this tuple, so we must not check it.
658+
*
659+
* As a special case, if our own transaction inserted the tuple, even if we
660+
* added a column to the table, our TupleDesc should match. We could check the
661+
* tuple, but choose not to do so.
662+
*
663+
* If a tuple has been updated or deleted, we can still read the old tuple for
664+
* corruption checking purposes, as long as we are careful about concurrent
665+
* vacuums. The main table tuple itself cannot be vacuumed away because we
666+
* hold a buffer lock on the page, but if the deleting transaction is older
667+
* than our transaction snapshot's xmin, then vacuum could remove the toast at
668+
* any time, so we must not try to follow TOAST pointers.
669+
*
670+
* If xmin or xmax values are older than can be checked against clog, or appear
671+
* to be in the future (possibly due to wrap-around), then we cannot make a
672+
* determination about the visibility of the tuple, so we skip further checks.
673+
*
674+
* Returns true if the tuple itself should be checked, false otherwise. Sets
675+
* ctx->tuple_could_be_pruned if the tuple -- and thus also any associated
676+
* TOAST tuples -- are eligible for pruning.
677+
*/
678+
static bool
679+
check_tuple_visibility(HeapCheckContext *ctx)
680+
{
681+
TransactionId xmin;
682+
TransactionId xvac;
683+
TransactionId xmax;
684+
XidCommitStatus xmin_status;
685+
XidCommitStatus xvac_status;
686+
XidCommitStatus xmax_status;
687+
HeapTupleHeader tuphdr = ctx->tuphdr;
688+
689+
ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */
690+
691+
/* If xmin is normal, it should be within valid range */
692+
xmin = HeapTupleHeaderGetXmin(tuphdr);
693+
switch (get_xid_status(xmin, ctx, &xmin_status))
694+
{
695+
case XID_INVALID:
696+
case XID_BOUNDS_OK:
697+
break;
698+
case XID_IN_FUTURE:
699+
report_corruption(ctx,
700+
psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u",
701+
xmin,
702+
EpochFromFullTransactionId(ctx->next_fxid),
703+
XidFromFullTransactionId(ctx->next_fxid)));
704+
return false;
705+
case XID_PRECEDES_CLUSTERMIN:
706+
report_corruption(ctx,
707+
psprintf("xmin %u precedes oldest valid transaction ID %u:%u",
708+
xmin,
709+
EpochFromFullTransactionId(ctx->oldest_fxid),
710+
XidFromFullTransactionId(ctx->oldest_fxid)));
711+
return false;
712+
case XID_PRECEDES_RELMIN:
713+
report_corruption(ctx,
714+
psprintf("xmin %u precedes relation freeze threshold %u:%u",
715+
xmin,
716+
EpochFromFullTransactionId(ctx->relfrozenfxid),
717+
XidFromFullTransactionId(ctx->relfrozenfxid)));
718+
return false;
719+
}
653720

654721
/*
655-
* Ok, we can examine the header for tuple visibility purposes, though we
656-
* still need to be careful about a few remaining types of header
657-
* corruption. This logic roughly follows that of
658-
* HeapTupleSatisfiesVacuum. Where possible the comments indicate which
659-
* HTSV_Result we think that function might return for this tuple.
722+
* Has inserting transaction committed?
660723
*/
661724
if (!HeapTupleHeaderXminCommitted(tuphdr))
662725
{
663-
TransactionId raw_xmin = HeapTupleHeaderGetRawXmin(tuphdr);
664-
665726
if (HeapTupleHeaderXminInvalid(tuphdr))
666-
return false; /* HEAPTUPLE_DEAD */
727+
return false; /* inserter aborted, don't check */
667728
/* Used by pre-9.0 binary upgrades */
668-
else if (infomask & HEAP_MOVED_OFF ||
669-
infomask & HEAP_MOVED_IN)
729+
else if (tuphdr->t_infomask & HEAP_MOVED_OFF)
670730
{
671-
XidCommitStatus status;
672-
TransactionId xvac = HeapTupleHeaderGetXvac(tuphdr);
731+
xvac = HeapTupleHeaderGetXvac(tuphdr);
673732

674-
switch (get_xid_status(xvac, ctx, &status))
733+
switch (get_xid_status(xvac, ctx, &xvac_status))
675734
{
676735
case XID_INVALID:
677736
report_corruption(ctx,
678-
pstrdup("old-style VACUUM FULL transaction ID is invalid"));
679-
return false; /* corrupt */
737+
pstrdup("old-style VACUUM FULL transaction ID for moved off tuple is invalid"));
738+
return false;
680739
case XID_IN_FUTURE:
681740
report_corruption(ctx,
682-
psprintf("old-style VACUUM FULL transaction ID %u equals or exceeds next valid transaction ID %u:%u",
741+
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u",
683742
xvac,
684743
EpochFromFullTransactionId(ctx->next_fxid),
685744
XidFromFullTransactionId(ctx->next_fxid)));
686-
return false; /* corrupt */
745+
return false;
687746
case XID_PRECEDES_RELMIN:
688747
report_corruption(ctx,
689-
psprintf("old-style VACUUM FULL transaction ID %u precedes relation freeze threshold %u:%u",
748+
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u",
690749
xvac,
691750
EpochFromFullTransactionId(ctx->relfrozenfxid),
692751
XidFromFullTransactionId(ctx->relfrozenfxid)));
693-
return false; /* corrupt */
694-
break;
752+
return false;
695753
case XID_PRECEDES_CLUSTERMIN:
696754
report_corruption(ctx,
697-
psprintf("old-style VACUUM FULL transaction ID %u precedes oldest valid transaction ID %u:%u",
755+
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u",
698756
xvac,
699757
EpochFromFullTransactionId(ctx->oldest_fxid),
700758
XidFromFullTransactionId(ctx->oldest_fxid)));
701-
return false; /* corrupt */
702-
break;
759+
return false;
703760
case XID_BOUNDS_OK:
704-
switch (status)
705-
{
706-
case XID_IN_PROGRESS:
707-
return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
708-
case XID_COMMITTED:
709-
case XID_ABORTED:
710-
return false; /* HEAPTUPLE_DEAD */
711-
}
761+
break;
762+
}
763+
764+
switch (xvac_status)
765+
{
766+
case XID_IS_CURRENT_XID:
767+
report_corruption(ctx,
768+
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID",
769+
xvac));
770+
return false;
771+
case XID_IN_PROGRESS:
772+
report_corruption(ctx,
773+
psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress",
774+
xvac));
775+
return false;
776+
777+
case XID_COMMITTED:
778+
/*
779+
* The tuple is dead, because the xvac transaction moved
780+
* it off and comitted. It's checkable, but also prunable.
781+
*/
782+
return true;
783+
784+
case XID_ABORTED:
785+
/*
786+
* The original xmin must have committed, because the xvac
787+
* transaction tried to move it later. Since xvac is
788+
* aborted, whether it's still alive now depends on the
789+
* status of xmax.
790+
*/
791+
break;
712792
}
713793
}
714-
else
794+
/* Used by pre-9.0 binary upgrades */
795+
else if (tuphdr->t_infomask & HEAP_MOVED_IN)
715796
{
716-
XidCommitStatus status;
797+
xvac = HeapTupleHeaderGetXvac(tuphdr);
717798

718-
switch (get_xid_status(raw_xmin, ctx, &status))
799+
switch (get_xid_status(xvac, ctx, &xvac_status))
719800
{
720801
case XID_INVALID:
721802
report_corruption(ctx,
722-
pstrdup("raw xmin is invalid"));
803+
pstrdup("old-style VACUUM FULL transaction ID for moved in tuple is invalid"));
723804
return false;
724805
case XID_IN_FUTURE:
725806
report_corruption(ctx,
726-
psprintf("raw xmin %u equals or exceeds next valid transaction ID %u:%u",
727-
raw_xmin,
807+
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u",
808+
xvac,
728809
EpochFromFullTransactionId(ctx->next_fxid),
729810
XidFromFullTransactionId(ctx->next_fxid)));
730-
return false; /* corrupt */
811+
return false;
731812
case XID_PRECEDES_RELMIN:
732813
report_corruption(ctx,
733-
psprintf("raw xmin %u precedes relation freeze threshold %u:%u",
734-
raw_xmin,
814+
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u",
815+
xvac,
735816
EpochFromFullTransactionId(ctx->relfrozenfxid),
736817
XidFromFullTransactionId(ctx->relfrozenfxid)));
737-
return false; /* corrupt */
818+
return false;
738819
case XID_PRECEDES_CLUSTERMIN:
739820
report_corruption(ctx,
740-
psprintf("raw xmin %u precedes oldest valid transaction ID %u:%u",
741-
raw_xmin,
821+
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u",
822+
xvac,
742823
EpochFromFullTransactionId(ctx->oldest_fxid),
743824
XidFromFullTransactionId(ctx->oldest_fxid)));
744-
return false; /* corrupt */
825+
return false;
745826
case XID_BOUNDS_OK:
746-
switch (status)
747-
{
748-
case XID_COMMITTED:
749-
break;
750-
case XID_IN_PROGRESS:
751-
return true; /* insert or delete in progress */
752-
case XID_ABORTED:
753-
return false; /* HEAPTUPLE_DEAD */
754-
}
827+
break;
755828
}
829+
830+
switch (xvac_status)
831+
{
832+
case XID_IS_CURRENT_XID:
833+
report_corruption(ctx,
834+
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID",
835+
xvac));
836+
return false;
837+
case XID_IN_PROGRESS:
838+
report_corruption(ctx,
839+
psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress",
840+
xvac));
841+
return false;
842+
843+
case XID_COMMITTED:
844+
/*
845+
* The original xmin must have committed, because the xvac
846+
* transaction moved it later. Whether it's still alive
847+
* now depends on the status of xmax.
848+
*/
849+
break;
850+
851+
case XID_ABORTED:
852+
/*
853+
* The tuple is dead, because the xvac transaction moved
854+
* it off and comitted. It's checkable, but also prunable.
855+
*/
856+
return true;
857+
}
858+
}
859+
else if (xmin_status != XID_COMMITTED)
860+
{
861+
/*
862+
* Inserting transaction is not in progress, and not committed, so
863+
* it might have changed the TupleDesc in ways we don't know about.
864+
* Thus, don't try to check the tuple structure.
865+
*
866+
* If xmin_status happens to be XID_IS_CURRENT_XID, then in theory
867+
* any such DDL changes ought to be visible to us, so perhaps
868+
* we could check anyway in that case. But, for now, let's be
869+
* conservate and treat this like any other uncommitted insert.
870+
*/
871+
return false;
756872
}
757873
}
758874

759-
if (!(infomask & HEAP_XMAX_INVALID) && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
875+
/*
876+
* Okay, the inserter committed, so it was good at some point. Now what
877+
* about the deleting transaction?
878+
*/
879+
880+
if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
760881
{
761-
if (infomask & HEAP_XMAX_IS_MULTI)
882+
/*
883+
* xmax is a multixact, so sanity-check the MXID. Note that we do this
884+
* prior to checking for HEAP_XMAX_INVALID or HEAP_XMAX_IS_LOCKED_ONLY.
885+
* This might therefore complain about things that wouldn't actually
886+
* be a problem during a normal scan, but eventually we're going to
887+
* have to freeze, and that process will ignore hint bits.
888+
*
889+
* Even if the MXID is out of range, we still know that the original
890+
* insert committed, so we can check the tuple itself. However, we
891+
* can't rule out the possibility that this tuple is dead, so don't
892+
* clear ctx->tuple_could_be_pruned. Possibly we should go ahead and
893+
* clear that flag anyway if HEAP_XMAX_INVALID is set or if
894+
* HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side
895+
* of avoiding possibly-bogus complaints about missing TOAST entries.
896+
*/
897+
xmax = HeapTupleHeaderGetRawXmax(tuphdr);
898+
switch (check_mxid_valid_in_rel(xmax, ctx))
762899
{
763-
XidCommitStatus status;
764-
TransactionId xmax = HeapTupleGetUpdateXid(tuphdr);
900+
case XID_INVALID:
901+
report_corruption(ctx,
902+
pstrdup("multitransaction ID is invalid"));
903+
return true;
904+
case XID_PRECEDES_RELMIN:
905+
report_corruption(ctx,
906+
psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u",
907+
xmax, ctx->relminmxid));
908+
return true;
909+
case XID_PRECEDES_CLUSTERMIN:
910+
report_corruption(ctx,
911+
psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u",
912+
xmax, ctx->oldest_mxact));
913+
return true;
914+
case XID_IN_FUTURE:
915+
report_corruption(ctx,
916+
psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u",
917+
xmax,
918+
ctx->next_mxact));
919+
return true;
920+
case XID_BOUNDS_OK:
921+
break;
922+
}
923+
}
765924

766-
switch (get_xid_status(xmax, ctx, &status))
767-
{
768-
/* not LOCKED_ONLY, so it has to have an xmax */
769-
case XID_INVALID:
770-
report_corruption(ctx,
771-
pstrdup("xmax is invalid"));
772-
return false; /* corrupt */
773-
case XID_IN_FUTURE:
774-
report_corruption(ctx,
775-
psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
776-
xmax,
777-
EpochFromFullTransactionId(ctx->next_fxid),
778-
XidFromFullTransactionId(ctx->next_fxid)));
779-
return false; /* corrupt */
780-
case XID_PRECEDES_RELMIN:
781-
report_corruption(ctx,
782-
psprintf("xmax %u precedes relation freeze threshold %u:%u",
783-
xmax,
784-
EpochFromFullTransactionId(ctx->relfrozenfxid),
785-
XidFromFullTransactionId(ctx->relfrozenfxid)));
786-
return false; /* corrupt */
787-
case XID_PRECEDES_CLUSTERMIN:
788-
report_corruption(ctx,
789-
psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
790-
xmax,
791-
EpochFromFullTransactionId(ctx->oldest_fxid),
792-
XidFromFullTransactionId(ctx->oldest_fxid)));
793-
return false; /* corrupt */
794-
case XID_BOUNDS_OK:
795-
switch (status)
796-
{
797-
case XID_IN_PROGRESS:
798-
return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
799-
case XID_COMMITTED:
800-
case XID_ABORTED:
801-
return false; /* HEAPTUPLE_RECENTLY_DEAD or
802-
* HEAPTUPLE_DEAD */
803-
}
804-
}
925+
if (tuphdr->t_infomask & HEAP_XMAX_INVALID)
926+
{
927+
/*
928+
* This tuple is live. A concurrently running transaction could
929+
* delete it before we get around to checking the toast, but any such
930+
* running transaction is surely not less than our safe_xmin, so the
931+
* toast cannot be vacuumed out from under us.
932+
*/
933+
ctx->tuple_could_be_pruned = false;
934+
return true;
935+
}
805936

806-
/* Ok, the tuple is live */
937+
if (HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask))
938+
{
939+
/*
940+
* "Deleting" xact really only locked it, so the tuple is live in any
941+
* case. As above, a concurrently running transaction could delete
942+
* it, but it cannot be vacuumed out from under us.
943+
*/
944+
ctx->tuple_could_be_pruned = false;
945+
return true;
946+
}
947+
948+
if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
949+
{
950+
/*
951+
* We already checked above that this multixact is within limits for
952+
* this table. Now check the update xid from this multixact.
953+
*/
954+
xmax = HeapTupleGetUpdateXid(tuphdr);
955+
switch (get_xid_status(xmax, ctx, &xmax_status))
956+
{
957+
case XID_INVALID:
958+
/* not LOCKED_ONLY, so it has to have an xmax */
959+
report_corruption(ctx,
960+
pstrdup("update xid is invalid"));
961+
return true;
962+
case XID_IN_FUTURE:
963+
report_corruption(ctx,
964+
psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u",
965+
xmax,
966+
EpochFromFullTransactionId(ctx->next_fxid),
967+
XidFromFullTransactionId(ctx->next_fxid)));
968+
return true;
969+
case XID_PRECEDES_RELMIN:
970+
report_corruption(ctx,
971+
psprintf("update xid %u precedes relation freeze threshold %u:%u",
972+
xmax,
973+
EpochFromFullTransactionId(ctx->relfrozenfxid),
974+
XidFromFullTransactionId(ctx->relfrozenfxid)));
975+
return true;
976+
case XID_PRECEDES_CLUSTERMIN:
977+
report_corruption(ctx,
978+
psprintf("update xid %u precedes oldest valid transaction ID %u:%u",
979+
xmax,
980+
EpochFromFullTransactionId(ctx->oldest_fxid),
981+
XidFromFullTransactionId(ctx->oldest_fxid)));
982+
return true;
983+
case XID_BOUNDS_OK:
984+
break;
807985
}
808-
else if (!(infomask & HEAP_XMAX_COMMITTED))
809-
return true; /* HEAPTUPLE_DELETE_IN_PROGRESS or
810-
* HEAPTUPLE_LIVE */
811-
else
812-
return false; /* HEAPTUPLE_RECENTLY_DEAD or HEAPTUPLE_DEAD */
986+
987+
switch (xmax_status)
988+
{
989+
case XID_IS_CURRENT_XID:
990+
case XID_IN_PROGRESS:
991+
992+
/*
993+
* The delete is in progress, so it cannot be visible to our
994+
* snapshot.
995+
*/
996+
ctx->tuple_could_be_pruned = false;
997+
break;
998+
case XID_COMMITTED:
999+
1000+
/*
1001+
* The delete committed. Whether the toast can be vacuumed
1002+
* away depends on how old the deleting transaction is.
1003+
*/
1004+
ctx->tuple_could_be_pruned = TransactionIdPrecedes(xmax,
1005+
ctx->safe_xmin);
1006+
break;
1007+
case XID_ABORTED:
1008+
/*
1009+
* The delete aborted or crashed. The tuple is still live.
1010+
*/
1011+
ctx->tuple_could_be_pruned = false;
1012+
break;
1013+
}
1014+
1015+
/* Tuple itself is checkable even if it's dead. */
1016+
return true;
1017+
}
1018+
1019+
/* xmax is an XID, not a MXID. Sanity check it. */
1020+
xmax = HeapTupleHeaderGetRawXmax(tuphdr);
1021+
switch (get_xid_status(xmax, ctx, &xmax_status))
1022+
{
1023+
case XID_IN_FUTURE:
1024+
report_corruption(ctx,
1025+
psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
1026+
xmax,
1027+
EpochFromFullTransactionId(ctx->next_fxid),
1028+
XidFromFullTransactionId(ctx->next_fxid)));
1029+
return false; /* corrupt */
1030+
case XID_PRECEDES_RELMIN:
1031+
report_corruption(ctx,
1032+
psprintf("xmax %u precedes relation freeze threshold %u:%u",
1033+
xmax,
1034+
EpochFromFullTransactionId(ctx->relfrozenfxid),
1035+
XidFromFullTransactionId(ctx->relfrozenfxid)));
1036+
return false; /* corrupt */
1037+
case XID_PRECEDES_CLUSTERMIN:
1038+
report_corruption(ctx,
1039+
psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
1040+
xmax,
1041+
EpochFromFullTransactionId(ctx->oldest_fxid),
1042+
XidFromFullTransactionId(ctx->oldest_fxid)));
1043+
return false; /* corrupt */
1044+
case XID_BOUNDS_OK:
1045+
case XID_INVALID:
1046+
break;
8131047
}
814-
return true; /* not dead */
1048+
1049+
/*
1050+
* Whether the toast can be vacuumed away depends on how old the deleting
1051+
* transaction is.
1052+
*/
1053+
switch (xmax_status)
1054+
{
1055+
case XID_IS_CURRENT_XID:
1056+
case XID_IN_PROGRESS:
1057+
1058+
/*
1059+
* The delete is in progress, so it cannot be visible to our
1060+
* snapshot.
1061+
*/
1062+
ctx->tuple_could_be_pruned = false;
1063+
break;
1064+
1065+
case XID_COMMITTED:
1066+
/*
1067+
* The delete committed. Whether the toast can be vacuumed away
1068+
* depends on how old the deleting transaction is.
1069+
*/
1070+
ctx->tuple_could_be_pruned = TransactionIdPrecedes(xmax,
1071+
ctx->safe_xmin);
1072+
break;
1073+
1074+
case XID_ABORTED:
1075+
/*
1076+
* The delete aborted or crashed. The tuple is still live.
1077+
*/
1078+
ctx->tuple_could_be_pruned = false;
1079+
break;
1080+
}
1081+
1082+
/* Tuple itself is checkable even if it's dead. */
1083+
return true;
8151084
}
8161085

1086+
8171087
/*
8181088
* Check the current toast tuple against the state tracked in ctx, recording
8191089
* any corruption found in ctx->tupstore.
@@ -1247,7 +1517,10 @@ check_tuple(HeapCheckContext *ctx)
12471517
* corrupt to continue checking, or if the tuple is not visible to anyone,
12481518
* we cannot continue with other checks.
12491519
*/
1250-
if (!check_tuple_header_and_visibilty(ctx->tuphdr, ctx))
1520+
if (!check_tuple_header(ctx))
1521+
return;
1522+
1523+
if (!check_tuple_visibility(ctx))
12511524
return;
12521525

12531526
/*
@@ -1448,13 +1721,13 @@ get_xid_status(TransactionId xid, HeapCheckContext *ctx,
14481721
if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid))
14491722
{
14501723
if (TransactionIdIsCurrentTransactionId(xid))
1724+
*status = XID_IS_CURRENT_XID;
1725+
else if (TransactionIdIsInProgress(xid))
14511726
*status = XID_IN_PROGRESS;
14521727
else if (TransactionIdDidCommit(xid))
14531728
*status = XID_COMMITTED;
1454-
else if (TransactionIdDidAbort(xid))
1455-
*status = XID_ABORTED;
14561729
else
1457-
*status = XID_IN_PROGRESS;
1730+
*status = XID_ABORTED;
14581731
}
14591732
LWLockRelease(XactTruncationLock);
14601733
ctx->cached_xid = xid;

0 commit comments

Comments
 (0)
Please sign in to comment.