Skip to content

Commit 8bfd2ce

Browse files
committed
At update of non-LP_NORMAL TID, fail instead of corrupting page header.
The right mix of DDL and VACUUM could corrupt a catalog page header such that PageIsVerified() durably fails, requiring a restore from backup. This affects only catalogs that both have a syscache and have DDL code that uses syscache tuples to construct updates. One of the test permutations shows a variant not yet fixed. This makes !TransactionIdIsValid(TM_FailureData.xmax) possible with TM_Deleted. I think core and PGXN are indifferent to that. Per bug #17821 from Alexander Lakhin. Back-patch to v13 (all supported versions). The test case is v17+, since it uses INJECTION_POINT. Discussion: https://postgr.es/m/17821-dd8c334263399284@postgresql.org
1 parent 6c1cc35 commit 8bfd2ce

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

src/backend/access/heap/heapam.c

+44-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#include "utils/relcache.h"
7171
#include "utils/snapmgr.h"
7272
#include "utils/spccache.h"
73+
#include "utils/syscache.h"
7374

7475

7576
static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
@@ -3047,7 +3048,49 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
30473048
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
30483049

30493050
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
3050-
Assert(ItemIdIsNormal(lp));
3051+
3052+
/*
3053+
* Usually, a buffer pin and/or snapshot blocks pruning of otid, ensuring
3054+
* we see LP_NORMAL here. When the otid origin is a syscache, we may have
3055+
* neither a pin nor a snapshot. Hence, we may see other LP_ states, each
3056+
* of which indicates concurrent pruning.
3057+
*
3058+
* Failing with TM_Updated would be most accurate. However, unlike other
3059+
* TM_Updated scenarios, we don't know the successor ctid in LP_UNUSED and
3060+
* LP_DEAD cases. While the distinction between TM_Updated and TM_Deleted
3061+
* does matter to SQL statements UPDATE and MERGE, those SQL statements
3062+
* hold a snapshot that ensures LP_NORMAL. Hence, the choice between
3063+
* TM_Updated and TM_Deleted affects only the wording of error messages.
3064+
* Settle on TM_Deleted, for two reasons. First, it avoids complicating
3065+
* the specification of when tmfd->ctid is valid. Second, it creates
3066+
* error log evidence that we took this branch.
3067+
*
3068+
* Since it's possible to see LP_UNUSED at otid, it's also possible to see
3069+
* LP_NORMAL for a tuple that replaced LP_UNUSED. If it's a tuple for an
3070+
* unrelated row, we'll fail with "duplicate key value violates unique".
3071+
* XXX if otid is the live, newer version of the newtup row, we'll discard
3072+
* changes originating in versions of this catalog row after the version
3073+
* the caller got from syscache. See syscache-update-pruned.spec.
3074+
*/
3075+
if (!ItemIdIsNormal(lp))
3076+
{
3077+
Assert(RelationSupportsSysCache(RelationGetRelid(relation)));
3078+
3079+
UnlockReleaseBuffer(buffer);
3080+
Assert(!have_tuple_lock);
3081+
if (vmbuffer != InvalidBuffer)
3082+
ReleaseBuffer(vmbuffer);
3083+
tmfd->ctid = *otid;
3084+
tmfd->xmax = InvalidTransactionId;
3085+
tmfd->cmax = InvalidCommandId;
3086+
3087+
bms_free(hot_attrs);
3088+
bms_free(key_attrs);
3089+
bms_free(id_attrs);
3090+
/* modified_attrs not yet initialized */
3091+
bms_free(interesting_attrs);
3092+
return TM_Deleted;
3093+
}
30513094

30523095
/*
30533096
* Fill in enough data in oldtup for HeapDetermineColumnsInfo to work

src/include/access/tableam.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ typedef enum TM_Result
111111
*
112112
* xmax is the outdating transaction's XID. If the caller wants to visit the
113113
* replacement tuple, it must check that this matches before believing the
114-
* replacement is really a match.
114+
* replacement is really a match. This is InvalidTransactionId if the target
115+
* was !LP_NORMAL (expected only for a TID retrieved from syscache).
115116
*
116117
* cmax is the outdating command's CID, but only when the failure code is
117118
* TM_SelfModified (i.e., something in the current transaction outdated the

0 commit comments

Comments
 (0)