/* adapted from Postgres' txid.c#convert_xid function */ uint64 convert_xid(TransactionId xid) { TxidEpoch state; uint64 epoch; GetNextXidAndEpoch(&state.last_xid, &state.epoch); /* return special xid's as-is */ if (!TransactionIdIsNormal(xid)) return (uint64) xid; /* xid can be on either side when near wrap-around */ epoch = (uint64) state.epoch; if (xid > state.last_xid && TransactionIdPrecedes(xid, state.last_xid)) epoch--; else if (xid < state.last_xid && TransactionIdFollows(xid, state.last_xid)) epoch++; return (epoch << 32) | xid; }
/* * Hot Standby feedback */ static void ProcessStandbyHSFeedbackMessage(void) { StandbyHSFeedbackMessage reply; TransactionId newxmin = InvalidTransactionId; pq_copymsgbytes(&reply_message, (char *) &reply, sizeof(StandbyHSFeedbackMessage)); elog(DEBUG2, "hot standby feedback xmin %u epoch %u", reply.xmin, reply.epoch); /* * Update the WalSender's proc xmin to allow it to be visible to * snapshots. This will hold back the removal of dead rows and thereby * prevent the generation of cleanup conflicts on the standby server. */ if (TransactionIdIsValid(reply.xmin)) { TransactionId nextXid; uint32 nextEpoch; bool epochOK = false; GetNextXidAndEpoch(&nextXid, &nextEpoch); /* * Epoch of oldestXmin should be same as standby or if the counter has * wrapped, then one less than reply. */ if (reply.xmin <= nextXid) { if (reply.epoch == nextEpoch) epochOK = true; } else { if (nextEpoch > 0 && reply.epoch == nextEpoch - 1) epochOK = true; } /* * Feedback from standby must not go backwards, nor should it go * forwards further than our most recent xid. */ if (epochOK && TransactionIdPrecedesOrEquals(reply.xmin, nextXid)) { if (!TransactionIdIsValid(MyProc->xmin)) { TransactionId oldestXmin = GetOldestXmin(true, true); if (TransactionIdPrecedes(oldestXmin, reply.xmin)) newxmin = reply.xmin; else newxmin = oldestXmin; } else { if (TransactionIdPrecedes(MyProc->xmin, reply.xmin)) newxmin = reply.xmin; else newxmin = MyProc->xmin; /* stay the same */ } } } /* * Grab the ProcArrayLock to set xmin, or invalidate for bad reply */ if (MyProc->xmin != newxmin) { LWLockAcquire(ProcArrayLock, LW_SHARED); MyProc->xmin = newxmin; LWLockRelease(ProcArrayLock); } }
/* * Fetch epoch data from xact.c. */ static void load_xid_epoch(TxidEpoch *state) { GetNextXidAndEpoch(&state->last_xid, &state->epoch); }
/* * Hot Standby feedback */ static void ProcessStandbyHSFeedbackMessage(void) { StandbyHSFeedbackMessage reply; TransactionId nextXid; uint32 nextEpoch; /* Decipher the reply message */ pq_copymsgbytes(&reply_message, (char *) &reply, sizeof(StandbyHSFeedbackMessage)); elog(DEBUG2, "hot standby feedback xmin %u epoch %u", reply.xmin, reply.epoch); /* Ignore invalid xmin (can't actually happen with current walreceiver) */ if (!TransactionIdIsNormal(reply.xmin)) return; /* * Check that the provided xmin/epoch are sane, that is, not in the future * and not so far back as to be already wrapped around. Ignore if not. * * Epoch of nextXid should be same as standby, or if the counter has * wrapped, then one greater than standby. */ GetNextXidAndEpoch(&nextXid, &nextEpoch); if (reply.xmin <= nextXid) { if (reply.epoch != nextEpoch) return; } else { if (reply.epoch + 1 != nextEpoch) return; } if (!TransactionIdPrecedesOrEquals(reply.xmin, nextXid)) return; /* epoch OK, but it's wrapped around */ /* * Set the WalSender's xmin equal to the standby's requested xmin, so that * the xmin will be taken into account by GetOldestXmin. This will hold * back the removal of dead rows and thereby prevent the generation of * cleanup conflicts on the standby server. * * There is a small window for a race condition here: although we just * checked that reply.xmin precedes nextXid, the nextXid could have gotten * advanced between our fetching it and applying the xmin below, perhaps * far enough to make reply.xmin wrap around. In that case the xmin we * set here would be "in the future" and have no effect. No point in * worrying about this since it's too late to save the desired data * anyway. Assuming that the standby sends us an increasing sequence of * xmins, this could only happen during the first reply cycle, else our * own xmin would prevent nextXid from advancing so far. * * We don't bother taking the ProcArrayLock here. Setting the xmin field * is assumed atomic, and there's no real need to prevent a concurrent * GetOldestXmin. (If we're moving our xmin forward, this is obviously * safe, and if we're moving it backwards, well, the data is at risk * already since a VACUUM could have just finished calling GetOldestXmin.) */ MyPgXact->xmin = reply.xmin; }
/* * Send hot standby feedback message to primary, plus the current time, * in case they don't have a watch. */ static void XLogWalRcvSendHSFeedback(void) { char buf[sizeof(StandbyHSFeedbackMessage) + 1]; TimestampTz now; TransactionId nextXid; uint32 nextEpoch; TransactionId xmin; /* * If the user doesn't want status to be reported to the master, be sure * to exit before doing anything at all. */ if (wal_receiver_status_interval <= 0 || !hot_standby_feedback) return; /* Get current timestamp. */ now = GetCurrentTimestamp(); /* * Send feedback at most once per wal_receiver_status_interval. */ if (!TimestampDifferenceExceeds(feedback_message.sendTime, now, wal_receiver_status_interval * 1000)) return; /* * If Hot Standby is not yet active there is nothing to send. Check this * after the interval has expired to reduce number of calls. */ if (!HotStandbyActive()) return; /* * Make the expensive call to get the oldest xmin once we are certain * everything else has been checked. */ xmin = GetOldestXmin(true, false); /* * Get epoch and adjust if nextXid and oldestXmin are different sides of * the epoch boundary. */ GetNextXidAndEpoch(&nextXid, &nextEpoch); if (nextXid < xmin) nextEpoch--; /* * Always send feedback message. */ feedback_message.sendTime = now; feedback_message.xmin = xmin; feedback_message.epoch = nextEpoch; elog(DEBUG2, "sending hot standby feedback xmin %u epoch %u", feedback_message.xmin, feedback_message.epoch); /* Prepend with the message type and send it. */ buf[0] = 'h'; memcpy(&buf[1], &feedback_message, sizeof(StandbyHSFeedbackMessage)); walrcv_send(buf, sizeof(StandbyHSFeedbackMessage) + 1); }