@@ -5264,6 +5264,8 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
5264
5264
* so we need no external state checks to decide what to do. (This is good
5265
5265
* because this function is applied during WAL recovery, when we don't have
5266
5266
* access to any such state, and can't depend on the hint bits to be set.)
5267
+ * There is an exception we make which is to assume GetMultiXactIdMembers can
5268
+ * be called during recovery.
5267
5269
*
5268
5270
* Similarly, cutoff_multi must be less than or equal to the smallest
5269
5271
* MultiXactId used by any transaction currently open.
@@ -5279,14 +5281,19 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
5279
5281
* exclusive lock ensures no other backend is in process of checking the
5280
5282
* tuple status. Also, getting exclusive lock makes it safe to adjust the
5281
5283
* infomask bits.
5284
+ *
5285
+ * NB: Cannot rely on hint bits here, they might not be set after a crash or
5286
+ * on a standby.
5282
5287
*/
5283
5288
bool
5284
5289
heap_freeze_tuple (HeapTupleHeader tuple , TransactionId cutoff_xid ,
5285
5290
MultiXactId cutoff_multi )
5286
5291
{
5287
5292
bool changed = false;
5293
+ bool freeze_xmax = false;
5288
5294
TransactionId xid ;
5289
5295
5296
+ /* Process xmin */
5290
5297
xid = HeapTupleHeaderGetXmin (tuple );
5291
5298
if (TransactionIdIsNormal (xid ) &&
5292
5299
TransactionIdPrecedes (xid , cutoff_xid ))
@@ -5303,16 +5310,112 @@ heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
5303
5310
}
5304
5311
5305
5312
/*
5306
- * Note that this code handles IS_MULTI Xmax values, too, but only to mark
5307
- * the tuple as not updated if the multixact is below the cutoff Multixact
5308
- * given; it doesn't remove dead members of a very old multixact.
5313
+ * Process xmax. To thoroughly examine the current Xmax value we need to
5314
+ * resolve a MultiXactId to its member Xids, in case some of them are
5315
+ * below the given cutoff for Xids. In that case, those values might need
5316
+ * freezing, too. Also, if a multi needs freezing, we cannot simply take
5317
+ * it out --- if there's a live updater Xid, it needs to be kept.
5318
+ *
5319
+ * Make sure to keep heap_tuple_needs_freeze in sync with this.
5309
5320
*/
5310
5321
xid = HeapTupleHeaderGetRawXmax (tuple );
5311
- if ((tuple -> t_infomask & HEAP_XMAX_IS_MULTI ) ?
5312
- (MultiXactIdIsValid (xid ) &&
5313
- MultiXactIdPrecedes (xid , cutoff_multi )) :
5314
- (TransactionIdIsNormal (xid ) &&
5315
- TransactionIdPrecedes (xid , cutoff_xid )))
5322
+
5323
+ if (tuple -> t_infomask & HEAP_XMAX_IS_MULTI )
5324
+ {
5325
+ if (!MultiXactIdIsValid (xid ))
5326
+ {
5327
+ /* no xmax set, ignore */
5328
+ ;
5329
+ }
5330
+ else if (MultiXactIdPrecedes (xid , cutoff_multi ))
5331
+ {
5332
+ /*
5333
+ * This old multi cannot possibly be running. If it was a locker
5334
+ * only, it can be removed without much further thought; but if it
5335
+ * contained an update, we need to preserve it.
5336
+ */
5337
+ if (HEAP_XMAX_IS_LOCKED_ONLY (tuple -> t_infomask ))
5338
+ freeze_xmax = true;
5339
+ else
5340
+ {
5341
+ TransactionId update_xid ;
5342
+
5343
+ update_xid = HeapTupleGetUpdateXid (tuple );
5344
+
5345
+ /*
5346
+ * The multixact has an update hidden within. Get rid of it.
5347
+ *
5348
+ * If the update_xid is below the cutoff_xid, it necessarily
5349
+ * must be an aborted transaction. In a primary server, such
5350
+ * an Xmax would have gotten marked invalid by
5351
+ * HeapTupleSatisfiesVacuum, but in a replica that is not
5352
+ * called before we are, so deal with it in the same way.
5353
+ *
5354
+ * If not below the cutoff_xid, then the tuple would have been
5355
+ * pruned by vacuum, if the update committed long enough ago,
5356
+ * and we wouldn't be freezing it; so it's either recently
5357
+ * committed, or in-progress. Deal with this by setting the
5358
+ * Xmax to the update Xid directly and remove the IS_MULTI
5359
+ * bit. (We know there cannot be running lockers in this
5360
+ * multi, because it's below the cutoff_multi value.)
5361
+ */
5362
+
5363
+ if (TransactionIdPrecedes (update_xid , cutoff_xid ))
5364
+ {
5365
+ Assert (InRecovery || TransactionIdDidAbort (update_xid ));
5366
+ freeze_xmax = true;
5367
+ }
5368
+ else
5369
+ {
5370
+ Assert (InRecovery || !TransactionIdIsInProgress (update_xid ));
5371
+ tuple -> t_infomask &= ~HEAP_XMAX_BITS ;
5372
+ HeapTupleHeaderSetXmax (tuple , update_xid );
5373
+ changed = true;
5374
+ }
5375
+ }
5376
+ }
5377
+ else if (HEAP_XMAX_IS_LOCKED_ONLY (tuple -> t_infomask ))
5378
+ {
5379
+ /* newer than the cutoff, so don't touch it */
5380
+ ;
5381
+ }
5382
+ else
5383
+ {
5384
+ TransactionId update_xid ;
5385
+
5386
+ /*
5387
+ * This is a multixact which is not marked LOCK_ONLY, but which
5388
+ * is newer than the cutoff_multi. If the update_xid is below the
5389
+ * cutoff_xid point, then we can just freeze the Xmax in the
5390
+ * tuple, removing it altogether. This seems simple, but there
5391
+ * are several underlying assumptions:
5392
+ *
5393
+ * 1. A tuple marked by an multixact containing a very old
5394
+ * committed update Xid would have been pruned away by vacuum; we
5395
+ * wouldn't be freezing this tuple at all.
5396
+ *
5397
+ * 2. There cannot possibly be any live locking members remaining
5398
+ * in the multixact. This is because if they were alive, the
5399
+ * update's Xid would had been considered, via the lockers'
5400
+ * snapshot's Xmin, as part the cutoff_xid.
5401
+ *
5402
+ * 3. We don't create new MultiXacts via MultiXactIdExpand() that
5403
+ * include a very old aborted update Xid: in that function we only
5404
+ * include update Xids corresponding to transactions that are
5405
+ * committed or in-progress.
5406
+ */
5407
+ update_xid = HeapTupleGetUpdateXid (tuple );
5408
+ if (TransactionIdPrecedes (update_xid , cutoff_xid ))
5409
+ freeze_xmax = true;
5410
+ }
5411
+ }
5412
+ else if (TransactionIdIsNormal (xid ) &&
5413
+ TransactionIdPrecedes (xid , cutoff_xid ))
5414
+ {
5415
+ freeze_xmax = true;
5416
+ }
5417
+
5418
+ if (freeze_xmax )
5316
5419
{
5317
5420
HeapTupleHeaderSetXmax (tuple , InvalidTransactionId );
5318
5421
@@ -5632,6 +5735,9 @@ ConditionalMultiXactIdWait(MultiXactId multi, MultiXactStatus status,
5632
5735
*
5633
5736
* It doesn't matter whether the tuple is alive or dead, we are checking
5634
5737
* to see if a tuple needs to be removed or frozen to avoid wraparound.
5738
+ *
5739
+ * NB: Cannot rely on hint bits here, they might not be set after a crash or
5740
+ * on a standby.
5635
5741
*/
5636
5742
bool
5637
5743
heap_tuple_needs_freeze (HeapTupleHeader tuple , TransactionId cutoff_xid ,
@@ -5644,24 +5750,42 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
5644
5750
TransactionIdPrecedes (xid , cutoff_xid ))
5645
5751
return true;
5646
5752
5647
- if (!(tuple -> t_infomask & HEAP_XMAX_INVALID ))
5753
+ /*
5754
+ * The considerations for multixacts are complicated; look at
5755
+ * heap_freeze_tuple for justifications. This routine had better be in
5756
+ * sync with that one!
5757
+ */
5758
+ if (tuple -> t_infomask & HEAP_XMAX_IS_MULTI )
5648
5759
{
5649
- if (!(tuple -> t_infomask & HEAP_XMAX_IS_MULTI ))
5760
+ MultiXactId multi ;
5761
+
5762
+ multi = HeapTupleHeaderGetRawXmax (tuple );
5763
+ if (!MultiXactIdIsValid (multi ))
5650
5764
{
5651
- xid = HeapTupleHeaderGetRawXmax (tuple );
5652
- if (TransactionIdIsNormal (xid ) &&
5653
- TransactionIdPrecedes (xid , cutoff_xid ))
5654
- return true;
5765
+ /* no xmax set, ignore */
5766
+ ;
5767
+ }
5768
+ else if (MultiXactIdPrecedes (multi , cutoff_multi ))
5769
+ return true;
5770
+ else if (HEAP_XMAX_IS_LOCKED_ONLY (tuple -> t_infomask ))
5771
+ {
5772
+ /* only-locker multis don't need internal examination */
5773
+ ;
5655
5774
}
5656
5775
else
5657
5776
{
5658
- MultiXactId multi ;
5659
-
5660
- multi = HeapTupleHeaderGetRawXmax (tuple );
5661
- if (MultiXactIdPrecedes (multi , cutoff_multi ))
5777
+ if (TransactionIdPrecedes (HeapTupleGetUpdateXid (tuple ),
5778
+ cutoff_xid ))
5662
5779
return true;
5663
5780
}
5664
5781
}
5782
+ else
5783
+ {
5784
+ xid = HeapTupleHeaderGetRawXmax (tuple );
5785
+ if (TransactionIdIsNormal (xid ) &&
5786
+ TransactionIdPrecedes (xid , cutoff_xid ))
5787
+ return true;
5788
+ }
5665
5789
5666
5790
if (tuple -> t_infomask & HEAP_MOVED )
5667
5791
{
0 commit comments