The problem here is in TransactionIdInRecentPast() function. If ShmemVariableCache->oldestClogXid had been stored as a FullTransactionId, this function would be very simple for normal transaction ids: ids between oldestClogXid and next full transaction id are in the recent past (return true), ids before oldestClogXid are too far in the past (return false), ids after next full xid are in the future (raise an error). Expected results are demonstrated in the following picture, where vertical bar is used to separate different epochs.
|pppppppppppppppp|pppppppppppprrff|ffffffffffffffff| ^ ^ | | | next fxid | oldestClogXid
The problem is that oldestClogXid is a TransactionId, so it's epoch is not stored, and the following condition is wrong:
First, given full transaction id (xid_epoch, xid) is compared to the next full transaction id (now_epoch, now_epoch_next_xid), and if it's more than one epoch older, it's far in the past. For newer ids, it's transaction id part without an epoch is compared to oldestClogXid, but it's a modulo-2^32 comparison, so only 2^31 xids before oldestClogXid are considered preceding it. Thus, all full transaction ids between (next full transaction id - 2^32) and (oldestClogXid - 2^31) are mistakenly considered to be in the recent past.
|pppppppppppppprr|rrrrpppppppprrff|ffffffffffffffff| ^ ^ | | | next fxid | oldestClogXid
In the attached patch I suggest calculating an epoch for oldestClogXid assuming it's not much older than next full transaction id, make a full transaction id for oldestClogXid, and then just compare it to the given full transaction id.