@@ -5247,6 +5247,8 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
52475247 * so we need no external state checks to decide what to do. (This is good
52485248 * because this function is applied during WAL recovery, when we don't have
52495249 * access to any such state, and can't depend on the hint bits to be set.)
5250+ * There is an exception we make which is to assume GetMultiXactIdMembers can
5251+ * be called during recovery.
52505252 *
52515253 * Similarly, cutoff_multi must be less than or equal to the smallest
52525254 * MultiXactId used by any transaction currently open.
@@ -5262,14 +5264,19 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
52625264 * exclusive lock ensures no other backend is in process of checking the
52635265 * tuple status. Also, getting exclusive lock makes it safe to adjust the
52645266 * infomask bits.
5267+ *
5268+ * NB: Cannot rely on hint bits here, they might not be set after a crash or
5269+ * on a standby.
52655270 */
52665271bool
52675272heap_freeze_tuple (HeapTupleHeader tuple , TransactionId cutoff_xid ,
52685273 MultiXactId cutoff_multi )
52695274{
52705275 bool changed = false;
5276+ bool freeze_xmax = false;
52715277 TransactionId xid ;
52725278
5279+ /* Process xmin */
52735280 xid = HeapTupleHeaderGetXmin (tuple );
52745281 if (TransactionIdIsNormal (xid ) &&
52755282 TransactionIdPrecedes (xid , cutoff_xid ))
@@ -5286,16 +5293,112 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
52865293 }
52875294
52885295 /*
5289- * Note that this code handles IS_MULTI Xmax values, too, but only to mark
5290- * the tuple as not updated if the multixact is below the cutoff Multixact
5291- * given; it doesn't remove dead members of a very old multixact.
5296+ * Process xmax. To thoroughly examine the current Xmax value we need to
5297+ * resolve a MultiXactId to its member Xids, in case some of them are
5298+ * below the given cutoff for Xids. In that case, those values might need
5299+ * freezing, too. Also, if a multi needs freezing, we cannot simply take
5300+ * it out --- if there's a live updater Xid, it needs to be kept.
5301+ *
5302+ * Make sure to keep heap_tuple_needs_freeze in sync with this.
52925303 */
52935304 xid = HeapTupleHeaderGetRawXmax (tuple );
5294- if ((tuple -> t_infomask & HEAP_XMAX_IS_MULTI ) ?
5295- (MultiXactIdIsValid (xid ) &&
5296- MultiXactIdPrecedes (xid , cutoff_multi )) :
5297- (TransactionIdIsNormal (xid ) &&
5298- TransactionIdPrecedes (xid , cutoff_xid )))
5305+
5306+ if (tuple -> t_infomask & HEAP_XMAX_IS_MULTI )
5307+ {
5308+ if (!MultiXactIdIsValid (xid ))
5309+ {
5310+ /* no xmax set, ignore */
5311+ ;
5312+ }
5313+ else if (MultiXactIdPrecedes (xid , cutoff_multi ))
5314+ {
5315+ /*
5316+ * This old multi cannot possibly be running. If it was a locker
5317+ * only, it can be removed without much further thought; but if it
5318+ * contained an update, we need to preserve it.
5319+ */
5320+ if (HEAP_XMAX_IS_LOCKED_ONLY (tuple -> t_infomask ))
5321+ freeze_xmax = true;
5322+ else
5323+ {
5324+ TransactionId update_xid ;
5325+
5326+ update_xid = HeapTupleGetUpdateXid (tuple );
5327+
5328+ /*
5329+ * The multixact has an update hidden within. Get rid of it.
5330+ *
5331+ * If the update_xid is below the cutoff_xid, it necessarily
5332+ * must be an aborted transaction. In a primary server, such
5333+ * an Xmax would have gotten marked invalid by
5334+ * HeapTupleSatisfiesVacuum, but in a replica that is not
5335+ * called before we are, so deal with it in the same way.
5336+ *
5337+ * If not below the cutoff_xid, then the tuple would have been
5338+ * pruned by vacuum, if the update committed long enough ago,
5339+ * and we wouldn't be freezing it; so it's either recently
5340+ * committed, or in-progress. Deal with this by setting the
5341+ * Xmax to the update Xid directly and remove the IS_MULTI
5342+ * bit. (We know there cannot be running lockers in this
5343+ * multi, because it's below the cutoff_multi value.)
5344+ */
5345+
5346+ if (TransactionIdPrecedes (update_xid , cutoff_xid ))
5347+ {
5348+ Assert (InRecovery || TransactionIdDidAbort (update_xid ));
5349+ freeze_xmax = true;
5350+ }
5351+ else
5352+ {
5353+ Assert (InRecovery || !TransactionIdIsInProgress (update_xid ));
5354+ tuple -> t_infomask &= ~HEAP_XMAX_BITS ;
5355+ HeapTupleHeaderSetXmax (tuple , update_xid );
5356+ changed = true;
5357+ }
5358+ }
5359+ }
5360+ else if (HEAP_XMAX_IS_LOCKED_ONLY (tuple -> t_infomask ))
5361+ {
5362+ /* newer than the cutoff, so don't touch it */
5363+ ;
5364+ }
5365+ else
5366+ {
5367+ TransactionId update_xid ;
5368+
5369+ /*
5370+ * This is a multixact which is not marked LOCK_ONLY, but which
5371+ * is newer than the cutoff_multi. If the update_xid is below the
5372+ * cutoff_xid point, then we can just freeze the Xmax in the
5373+ * tuple, removing it altogether. This seems simple, but there
5374+ * are several underlying assumptions:
5375+ *
5376+ * 1. A tuple marked by an multixact containing a very old
5377+ * committed update Xid would have been pruned away by vacuum; we
5378+ * wouldn't be freezing this tuple at all.
5379+ *
5380+ * 2. There cannot possibly be any live locking members remaining
5381+ * in the multixact. This is because if they were alive, the
5382+ * update's Xid would had been considered, via the lockers'
5383+ * snapshot's Xmin, as part the cutoff_xid.
5384+ *
5385+ * 3. We don't create new MultiXacts via MultiXactIdExpand() that
5386+ * include a very old aborted update Xid: in that function we only
5387+ * include update Xids corresponding to transactions that are
5388+ * committed or in-progress.
5389+ */
5390+ update_xid = HeapTupleGetUpdateXid (tuple );
5391+ if (TransactionIdPrecedes (update_xid , cutoff_xid ))
5392+ freeze_xmax = true;
5393+ }
5394+ }
5395+ else if (TransactionIdIsNormal (xid ) &&
5396+ TransactionIdPrecedes (xid , cutoff_xid ))
5397+ {
5398+ freeze_xmax = true;
5399+ }
5400+
5401+ if (freeze_xmax )
52995402 {
53005403 HeapTupleHeaderSetXmax (tuple , InvalidTransactionId );
53015404
@@ -5615,6 +5718,9 @@ ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
56155718 *
56165719 * It doesn't matter whether the tuple is alive or dead, we are checking
56175720 * to see if a tuple needs to be removed or frozen to avoid wraparound.
5721+ *
5722+ * NB: Cannot rely on hint bits here, they might not be set after a crash or
5723+ * on a standby.
56185724 */
56195725bool
56205726heap_tuple_needs_freeze (HeapTupleHeader tuple , TransactionId cutoff_xid ,
@@ -5627,24 +5733,42 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
56275733 TransactionIdPrecedes (xid , cutoff_xid ))
56285734 return true;
56295735
5630- if (!(tuple -> t_infomask & HEAP_XMAX_INVALID ))
5736+ /*
5737+ * The considerations for multixacts are complicated; look at
5738+ * heap_freeze_tuple for justifications. This routine had better be in
5739+ * sync with that one!
5740+ */
5741+ if (tuple -> t_infomask & HEAP_XMAX_IS_MULTI )
56315742 {
5632- if (!(tuple -> t_infomask & HEAP_XMAX_IS_MULTI ))
5743+ MultiXactId multi ;
5744+
5745+ multi = HeapTupleHeaderGetRawXmax (tuple );
5746+ if (!MultiXactIdIsValid (multi ))
56335747 {
5634- xid = HeapTupleHeaderGetRawXmax (tuple );
5635- if (TransactionIdIsNormal (xid ) &&
5636- TransactionIdPrecedes (xid , cutoff_xid ))
5637- return true;
5748+ /* no xmax set, ignore */
5749+ ;
5750+ }
5751+ else if (MultiXactIdPrecedes (multi , cutoff_multi ))
5752+ return true;
5753+ else if (HEAP_XMAX_IS_LOCKED_ONLY (tuple -> t_infomask ))
5754+ {
5755+ /* only-locker multis don't need internal examination */
5756+ ;
56385757 }
56395758 else
56405759 {
5641- MultiXactId multi ;
5642-
5643- multi = HeapTupleHeaderGetRawXmax (tuple );
5644- if (MultiXactIdPrecedes (multi , cutoff_multi ))
5760+ if (TransactionIdPrecedes (HeapTupleGetUpdateXid (tuple ),
5761+ cutoff_xid ))
56455762 return true;
56465763 }
56475764 }
5765+ else
5766+ {
5767+ xid = HeapTupleHeaderGetRawXmax (tuple );
5768+ if (TransactionIdIsNormal (xid ) &&
5769+ TransactionIdPrecedes (xid , cutoff_xid ))
5770+ return true;
5771+ }
56485772
56495773 if (tuple -> t_infomask & HEAP_MOVED )
56505774 {
0 commit comments